Roles
Roles connect workflows to people: an escalation targets a role, and users who hold that role see it in their queue. This page covers role assignment on users, role configuration under /api/roles, and escalation chains.
Roles are implicit: a role exists the moment you reference it. Role assignment endpoints are nested under /api/users/:id/roles. All endpoints require authentication.
Role types
Section titled “Role types”Every role assignment has a type that controls management permissions.
| Type | Permissions |
|---|---|
member |
Work the role’s task queue at the breadth set by its work-surface scope (see below) |
admin |
Everything a member can do on the whole queue, plus manage users within this role |
superadmin |
Full access: manage all roles, all users, system configuration |
A user can hold multiple roles with different types. For example, a user might be a member of reviewer and an admin of senior-reviewer.
Work-surface scope
Section titled “Work-surface scope”A role is a task queue with four verbs: search, claim, ack (resolve) and delete (cancel). A member carries two independent scope axes that set the breadth of those verbs. admin and superadmin ignore scope and always act on the whole queue.
| Axis | Values | Governs |
|---|---|---|
read_scope |
self, all |
search: which escalations the member sees |
write_scope |
none, self, all |
claim / ack / delete: which escalations the member may act on |
self means escalations assigned to the member (assigned_to = user); all means the whole role queue. The only constraint is write ⊆ read (you cannot act on what you cannot see), so write_scope=all requires read_scope=all. Both default to all, which is the full-queue worker.
The five member profiles:
| read_scope | write_scope | Profile |
|---|---|---|
all |
all |
Full worker: search and act on the whole queue (default) |
all |
self |
See the whole queue, act only on own items (for example, a chat-style room) |
self |
self |
Own items only: a one-time user filling in their pre-assigned form |
all |
none |
Read-only observer or auditor of the queue |
self |
none |
Read-only view of one’s own items |
Releasing and escalating are queue-management verbs (they move an item out of the member’s hands) and require write_scope=all. Creating a standalone escalation also requires write_scope=all. A member with write_scope=self may only claim, ack or delete items already assigned to them.
This makes one-time and limited-surface users first-class. A workflow can assign an escalation to a named person (pre-claim) and provision them as read_self + write_self. They see and act on exactly that one item: the form the workflow routed to them.
Special roles
Section titled “Special roles”Three role names have fixed types and elevated permissions:
| Role | Fixed type | Dashboard | Escalation scope | User management |
|---|---|---|---|---|
superadmin |
superadmin |
Full (builder + admin) | All roles; role filter ignored | Create other superadmins. Manage any user, any role |
engineer |
admin |
Full (builder) | Engineer role only | Assign users to engineer role |
admin |
admin |
Escalations + user management | All roles; role filter ignored | Assign users to any non-special role. Bulk actions on all escalations |
All other role names are dynamic. A dynamic role can have type member (work the queue at its read and write scope) or admin (whole-queue access plus managing users within that role and bulk actions).
Dashboard access
Section titled “Dashboard access”The dashboard adapts to the authenticated user’s access tier:
| Tier | Condition | Sees |
|---|---|---|
| Builder | superadmin or engineer role |
Full dashboard: workflows, pipelines, MCP, design, storage, admin, all home page sections |
| Operations | Any role with admin type |
Home page (escalations only), escalation pages, user and role management (scoped to their roles) |
| Operator | Any role with member type |
Home page (escalations only), escalation pages, scoped to the member’s read scope |
Bulk escalation actions (bulk claim, assign, triage, escalate) require admin or superadmin type. Plain member users work on escalations one at a time. A member whose read scope is self lands directly on their own item in user mode; the queue list and aggregate stats reflect only read_all memberships.
API access by tier
Section titled “API access by tier”Certain API endpoints require builder access (superadmin or engineer role):
| Endpoint | Access |
|---|---|
POST/PUT/PATCH/DELETE /api/users |
Builder |
POST /api/users/:id/roles |
Admin (scoped, see below) |
DELETE /api/users/:id/roles/:role |
Admin (scoped, see below) |
All /api/bot-accounts |
Builder |
All /api/roles mutations |
Admin access or the engineer role |
All /api/controlplane |
Builder |
Scoped role assignment
Section titled “Scoped role assignment”When assigning roles via POST /api/users/:id/roles, the caller’s own roles determine what they can assign:
| Caller | Can assign |
|---|---|
superadmin |
Any role, any type (including superadmin/superadmin) |
engineer |
Any role up to admin type (never superadmin type) |
*/admin (non-builder) |
member or admin type for roles they themselves hold |
Removing a role follows the same rule for the role at the type the account holds it. Only a superadmin may change, remove roles from, or mint keys for an account that holds a superadmin grant; others receive 403.
A caller who may assign a role may set any work-surface scope (read_scope, write_scope) on it. Scope refines a member grant; admin and superadmin always act on the whole queue.
List roles for a user
Section titled “List roles for a user”GET /api/users/:id/rolesReturns every role the user holds, with its type.
Path parameters
| Parameter | Description |
|---|---|
id |
User UUID |
Example request
curl https://lt.acme.dev/api/users/b2c3d4e5-.../roles \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "roles": [ { "user_id": "b2c3d4e5-...", "role": "reviewer", "type": "member", "created_at": "2025-01-10T08:00:00.000Z" }, { "user_id": "b2c3d4e5-...", "role": "senior-reviewer", "type": "admin", "created_at": "2025-01-10T08:00:00.000Z" } ]}Add role to user
Section titled “Add role to user”POST /api/users/:id/rolesGrants a role to a user at a type and, for a member, a work-surface scope. A user holds each role at most once (the primary key is (user_id, role)), so adding a role the user already holds returns an error.
Scope: read_scope and write_scope set the member’s breadth on the role’s queue; write_scope=all requires read_scope=all.
Auth: Admin access, following the scoped role assignment rules.
Path parameters
| Parameter | Description |
|---|---|
id |
User UUID |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
role |
string |
yes | Role name (for example, reviewer) |
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 (a one-time user who works only their own assigned item)
curl -X POST https://lt.acme.dev/api/users/b2c3d4e5-.../roles \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "role": "customer-triage", "type": "member", "read_scope": "self", "write_scope": "self" }'Response 201
{ "user_id": "b2c3d4e5-...", "role": "customer-triage", "type": "member", "read_scope": "self", "write_scope": "self", "created_at": "2025-01-15T12:00:00.000Z"}Response 400
{ "error": "role and type are required" }{ "error": "type must be superadmin, admin, or member" }{ "error": "write_scope=all requires read_scope=all (cannot act on what you cannot see)" }Remove role from user
Section titled “Remove role from user”DELETE /api/users/:id/roles/:roleRemoves one role from a user.
Auth: Admin access, following the scoped role assignment rules.
Path parameters
| Parameter | Description |
|---|---|
id |
User UUID |
role |
Role name to remove |
Example request
curl -X DELETE https://lt.acme.dev/api/users/b2c3d4e5-.../roles/reviewer \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "removed": true }Response 404
{ "error": "Role not found" }Role management
Section titled “Role management”The endpoints below manage roles as standalone entities under /api/roles. All require authentication. Mutations require a role manager: admin type, superadmin, or the engineer role.
List all roles
Section titled “List all roles”GET /api/rolesReturns all distinct role names known to the system.
Example request
curl https://lt.acme.dev/api/roles \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "roles": ["reviewer", "senior-reviewer", "admin"]}List roles with details
Section titled “List roles with details”GET /api/roles/detailsReturns all roles with their configuration and usage counts.
Example request
curl https://lt.acme.dev/api/roles/details \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "roles": [ { "role": "reviewer", "title": "Document Reviewer", "description": "Reviews flagged documents for compliance", "form_schema": null, "metadata_schema": null, "properties": {}, "ops_visible": true, "parent_role": null, "sla_minutes": 30, "target_per_hour": 20, "worker_count": 4, "priority_threshold_minutes": null, "priority_facet": null, "entity_facet": null, "entity_state_source": "role", "user_count": 5, "chain_count": 2, "workflow_count": 3 } ]}| Field | Type | Description |
|---|---|---|
title |
string | null |
Display name separate from the technical role key |
description |
string | null |
Human-readable summary |
form_schema |
object | null |
JSON Schema for the escalation resolve form. Versioned per role; fields may carry x-lt-bind to map values into the payload. Takes precedence over the deprecated workflow-level resolver_schema |
metadata_schema |
object | null |
JSON Schema declaring the expected shape of lt_escalations.metadata for this role. Drives faceted-query key autocomplete and creation-time validation |
properties |
object |
Free user-owned bag: icons, colors, tags and so on. No reserved keys |
ops_visible |
boolean |
When true, the role appears as a station on the /operations view |
enforce_schema |
boolean |
When true, every resolve surface validates submitted payloads against this role’s form schema and rejects violations with 422 (code: schema_validation). See Schema enforcement |
parent_role |
string | null |
Parent role in the process dependency graph; null for root stations |
sla_minutes |
number | null |
Target resolution time in minutes (capacity setting) |
target_per_hour |
number | null |
Intended throughput: items resolved per hour (capacity setting) |
worker_count |
number | null |
Capacity at this station: staff or machine count (capacity setting) |
priority_threshold_minutes |
number | null |
Max age (minutes) before a pending unclaimed item counts toward priority_count in station metrics; falls back to sla_minutes |
priority_facet |
string | null |
Escalation metadata key holding the age origin as an ISO 8601 UTC timestamp (for example, authorized_at); falls back to created_at |
entity_facet |
string | null |
Escalation metadata key naming the entity that moves through this role (for example, serialNumber, orderId). Roles sharing the key form that entity’s system; the analytics surfaces (distinct-entity counts, per-entity dwell, entity timelines) derive from it. null means the role has no entity notion |
entity_state_source |
'role' | 'subtype' |
How the role names its contribution to the entity’s state space: 'role' (the station itself is the state) or 'subtype' (the role’s subtypes are its states). Default 'role'; only meaningful while entity_facet is set |
user_count |
number |
Number of users assigned this role |
chain_count |
number |
Number of escalation chain entries referencing this role |
workflow_count |
number |
Number of workflow configs that reference this role |
Create a role
Section titled “Create a role”POST /api/rolesCreates a standalone role.
Auth: Role manager (admin type, superadmin, or engineer).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
role |
string |
yes | Role name (lowercase letters, numbers, hyphens, underscores; must start with a letter) |
Example request
curl -X POST https://lt.acme.dev/api/roles \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "role": "senior-reviewer" }'Response 201
{ "role": "senior-reviewer" }Response 400
{ "error": "role is required" }{ "error": "Role must start with a letter and contain only lowercase letters, numbers, hyphens, and underscores" }Delete a role
Section titled “Delete a role”DELETE /api/roles/:roleDeletes a role when nothing references it.
Auth: Role manager (admin type, superadmin, or engineer).
Path parameters
| Parameter | Description |
|---|---|
role |
Role name to delete |
Example request
curl -X DELETE https://lt.acme.dev/api/roles/senior-reviewer \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "deleted": true }Response 409
{ "error": "Cannot delete role" }Update a role
Section titled “Update a role”PATCH /api/roles/:roleUpdates role configuration. Only provided fields change; form_schema, metadata_schema and parent_role accept null to clear them.
When the update changes form_schema or metadata_schema, the new pair is snapshotted into the role’s schema version history and current_schema_version advances. A save with identical schema values leaves the version alone. Pass change_summary to label the snapshot.
Auth: Role manager (admin type, superadmin, or engineer).
Path parameters
| Parameter | Description |
|---|---|
role |
Role key to update |
Request body (all fields optional)
| Field | Type | Description |
|---|---|---|
title |
string | null |
Display name |
description |
string | null |
Short description |
form_schema |
object | null |
JSON Schema for the resolve form |
metadata_schema |
object | null |
JSON Schema for lt_escalations.metadata |
properties |
object | null |
Free user-owned bag |
ops_visible |
boolean |
Include in the /operations view |
enforce_schema |
boolean |
Turn server-side resolver schema validation on or off |
parent_role |
string | null |
Parent in the process dependency graph |
sla_minutes |
number | null |
SLA target in minutes |
target_per_hour |
number | null |
Throughput target (items per hour) |
worker_count |
number | null |
Station capacity |
priority_threshold_minutes |
number | null |
Priority age threshold in minutes; falls back to sla_minutes |
priority_facet |
string | null |
Metadata key for the priority age origin (letters, numbers, underscores); falls back to created_at |
entity_facet |
string | null |
Metadata key naming the entity that moves through this role (letters, numbers, underscores). Roles sharing the key form the entity’s system for the analytics surfaces. null clears |
entity_state_source |
'role' | 'subtype' | null |
How the role names the entity’s states: the station itself ('role') or its subtypes ('subtype'). null resets to the 'role' default |
upstream_roles |
string[] | null |
Replace the set of roles this station draws input from across other Operations sequences (omitted preserves; null or [] clears). Every entry must name an existing role other than this one. Distinct from parent_role, which places the role in its own sequence |
list_schema |
object | null |
Rich formatting (x-lt-* markup) for this role’s escalation list page. Versions independently of form_schema; the list always renders the latest |
default_pins |
array | null |
Pinned-view seeds for members: [{ label, url, badge? }] with dashboard-relative urls. Members promote, hide or reorder them through their own preferences (/api/me/preferences). null clears |
portals |
array | null |
The role’s named portals: [{ key, label, rows, counts? }]. key is a slug unique within the role, rows holds 1 to 4 rows of 1 to 6 pin cells { label, url, badge? }, and counts holds up to 8 tiles { label, url, blurb? } shown above the panels. Each portal renders as one page at /portal/<role>/<key>. Up to 12. null clears |
change_summary |
string |
Label recorded on the schema version snapshot when this update changes a schema field |
Example request (configure a role as a station in the operations view)
curl -X PATCH https://lt.acme.dev/api/roles/reviewer \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "Document Reviewer", "ops_visible": true, "sla_minutes": 30, "target_per_hour": 20, "worker_count": 4 }'Response 200
The updated RoleDetail object (same shape as GET /api/roles/details).
Response 404
{ "error": "Role 'unknown-role' not found" }Get a role’s schema
Section titled “Get a role’s schema”GET /api/roles/:role/schemaFetches the role’s form_schema and metadata_schema pair. Without version, it returns the live (latest) schema with the role’s current version number; with version, it returns that immutable snapshot, the one an escalation pinned through metadata.schema_version (the schemaVersion field of conditional).
Path parameters
| Parameter | Description |
|---|---|
role |
Role name |
Query parameters
| Parameter | Type | Description |
|---|---|---|
version |
integer |
Snapshot version to fetch. Omit for the latest |
Example request
curl "https://lt.acme.dev/api/roles/reviewer/schema?version=3" \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "role": "reviewer", "version": 3, "form_schema": { "type": "object", "properties": { "approved": { "type": "boolean" } } }, "metadata_schema": { "type": "object", "properties": { "order_id": { "type": "string" } } }, "change_summary": "Added lotNumber field", "created_at": "2026-07-01T12:00:00.000Z", "latest_version": 4}Response 404
The role does not exist, or the requested version does not exist for it. A missing version is an error; the endpoint returns exactly the version requested.
List a role’s schema versions
Section titled “List a role’s schema versions”GET /api/roles/:role/schema/versionsLists the role’s schema version history, newest first. Each entry carries presence flags in place of the schemas; fetch a full snapshot with GET /api/roles/:role/schema?version=N.
Path parameters
| Parameter | Description |
|---|---|
role |
Role name |
Example request
curl https://lt.acme.dev/api/roles/reviewer/schema/versions \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "versions": [ { "version": 4, "has_form_schema": true, "has_metadata_schema": true, "change_summary": null, "created_at": "2026-07-02T09:30:00.000Z", "is_current": true }, { "version": 3, "has_form_schema": true, "has_metadata_schema": true, "change_summary": "Added lotNumber field", "created_at": "2026-07-01T12:00:00.000Z", "is_current": false } ]}Escalation chains
Section titled “Escalation chains”Escalation chains define which roles can escalate to which other roles. They live under /api/roles/escalation-chains, and the per-role view is /api/roles/:role/escalation-targets.
List all escalation chains
Section titled “List all escalation chains”GET /api/roles/escalation-chainsReturns all escalation chain pairs.
Example request
curl https://lt.acme.dev/api/roles/escalation-chains \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "chains": [ { "source_role": "reviewer", "target_role": "senior-reviewer" } ]}Add an escalation chain
Section titled “Add an escalation chain”POST /api/roles/escalation-chainsAdds a single escalation chain entry.
Auth: Role manager (admin type, superadmin, or engineer).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
source_role |
string |
yes | Role that can escalate |
target_role |
string |
yes | Role to escalate to |
Example request
curl -X POST https://lt.acme.dev/api/roles/escalation-chains \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "source_role": "reviewer", "target_role": "senior-reviewer" }'Response 201
{ "source_role": "reviewer", "target_role": "senior-reviewer" }Response 400
{ "error": "source_role and target_role are required" }Remove an escalation chain
Section titled “Remove an escalation chain”DELETE /api/roles/escalation-chainsRemoves a single escalation chain entry.
Auth: Role manager (admin type, superadmin, or engineer).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
source_role |
string |
yes | Source role |
target_role |
string |
yes | Target role |
Example request
curl -X DELETE https://lt.acme.dev/api/roles/escalation-chains \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "source_role": "reviewer", "target_role": "senior-reviewer" }'Response 200
{ "removed": true }Response 404
{ "error": "Chain entry not found" }Get escalation targets for a role
Section titled “Get escalation targets for a role”GET /api/roles/:role/escalation-targetsReturns the allowed escalation targets for one role.
Path parameters
| Parameter | Description |
|---|---|
role |
Role name |
Example request
curl https://lt.acme.dev/api/roles/reviewer/escalation-targets \ -H "Authorization: Bearer $LT_TOKEN"Response 200
{ "targets": ["senior-reviewer", "manager"]}Replace escalation targets for a role
Section titled “Replace escalation targets for a role”PUT /api/roles/:role/escalation-targetsReplaces all escalation targets for a role.
Auth: Role manager (admin type, superadmin, or engineer).
Path parameters
| Parameter | Description |
|---|---|
role |
Role name |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
targets |
string[] |
yes | New list of target roles |
Example request
curl -X PUT https://lt.acme.dev/api/roles/reviewer/escalation-targets \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "targets": ["senior-reviewer", "manager"] }'Response 200
{ "role": "reviewer", "targets": ["senior-reviewer", "manager"] }Response 400
{ "error": "targets must be an array of strings" }Roles in workflow configuration
Section titled “Roles in workflow configuration”Roles also appear in workflow configuration. When you register a workflow, default_role sets the escalation target and roles lists every role allowed to claim its escalations (see Create or replace a workflow configuration):
curl -s https://lt.acme.dev/api/workflows/reviewContent/config \ -X PUT \ -H "Authorization: Bearer $LT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"is_lt": true, "default_role": "reviewer", "roles": ["reviewer", "senior-reviewer"]}'When reviewContent escalates, the escalation targets reviewer by default. Users with either reviewer or senior-reviewer can claim it.
See the Workflows API for full configuration details.
