Skip to Content

Users API

The Users API provides full lifecycle management for user accounts within a tenant: creating, reading, updating, disabling, and deleting users; assigning roles; importing and exporting users in bulk; and managing phone numbers and two-factor authentication settings for individual accounts.

All endpoints in this section require the x-tenant header and, unless noted otherwise, require the manage:users permission.


User Management

GET/api/usersRequires: manage:users

List all users in the tenant. Supports pagination and filtering by role, account status, and search query. Returns user objects with summary information (does not include 2FA status or session details — use the individual user endpoint for those).

Query parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerItems per page (default: 20, max: 100)
searchstringFull-text search across email, username, firstName, lastName
rolestringFilter by role name
statusactive | disabled | lockedFilter by account status

Success response

{ "ok": true, "data": { "data": [ { "id": "usr_abc123", "email": "[email protected]", "username": "alice", "firstName": "Alice", "lastName": "Smith", "enabled": true, "emailVerified": true, "createdAt": "2025-01-15T10:30:00Z", "roles": ["editor", "viewer"] } ], "pagination": { "page": 1, "limit": 20, "total": 87, "totalPages": 5 } } }

POST/api/usersRequires: manage:users

Create a new user account in the tenant. The user is created in both the Auris database and the underlying Keycloak realm. If password is omitted, the account is created without a password (the user must set one via a magic link or password reset flow).

Request body

{ "email": "[email protected]", "username": "bob", "firstName": "Bob", "lastName": "Jones", "password": "initialPassword123", "roles": ["viewer"], "enabled": true }

All fields except email are optional.

Success response

{ "ok": true, "data": { "id": "usr_def456", "email": "[email protected]", "username": "bob", "firstName": "Bob", "lastName": "Jones", "enabled": true, "emailVerified": false, "createdAt": "2025-02-18T09:00:00Z", "roles": ["viewer"] } }

Error codes

CodeHTTPDescription
EMAIL_TAKEN409A user with this email already exists in the tenant
USERNAME_TAKEN409Username is already in use
VALIDATION_ERROR400Request body failed schema validation

GET/api/users/[id]Requires: manage:users

Retrieve a single user by their ID. Returns full user details including role memberships, 2FA status, phone number, group memberships, and last login information.

Success response

{ "ok": true, "data": { "id": "usr_abc123", "email": "[email protected]", "username": "alice", "firstName": "Alice", "lastName": "Smith", "enabled": true, "emailVerified": true, "phoneNumber": "+39 02 1234567", "phoneNumberVerified": true, "createdAt": "2025-01-15T10:30:00Z", "lastLoginAt": "2025-02-17T14:22:00Z", "roles": ["editor", "viewer"], "twoFactor": { "totpEnabled": true, "smsEnabled": false, "webauthnEnabled": false } } }

Error codes

CodeHTTPDescription
NOT_FOUND404User does not exist in this tenant

PUT/api/users/[id]Requires: manage:users

Update a user’s profile fields. All fields are optional — only provided fields are updated. To disable a user without deleting them, set enabled: false.

Request body

{ "firstName": "Alicia", "lastName": "Smith-Jones", "enabled": false }

Success response

{ "ok": true, "data": { "id": "usr_abc123", "email": "[email protected]", "firstName": "Alicia", "lastName": "Smith-Jones", "enabled": false } }

DELETE/api/users/[id]Requires: manage:users

Soft-delete a user. The user’s account is disabled and marked as deleted in the Auris database, and their Keycloak account is removed. Active sessions are invalidated immediately.

Soft-deletion is irreversible through the API. The user record is retained in the database for audit log consistency but cannot be recovered or logged into. Use enabled: false via PUT /api/users/[id] if you want to temporarily disable without deletion.

Success response

{ "ok": true, "data": { "deleted": true } }

Role Assignment

GET/api/users/[id]/rolesRequires: manage:users

List all roles currently assigned to a user.

Success response

{ "ok": true, "data": [ { "id": "role_123", "name": "editor", "description": "Can create and edit content", "color": "#3b82f6" } ] }

POST/api/users/[id]/rolesRequires: manage:users

Assign a role to a user. The role must exist in the tenant. Assigning the same role twice is a no-op (idempotent).

Request body

{ "roleId": "role_123" }

Success response

{ "ok": true, "data": { "assigned": true } }

Error codes

CodeHTTPDescription
NOT_FOUND404Role does not exist in this tenant
SELF_ROLE_CHANGE403Cannot modify your own roles

DELETE/api/users/[id]/rolesRequires: manage:users

Remove a role from a user. Removing a role the user does not have is a no-op (idempotent).

Request body

{ "roleId": "role_123" }

Success response

{ "ok": true, "data": { "removed": true } }

Bulk Import

POST/api/users/importRequires: admin:all

Import users in bulk from a CSV or JSON file. The import runs asynchronously — the endpoint returns a job ID immediately. Poll GET /api/users/import/[id] to track progress.

Request format: multipart/form-data

