Skip to content

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.

GET /api/users

Returns 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

Terminal window
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 /api/users/:id

Returns one user with their roles.

Path parameters

Parameter Description
id User UUID

Example request

Terminal window
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" }
POST /api/users

Creates 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

Terminal window
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" }
PUT /api/users/:id

Partial 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

Terminal window
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 /api/users/:id/properties

Atomically 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

Terminal window
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" }
GET /api/users/system-property-keys

Returns 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

Terminal window
curl https://lt.acme.dev/api/users/system-property-keys \
-H "Authorization: Bearer $LT_TOKEN"

Response 200

{ "keys": ["badge_id"] }
DELETE /api/users/:id

Deletes 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

Terminal window
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" }

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 /api/me/preferences

Returns the caller’s preferences document, a per-user JSON store for presentation state (pinned views are the first tenant). Reads {} when unset.

Example request

Terminal window
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"]
}
}
PATCH /api/me/preferences

Shallow-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

Terminal window
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.