Users
Users are the people who claim and resolve escalations. Each user has a unique external_id that maps to your application’s identity system. This page also covers the self-service /api/me endpoints.
All endpoints require authentication. Creating, updating, patching and deleting users require builder access (superadmin or the engineer role). Adding and removing roles require admin access and follow the scoped role assignment rules. Only a superadmin may grant the superadmin type or change a superadmin account.
For service identities (CI bots, schedulers, automated agents), use the Service accounts API. Service accounts share the same RBAC system and authenticate with API keys.
List users
Section titled “List users”GET /api/usersReturns users with their role assignments.
Query parameters
| Parameter | Type | Description |
|---|---|---|
role |
string |
Filter by role name (for example, reviewer) |
roleType |
string |
Filter by role type: superadmin, admin, or member |
status |
string |
Filter by status: active, inactive, or suspended |
limit |
integer |
Max results (default: 50) |
offset |
integer |
Pagination offset (default: 0) |
Example request
curl "https://lt.acme.dev/api/users?role=reviewer&status=active&limit=10" \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "users": [ { "id": "b2c3d4e5-...", "external_id": "jane", "email": "jane@acme.com", "display_name": "Jane Chen", "status": "active", "metadata": null, "created_at": "2025-01-10T08:00:00.000Z", "updated_at": "2025-01-10T08:00:00.000Z", "roles": [ { "user_id": "b2c3d4e5-...", "role": "reviewer", "type": "member", "read_scope": "all", "write_scope": "all", "created_at": "2025-01-10T08:00:00.000Z" }, { "user_id": "b2c3d4e5-...", "role": "senior-reviewer", "type": "admin", "read_scope": "all", "write_scope": "all", "created_at": "2025-01-10T08:00:00.000Z" } ] } ], "total": 1}Get user details
Section titled “Get user details”GET /api/users/:idReturns one user with their roles.
Path parameters
| Parameter | Description |
|---|---|
id |
User UUID |
Example request
curl https://lt.acme.dev/api/users/b2c3d4e5-... \ -H "Authorization: Bearer $LT_TOKEN"Response 200
A single user object with a roles array.
Response 404
{ "error": "User not found" }Create user
Section titled “Create user”POST /api/usersCreates a user, optionally with initial role assignments.
Scope: Each member grant in roles may carry read_scope and write_scope. Both default to all (the full-queue worker). The constraint is write ⊆ read: write_scope=all requires read_scope=all. See Work-surface scope for the five member profiles.
Auth: Builder access. Every initial role must be one the caller may assign (see scoped role assignment); otherwise 403.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
external_id |
string |
yes | Your application’s identifier for this user |
email |
string |
no | Email address |
display_name |
string |
no | Display name |
roles |
array |
no | Initial role assignments (see below) |
metadata |
object |
no | Arbitrary metadata |
Each element in roles:
| Field | Type | Required | Description |
|---|---|---|---|
role |
string |
yes | Role name |
type |
string |
yes | superadmin, admin, or member |
read_scope |
string |
no | self or all (default all). Search breadth for a member; ignored for admin and superadmin |
write_scope |
string |
no | none, self, or all (default all). Claim, ack and delete breadth for a member |
Example request
curl -X POST https://lt.acme.dev/api/users \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "external_id": "new-user", "email": "new-user@acme.dev", "display_name": "New User", "roles": [ { "role": "reviewer", "type": "member" }, { "role": "customer-triage", "type": "member", "read_scope": "self", "write_scope": "self" }, { "role": "senior-reviewer", "type": "admin" } ] }'Response 201
The created user object with its roles array. Each returned role object includes its read_scope and write_scope.
Response 400
{ "error": "external_id is required" }{ "error": "Each role must have a role name and type (superadmin, admin, member)" }{ "error": "write_scope=all requires read_scope=all" }Response 409
{ "error": "User with this external_id already exists" }Update user
Section titled “Update user”PUT /api/users/:idPartial update: only the fields you include change.
Auth: Builder access. Changing a superadmin account requires superadmin (403 otherwise).
Path parameters
| Parameter | Description |
|---|---|
id |
User UUID |
Request body
| Field | Type | Description |
|---|---|---|
email |
string |
New email address |
display_name |
string |
New display name |
status |
string |
active, inactive, or suspended |
metadata |
object |
Replaces the whole properties dictionary. For per-key edits use PATCH /api/users/:id/properties, which leaves sibling keys in place |
Example request
curl -X PUT https://lt.acme.dev/api/users/b2c3d4e5-... \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "status": "suspended", "metadata": { "suspension_reason": "pending investigation" } }'Response 200
The updated user object.
Response 404
{ "error": "User not found" }Patch user properties
Section titled “Patch user properties”PATCH /api/users/:id/propertiesAtomically patches the user’s properties dictionary (metadata) in one statement. Editing one property leaves every other property intact, and a rename keeps its value at every moment.
Auth: Builder access, the same gate as every other user write. Changing a superadmin account requires superadmin.
Path parameters
| Parameter | Description |
|---|---|
id |
User UUID |
Request body
| Field | Type | Description |
|---|---|---|
set |
object |
Properties to set. Values are typed JSON: numbers, booleans and objects round-trip intact |
remove |
string[] |
Property keys to delete. Deleting is explicit: a key absent from the patch is kept |
rename |
object |
{ "oldKey": "newKey" } renames, values preserved |
Precedence on key collision: set > rename > existing.
Example request
curl -X PATCH https://lt.acme.dev/api/users/b2c3d4e5-.../properties \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "set": { "badgeSlug": "packer-june", "shift": "night" }, "remove": ["legacy_pin"], "rename": { "station": "station_affinity" } }'Response 200
The updated user object.
Response 400
A malformed patch: an empty key, a key both set and removed, or a self-rename.
Response 409
An identity-binding value already belongs to another active user. Keys named as an enabled identity scan scheme’s target_facet (for example, badge_id) are how the platform resolves badge scans to people. The patch asserts, in the same statement, that no other active user carries the value.
{ "error": "\"badge_id\" value is already bound to another active user" }System property keys
Section titled “System property keys”GET /api/users/system-property-keysReturns the property keys the platform resolves identities against: every enabled identity scan scheme’s target_facet. The dashboard marks these as system properties in the editor. See Scan codes.
Example request
curl https://lt.acme.dev/api/users/system-property-keys \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "keys": ["badge_id"] }Delete user
Section titled “Delete user”DELETE /api/users/:idDeletes the user and all of their role assignments (cascade).
Auth: Builder access. Changing a superadmin account requires superadmin (403 otherwise).
Path parameters
| Parameter | Description |
|---|---|
id |
User UUID |
Example request
curl -X DELETE https://lt.acme.dev/api/users/b2c3d4e5-... \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "deleted": true }Response 404
{ "error": "User not found" }Self-service
Section titled “Self-service”The /api/me endpoints operate on the authenticated caller: they take no ids and have no admin gates. Any signed-in user or service account may call them.
Get my preferences
Section titled “Get my preferences”GET /api/me/preferencesReturns the caller’s preferences document, a per-user JSON store for presentation state (pinned views are the first tenant). Reads {} when unset.
Example request
curl https://lt.acme.dev/api/me/preferences \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "preferences": { "pinnedViews": [ { "id": "pin-x1", "label": "Orders past due", "url": "/escalations/available?role=packing&jeopardy=1&view=table", "badge": true } ], "hiddenRolePins": ["My orders"] }}Update my preferences
Section titled “Update my preferences”PATCH /api/me/preferencesShallow-merges the body into the caller’s preferences: top-level keys overwrite whole, and a null value deletes its key. The merge is a single guarded statement, and the stored document is size-capped (about 32 KB). Preferences carry presentation state only (URLs and UI choices), never data or authorization.
Request body
Any JSON object of preference keys.
Example request
curl -X PATCH https://lt.acme.dev/api/me/preferences \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "pinnedViews": [ { "id": "pin-x1", "label": "Orders past due", "url": "/escalations/available?role=packing&jeopardy=1", "badge": true } ], "theme": null }'Response 200
The merged document, { "preferences": { ... } }.
Response 400
The body is not a JSON object.
Response 413
The patch or the merged document would exceed the size cap. Nothing is written.
See Forms for how the dashboard uses pinned views.
