/api/v1/users/* is the admin user surface. It is called by trusted backends — your management UIs, your batch jobs, your migration scripts. Scoped to users:read (reads) and users:write (mutations).
End-user self-service does NOT happen here; that's the Me API.
List users
Section titled “List users”GET /api/v1/usersAuthorization: Bearer <access-token>Required scope: users:read
Query parameters: cursor — opaque pagination cursor limit — page size (1–250, default 50) email — exact email filter state — 'active' | 'disabled' | 'pending' created_after — ISO timestamp q — free-text search across email + name + attributes{ "data": [ { "id": "usr_01HZX...", "email": "user@cymmetri.com", "email_verified": true, "name": "User Name", "state": "active", "created_at": "2026-01-15T10:00:00Z", "last_signed_in_at": "2026-05-17T08:00:00Z" } ], "meta": { "next_cursor": "...", "limit": 50 }}Create a user
Section titled “Create a user”POST /api/v1/usersAuthorization: Bearer <access-token>Content-Type: application/jsonRequired scope: users:writeIdempotency-Key: <uuid> (recommended)
{ "email": "finance.lead@cymmetri.com", "name": "Finance Lead", "password": "a temporary one", "password_must_be_reset": true, "email_verified": false, "attributes": { "department": "finance" }}Returns the created user. If email_verified is omitted or false, a verification email is sent.
If the email already exists, returns 409 user_exists. If you want "create or get", call GET /api/v1/users?email=... first.
Look up a user
Section titled “Look up a user”GET /api/v1/users/{user_id}Authorization: Bearer <access-token>Required scope: users:readThe detailed user record includes:
{ "data": { "id": "usr_01HZX...", "email": "user@cymmetri.com", "email_verified": true, "name": "User Name", "state": "active", "attributes": { ... }, "mfa_factors": [ { "kind": "webauthn", "count": 2 }, { "kind": "totp", "count": 1 } ], "groups": [ { "id": "grp_01HZX...", "name": "Finance" } ], "applications_used": [ "app_01HZX...", "app_01HZY..." ], "created_at": "...", "updated_at": "...", "last_signed_in_at": "..." }}The DTO summarises sub-resources (factor counts, group memberships, recently-used applications) so a single fetch is enough for a typical admin "user detail" page. Fetch the full lists with the per-sub-resource endpoints when needed.
Update a user
Section titled “Update a user”PATCH /api/v1/users/{user_id}Authorization: Bearer <access-token>Content-Type: application/jsonRequired scope: users:write
{ "name": "Anita Singh", "attributes": { "department": "engineering" }}Fields not in the body are unchanged. Some fields are immutable (id, created_at); the API rejects an attempt to PATCH them.
Disable / enable
Section titled “Disable / enable”Soft state changes that preserve the record:
POST /api/v1/users/{user_id}/disablePOST /api/v1/users/{user_id}/enableRequired scope: users:writeA disabled user cannot sign in. All their sessions are revoked. Their record stays. Re-enabling restores the ability to sign in but does not restore sessions.
Delete a user
Section titled “Delete a user”DELETE /api/v1/users/{user_id}Authorization: Bearer <access-token>Required scope: users:writeHard delete: the user record + all their factors + all their sessions are removed. The audit log entries about them are retained per the tenant's retention policy.
For GDPR-style erasure where you want absolute removal including audit, the data export + erasure flow is in the tenant admin console.
Reset password (admin-initiated)
Section titled “Reset password (admin-initiated)”POST /api/v1/users/{user_id}/reset-passwordAuthorization: Bearer <access-token>Required scope: users:writeSends the user a password-reset email. Does not return a new password.
To set a temporary password directly (operator-set rather than user-set), use PATCH with { "password": "...", "password_must_be_reset": true }. The user is forced to change it on next sign-in.
Force MFA reset
Section titled “Force MFA reset”POST /api/v1/users/{user_id}/force-mfa-resetAuthorization: Bearer <access-token>Required scope: users:writeRemoves all the user's MFA factors. They are forced to re-enrol on next sign-in. Recovery path for users who lost their second factor; pair with a separate user-identity-verification step.
Bulk import
Section titled “Bulk import”POST /api/v1/users/bulk-importAuthorization: Bearer <access-token>Content-Type: application/x-ndjsonRequired scope: users:write
{"email":"a@cymmetri.com","name":"A","password":"..."}{"email":"b@cymmetri.com","name":"B","password":"..."}Streamed input. Returns a job id; poll the job for status:
GET /api/v1/users/bulk-import/{job_id}{ "data": { "id": "job_01HZX...", "state": "running", "imported": 1500, "failed": 3, "errors": [ ... ] }}For tens-of-thousands-of-users migrations, the bulk-import endpoint is preferable to a serial loop over POST /api/v1/users — it batches transactionally on the platform side.
Common errors
Section titled “Common errors”| Error | When |
|---|---|
user_not_found | The id doesn't exist or you can't see it (404) |
user_exists | A user with that email already exists on create |
insufficient_scope | Token doesn't carry the capability this action requires |
password_policy_violation | The password does not meet the tenant's policy |
email_invalid | The email is malformed |