Users
Human identities within the SOAT instance, authenticated via username and password.
Overview
Users are global to the SOAT instance — not scoped to any project. The first user is created via the bootstrap endpoint. After that, only authenticated admin users may create additional users. See it end to end in Permissions in Practice — Step 2 (Create regular users).
Users can have Policies attached to them, which control what resources and operations they are permitted to access. See IAM for the full authorization model.
See the Permissions Reference for the IAM action strings for this module.
Related Tutorials
- Permissions in Practice - Step 2 (Create regular users)
- Chat with an LLM - Step 1 (Log in as admin)
- Agent with Persistent Memory - Step 1 (Log in as admin)
Data Model
| Field | Type | Description |
|---|---|---|
id | string | Public identifier prefixed with user_ |
username | string | Unique login name |
role | string | "admin" or "user" — see Roles |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 last-updated timestamp |
Sensitive fields (passwordHash, internal numeric ID) are never exposed in responses.
Key Concepts
Roles
| Role | Description |
|---|---|
admin | Full access to all resources and operations. Bypasses policy evaluation. |
user | Access determined by the policies attached to the account. |
Bootstrap
The POST /api/v1/users/bootstrap endpoint creates the first admin user. It is only available when the user table is empty and returns 409 Conflict if any user already exists. This endpoint does not require authentication.
You can also bootstrap an admin automatically on server startup by setting two environment variables:
SOAT_ADMIN_USERNAME=admin
SOAT_ADMIN_PASSWORD=supersecret
When both variables are present and no users exist, the server creates the admin user before accepting requests. If users already exist, the variables are ignored.
Authentication
Users authenticate via POST /api/v1/users/login with username and password. On success, the server returns a signed JWT containing the user's public ID and role. The token is passed as Authorization: Bearer <token> on subsequent requests. See IAM — Authentication, or Chat with an LLM — Step 1 (Log in as admin) for a worked login example.
Policy Attachment
Policies are attached to a user through the user-policies endpoint, which replaces the user's full policy list. User management operations (create, delete) require the admin role and are not governed by the policy engine.
Examples
Bootstrap first user
- CLI
- SDK
- curl
soat bootstrap-user --username admin --password supersecret
import { SoatClient } from '@soat/sdk';
const soat = new SoatClient({ baseUrl: 'https://api.example.com' });
const { data, error } = await soat.users.bootstrapUser({
body: { username: 'admin', password: 'supersecret' },
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/users/bootstrap \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "supersecret"}'
Login
- CLI
- SDK
- curl
soat login-user --username admin --password supersecret
const { data, error } = await soat.users.loginUser({
body: { username: 'admin', password: 'supersecret' },
});
if (error) throw new Error(JSON.stringify(error));
// data.token is the JWT to use in subsequent requests
curl -X POST https://api.example.com/api/v1/users/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "supersecret"}'
Create an additional user
- CLI
- SDK
- curl
soat create-user --username alice --password alicepass
const authedSoat = new SoatClient({ baseUrl: 'https://api.example.com', token: 'sk_...' });
const { data, error } = await authedSoat.users.createUser({
body: { username: 'alice', password: 'alicepass' },
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/users \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "alicepass"}'