Appearance
Users, roles and keys
Users, roles and their mappings, API keys and everyone's sessions.
GET /v1/keys
List API keys.
Each key's lastUsed is when it last signed a request in, written at most once a minute (absent for never); rotatedFrom names the key a rotation replaced.
Responses
| Status | Description | Body |
|---|---|---|
200 | Keys, without secrets, each with its own rateLimits when it has any | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
POST /v1/keys
Create an API key.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | |
kind | key, agent | Default "key". | |
onBehalfOf | string | Agent keys only. The user the agent acts for; its access is capped by theirs. | |
assignments | array of object | ||
assignments[].role | string | ||
assignments[].environments | array of string | ||
expiresAt | string (date-time) | When the key stops working. Omit for a key that doesn't expire. | |
rateLimits | RateLimits |
Responses
| Status | Description | Body |
|---|---|---|
201 | Key shown once; the record has createdAt, expiresAt and rateLimits | |
400 | rateLimits names a class that doesn't exist, or a negative rate | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
PATCH /v1/keys/{id}
Change an API key's own rate limits.
rateLimits replaces the key's own limits; {} goes back to limits.rate for every class. They apply from the key's next request, and only to that key: an agent key's limits don't change those of the user it acts for. Needs user.manage; audited as key.rate-limits <id>.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
rateLimits | RateLimits | yes |
Responses
| Status | Description | Body |
|---|---|---|
200 | The key's record, without its secret | |
400 | No rateLimits, a class that doesn't exist, or a negative rate | |
404 | No such key | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
DELETE /v1/keys/{id}
Revoke an API key.
The key stops working at once. Needs user.manage.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
204 | Revoked | |
404 | No such key | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
GET /v1/users
Users (user.manage).
password says whether the user has one. identities are the OIDC identities that sign them in ({provider, issuer, subject, email, linkedAt}); invites bind one at the user's first sign-in through a provider ({provider, email}). mapped are the assignments the provider's role rules gave at their last sign-in: they add to assignments, and only the IdP changes them. lastSignIn is when the user last signed in, by any method (null for never).
Users come by name, a page at a time: next, when there is one, is the cursor of the page after.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
q | query | string | Only users whose name, or an identity's or invite's email, contains this, ignoring case | |
cursor | query | string | The next a previous page answered | |
limit | query | integer |
Responses
| Status | Description | Body |
|---|---|---|
200 | {users: [{username, assignments, disabled, mustChangePassword, password, identities, invites, mapped, mfa, lastSignIn}], next?} | |
400 | A limit out of range, or a cursor this list didn't give | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
POST /v1/users
Create a user (user.manage).
Give a password, or an invite instead: the user's first sign-in through that provider, with that email (verified, or the provider has trustEmail), binds their identity. An invited user without a password signs in only through providers.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
username | string | yes | |
password | string | ||
assignments | array of object | ||
assignments[].role | string | ||
assignments[].environments | array of string | ||
mustChangePassword | boolean | The user chooses their own password at the first sign-in. Default true. | |
invite | object | ||
invite.provider | string | yes | A configured provider's id. |
invite.email | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
201 | The user | |
400 | A bad username or password, an unknown role or provider, or neither a password nor an invite | |
409 | The username is taken, or another user has the same invite | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
GET /v1/users/{name}
One user (user.manage).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
200 | The user | |
404 | No such user | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
PATCH /v1/users/{name}
Change a user's roles, disable or enable them, set a password, or change their invites (user.manage).
Fields left out don't change. A password set here must be changed at the user's next sign-in. Disabling a user or setting their password ends their sessions; a disabled user's agent keys have no access. The last enabled user who can manage users can't be disabled or lose that permission (409). invites replace the user's invites; an invite from a provider the user already has an identity from, or one another user has, is refused (409). mapped roles can't be changed here.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
assignments | array of object | ||
assignments[].role | string | ||
assignments[].environments | array of string | ||
disabled | boolean | ||
password | string | ||
invites | array of object | ||
invites[].provider | string | yes | |
invites[].email | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
200 | The user | |
404 | No such user | |
409 | The last user who can manage users, or an invite that clashes | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
DELETE /v1/users/{name}
Delete a user (user.manage).
Ends their sessions. Refused (409) for the last user who can manage users, and while agent keys act for the user: revoke those first.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
204 | Deleted | |
404 | No such user | |
409 | The last user who can manage users, or agent keys act for them | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
DELETE /v1/users/{name}/identities/{provider}
Unlink a user's identity from a provider (user.manage).
The user may be left with no way to sign in; an invite or a password gives them one.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | yes | |
provider | path | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
204 | Unlinked | |
404 | No such user, or no identity from this provider | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
GET /v1/users/{name}/sessions
A user's sessions.
Needs user.manage. {sessions: [Session]}, as GET /v1/me/sessions shows a user their own.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
200 | The sessions | object |
403 | The caller lacks user.manage | |
404 | No such user | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
200 body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
sessions | array of Session |
DELETE /v1/users/{name}/sessions
End all of a user's sessions.
Needs user.manage. Ends every session of the user's, on every node; each is audited as user.sign-out <name> (session <id>) (by <admin>). Answers {ended}.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
200 | ||
403 | The caller lacks user.manage | |
404 | No such user | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
DELETE /v1/users/{name}/sessions/{id}
End one of a user's sessions.
Needs user.manage. Audited as user.sign-out <name> (session <id>) (by <admin>).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | yes | |
id | path | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
204 | Ended | |
403 | The caller lacks user.manage | |
404 | No such user, or no session with this id | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
DELETE /v1/users/{name}/mfa
Reset a user's MFA.
Needs user.manage. Removes every factor and recovery code of a user who lost theirs, and ends their sessions; the audit entry is user.mfa-reset <name>. Under auth.mfa admins or required, the user enrols again at their next sign-in. For admin.username while reportd is stopped, use reportd mfa reset <user>.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
204 | Reset | |
403 | The caller lacks user.manage | |
404 | No such user, or the user has no MFA | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
PUT /v1/roles/{name}
Change a custom role's permissions.
Needs user.manage. permissions replaces the role's; whoever holds it has the new ones from their next request. Built-in roles don't change. A change that would leave no enabled user with user.manage, or make designers past the licence's seats, is refused. Audited as role.change <name>.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
permissions | array of string | yes |
Responses
| Status | Description | Body |
|---|---|---|
200 | ||
400 | built-in-role, or an unknown permission | |
403 | The caller lacks user.manage | |
404 | No such role | |
409 | No one would be left who can manage users, or licence-limit (designer seats) | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
DELETE /v1/roles/{name}
Delete a custom role.
Needs user.manage. Only a custom role that no user or key holds, granted or mapped from an IdP, can be deleted; built-in roles stay. Audited as role.delete <name>.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | yes |
Responses
| Status | Description | Body |
|---|---|---|
204 | Deleted | |
400 | built-in-role | |
403 | The caller lacks user.manage | |
404 | No such role | |
409 | role-in-use | object |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
GET /v1/roles/mappings
Each SSO provider's role rules.
Needs user.manage. Each OIDC provider's claim and SAML provider's attribute, the rules that map its values to roles, and the default for a user no rule matches, as auth.oidc and auth.saml configure them. They are shown, not changed, here: the roles they gave each user are the users' mapped.
Responses
| Status | Description | Body |
|---|---|---|
200 | The providers' rules | object |
403 | The caller lacks user.manage | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
200 body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
providers | array of object | ||
providers[].id | string | ||
providers[].name | string | ||
providers[].kind | oidc, saml | ||
providers[].claim | string | OIDC only. | |
providers[].attribute | string | SAML only. | |
providers[].rules | array of object | ||
providers[].rules[].value | string | ||
providers[].rules[].role | string | ||
providers[].rules[].environments | array of string | ||
providers[].default | array of object | ||
providers[].default[].role | string | ||
providers[].default[].environments | array of string |
POST /v1/keys/{id}/rotate
Rotate an API key.
Needs user.manage. Issues the key a new secret, shown once, as a new key with the same name, kind, user, roles, rate limits and expiry; its rotatedFrom names the old one. The old secret keeps working for overlapDays (7 by default; 0 stops it at once), or until its own expiry if that is sooner. Audited as key.rotate <old id> <new id>.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Request body, optional (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
overlapDays | integer | Default 7. At least 0. At most 90. |
Responses
| Status | Description | Body |
|---|---|---|
201 | {key, record, previous}: the new secret, the new key's record and the old key's, without secrets | |
400 | overlapDays out of range | |
403 | The caller lacks user.manage | |
404 | No such key | |
409 | The key has expired | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
GET /v1/sessions
Every user's sessions.
Needs user.manage. Every user's live sessions, or one user's, the most recently used first, each with its username. Sessions are named by id, never by token; current marks the caller's own. End one with DELETE /v1/users/{name}/sessions/{id}.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
user | query | string | Only this user's |
Responses
| Status | Description | Body |
|---|---|---|
200 | The sessions | object |
403 | The caller lacks user.manage | |
404 | No such user | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
200 body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
sessions | array of any |
GET /v1/roles
Built-in and custom roles.
designer holds template.unlock and may change locked content and meta.locked; author may edit drafts but not locked content.
Responses
| Status | Description | Body |
|---|---|---|
200 | Roles | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |
POST /v1/roles
Add a custom role.
Needs user.manage. Audited as role.create <name>.
Responses
| Status | Description | Body |
|---|---|---|
201 | Created | |
409 | licence-limit (designer seats) | |
429 | The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class. | object |