FieldTypeDescription
filebinaryCSV or JSON file
formatcsv | jsonFile format
sendWelcomeEmailbooleanSend welcome email to new users (default: false)

CSV format (first row is header):

email,firstName,lastName,username,password,roles [email protected],Alice,Smith,alice,,viewer [email protected],Bob,Jones,bob,TempPass123,editor

JSON format:

[ { "email": "[email protected]", "firstName": "Alice", "lastName": "Smith", "roles": ["viewer"] } ]

Success response (job created)

{ "ok": true, "data": { "jobId": "import_xyz789", "status": "pending", "totalRows": 142, "createdAt": "2025-02-18T10:00:00Z" } }

GET/api/users/importRequires: admin:all

List all import jobs for the current tenant, ordered by creation time descending.

Success response

{ "ok": true, "data": { "data": [ { "id": "import_xyz789", "fileName": "users-2025-02.csv", "format": "csv", "status": "completed", "totalRows": 142, "processedRows": 142, "successCount": 140, "errorCount": 2, "createdAt": "2025-02-18T10:00:00Z", "completedAt": "2025-02-18T10:01:34Z" } ], "pagination": { "page": 1, "limit": 20, "total": 5, "totalPages": 1 } } }

GET/api/users/import/[id]Requires: admin:all

Get the current status and error details of an import job.

Success response

{ "ok": true, "data": { "id": "import_xyz789", "status": "completed", "totalRows": 142, "processedRows": 142, "successCount": 140, "errorCount": 2, "errors": [ { "row": 45, "email": "bad@", "error": "Invalid email address" }, { "row": 98, "email": "[email protected]", "error": "Email already exists" } ] } }

Import job statuses: pending, processing, completed, failed, partial.


Bulk Export

POST/api/users/exportRequires: admin:all

Trigger a user export. The export runs asynchronously. Poll GET /api/users/export to find the job, then download using GET /api/users/export/[id]/download once status is completed.

Request body

{ "format": "csv" }

format is "csv" or "json".

Success response

{ "ok": true, "data": { "id": "export_abc123", "status": "pending", "format": "csv", "createdAt": "2025-02-18T11:00:00Z" } }

GET/api/users/exportRequires: admin:all

List all export jobs.


GET/api/users/export/[id]/downloadRequires: admin:all

Download the export file once the job status is completed. Returns the raw file binary with an appropriate Content-Disposition header.

Export files are stored temporarily and expire after 24 hours. Download them promptly after the job completes.


Phone Number Management

These endpoints allow authenticated users to manage their own phone number. No special permission beyond a valid access token is required.

POST/api/user/phone/setRequires: authenticated user

Set or update the authenticated user’s phone number. After setting, the number must be verified using POST /api/user/phone/verify. An SMS OTP is sent to the provided number.

Request body

{ "phoneNumber": "+39021234567" }

The phone number must be in E.164 format (international format with country code).

Success response

{ "ok": true, "data": { "sent": true, "phoneNumber": "+39021234567" } }

Error codes

CodeHTTPDescription
INVALID_PHONE400Number is not in E.164 format
PHONE_TAKEN409Number is already associated with another account
SMS_RATE_LIMITED429Too many SMS requests (max 5 per hour)

POST/api/user/phone/verifyRequires: authenticated user

Verify the phone number with the OTP code sent via SMS. On success, phoneNumberVerified is set to true on the user account.

Request body

{ "code": "482910" }

Success response

{ "ok": true, "data": { "verified": true } }

Error codes

CodeHTTPDescription
INVALID_OTP400Code is incorrect
OTP_EXPIRED400Code has expired (TTL: 10 minutes)
MAX_ATTEMPTS400Maximum verification attempts exceeded

Two-Factor Authentication

POST/api/user/2fa/sms/enableRequires: authenticated user

Enable SMS OTP as a 2FA method for the authenticated user. Requires a verified phone number. An OTP is sent to confirm the phone can receive codes before enabling.

Request body

{ "code": "123456" }

Provide the OTP that was sent to the user’s verified phone number.

Success response

{ "ok": true, "data": { "smsEnabled": true } }

Error codes

CodeHTTPDescription
PHONE_NOT_VERIFIED400User does not have a verified phone number
INVALID_OTP400Confirmation code is incorrect

POST/api/user/2fa/webauthn/enableRequires: authenticated user

Begin the WebAuthn passkey registration ceremony to enable WebAuthn as a 2FA method. Returns a registration challenge. The client must complete the ceremony using the browser’s WebAuthn API and submit the AuthenticatorAttestationResponse to POST /api/user/2fa/webauthn/challenge.

Request: No body required.

Success response (registration options)

{ "ok": true, "data": { "challenge": "base64url-challenge", "rp": { "name": "Auris", "id": "your-auris-domain.com" }, "user": { "id": "base64url-user-id", "name": "[email protected]", "displayName": "Alice Smith" }, "pubKeyCredParams": [{ "type": "public-key", "alg": -7 }], "timeout": 60000, "attestation": "none" } }

Use the @simplewebauthn/browser library to handle this challenge in the browser.