Users
Manage your project's users from a trusted server or AI agent with users.* operations.
Overview
Users is the admin side of Identity: the API for your project's user records, the same accounts that sign in through Authentication. Call it from your backend, or let a support or ops agent manage users within the permissions you grant.
Key features:
- Create users, with or without a password
- Look up users by ID, email, username, or linked provider
- Search and paginate users
- Update profiles and application data
- Suspend users, with scheduled status transitions
- Soft-delete or permanently purge users
- Link and unlink OAuth provider identities
Use cases: Admin dashboards, back-office tools, imports, support agents, moderation
Access: Admin only. Call users.* with a server key (sk_). An agent key can call permitted user operations, including users.get_details with users.list; agent keys can never call users.get. Don't send a bearer token: with one, the request acts as that user and is denied. Never call these operations from a browser.
Self-service vs admin
- A signed-in user reads and edits their own profile with
user.getanduser.update. - Your server manages any user with
users.*. - Passwords, codes, and sessions belong to
auth.*.
Operations
| Operation | Description | Selector |
|---|---|---|
users.create | Create a user | None |
users.get | Get one user | One of user_id, id, email, username; or provider + provider_user_id |
users.get_details | Get a user with providers, login methods, and recent events | One of user_id, id, email, username |
users.query | Search and list users | None |
users.update | Update profile and application data | user_id or id |
users.update_status | Change status, optionally on a schedule | user_id or id |
users.update_password | Set a new password | user_id or id |
users.delete | Soft-delete or purge a user | user_id or id |
users.link_provider | Link a provider identity | user_id or id |
users.unlink_provider | Unlink a provider identity | Provider identity |
id is an alias for user_id in selectors. Send one or the other, not both.
User Fields
| Field | Description |
|---|---|
id | User ID |
email, username, phone | Identity fields |
first_name, last_name, profile_photo | Profile fields |
status | active by default. Other values are up to your app, such as suspended or banned |
requires_password_change | When true, the user must reset their password before signing in |
email_verified_at, phone_verified_at | Verification times |
data | Application data: timezone, locale, metadata, and claims |
Roles live in data.claims, keyed by audience. For example, {"web": ["user"]} gives the role user in the web audience.
Only active users that are not deleted can sign in.
users.create
json
{
"operation": "users.create",
"payload": {
"email": "ada@example.com",
"password": "ExamplePass123!",
"first_name": "Ada",
"last_name": "Lovelace",
"data": {
"timezone": "Europe/London",
"locale": "en-GB",
"metadata": { "plan": "pro" },
"claims": { "web": ["user", "editor"] }
}
}
}Parameters (all optional):
| Field | Description |
|---|---|
user_id | Your own ID. A UUID is generated when omitted |
email, username, phone | Identity fields |
first_name, last_name, profile_photo | Profile fields |
password | Plain password. It is checked against the password policy and stored hashed |
status, status_reason | Initial status. Default active |
requires_password_change | Force a password reset on first signin. Default false |
provider, provider_user_id | A provider identity your server has already verified |
data | Application data. claims defaults to {"web": ["user"]} |
Behavior:
- No welcome email is sent and no session is created. Use
auth.signupfor the normal signup flow. password_hashandpassword_algoare rejected. Sendpassword.- Put roles in
data.claims, notdata.auth.claims.
users.get
json
{
"operation": "users.get",
"payload": { "email": "ada@example.com" }
}Example - By provider identity:
json
{
"operation": "users.get",
"payload": { "provider": "github", "provider_user_id": "827364" }
}Response:
json
{
"data": {
"item": {
"id": "user_123",
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"status": "active",
"data": { "claims": { "web": ["user"] } }
}
}
}The record is returned under data.item. Admin responses are not filtered like user.get; don't forward them to public clients.
users.get_details
Get a user with linked providers, login methods, and recent identity events.
json
{
"operation": "users.get_details",
"payload": { "user_id": "user_123" }
}Accepts one of user_id, id, email, or username. To look up by provider, call users.get first.
users.query
json
{
"operation": "users.query",
"payload": {
"status": "active",
"search": "ada",
"filter": { "data.metadata.plan:$in": ["pro", "enterprise"] },
"page": 1,
"per_page": 25
}
}Parameters (all optional):
| Field | Description |
|---|---|
search | Matches ID, email, username, or phone. Alias q |
status, email, username | Exact matches |
filter | Filter on application data. Paths must start with data. |
page, per_page | Page-based pagination |
limit, offset | Offset-based pagination |
Returns users in data and pagination in meta.pagination. Use status, email, and username instead of filtering those fields.
users.update
json
{
"operation": "users.update",
"payload": {
"user_id": "user_123",
"first_name": "Ada",
"data": { "timezone": "Europe/London" }
}
}Updatable fields: email, username, phone, first_name, last_name, profile_photo, requires_password_change, email_verified_at, phone_verified_at, data
Behavior:
datais merged deeply; nested fields you omit are kept.- To replace
dataentirely, send"data": {"$replace": {...}}. This can removeclaims, so use it with care. - Status and passwords have their own operations.
users.update_status
Suspend, ban, or reactivate a user. Optionally schedule the next status.
json
{
"operation": "users.update_status",
"payload": {
"user_id": "user_123",
"status": "suspended",
"status_reason": "Payment overdue",
"status_expires_in": 172800,
"status_next": "active",
"status_next_reason": "Suspension ended",
"changed_by": "admin:42"
}
}Parameters:
| Field | Description |
|---|---|
status | New status (required) |
status_reason | Why it changed |
status_expires_in | Seconds until status_next applies |
status_expires_at | Time when status_next applies. Use one expiration field only |
status_next | Status to apply when the current one expires. Required with an expiration |
status_next_reason | Reason for the scheduled change |
changed_by | Who made the change, for the audit trail |
A non-active status blocks new signins. Session verification also checks that the user is active, so suspended or deleted users cannot continue authenticated requests. Status updates do not themselves consume every stored session token.
users.update_password
json
{
"operation": "users.update_password",
"payload": {
"user_id": "user_123",
"password": "NewExamplePass123!"
}
}The password is checked against the policy and stored hashed. Existing sessions are revoked. Returns data: {"id": "user_123", "updated": true}.
users.delete
json
{
"operation": "users.delete",
"payload": {
"user_id": "user_123",
"status_reason": "User requested deletion"
}
}Soft delete (default): Sets status to deleted, revokes sessions, and keeps the email and provider identities reserved.
Purge: Add "purge": true to permanently remove the user, providers, tokens, and identity events.
DANGER
Purging cannot be undone. It does not delete the user's documents or files. Remove those separately with data.delete and files.delete.
Provider Identities
users.link_provider
json
{
"operation": "users.link_provider",
"payload": {
"user_id": "user_123",
"provider": "github",
"provider_user_id": "827364",
"email": "ada@example.com"
}
}Requires a user selector, provider, and provider_user_id. Optional: email, data. Only link identities your server has verified.
users.unlink_provider
json
{
"operation": "users.unlink_provider",
"payload": {
"provider": "github",
"provider_user_id": "827364"
}
}Requires provider and provider_user_id. user_id or id is optional.
Common Errors
| Message | Meaning |
|---|---|
AUTHENTICATION_REQUIRED, ACCESS_DENIED | The caller is not a server or agent key, or the request included a user's bearer token |
AGENT_SCOPE_DENIED | The agent key lacks the needed users.* permission |
INVALID_PAYLOAD | Missing or conflicting selectors, or an invalid status schedule |
MISSING_USER_SELECTOR | No user selector was sent |
See Responses for the error envelope.
Next Steps
- Authentication - Signup, signin, and sessions for end users
- Agents - Let an AI agent manage users with scoped permissions
- Data - Query documents owned by a user