An API token is either delegated (minted by a person under Account → API tokens, acting on their behalf) or an agent key (the single live key of an agent account, a token-only workspace member). Tokens can never list, mint, revoke, or rotate tokens: every operation on this page answers 403 forbidden to a Bearer request, and /api/me/* is closed to tokens altogether. The token string is shown once, in the response that creates it.
Scopes are read (always implied), write, and triage. Regular client users cannot mint; elevated client users must name at least one project and may not grant triage. Owners and Admins must hold two-factor authentication; promoting a person to Owner or Admin revokes their delegated tokens.
GET /api/me/tokens
The delegated tokens the signed-in person minted, across every workspace they still belong to. Token: no (browser session only).
Response 200 { "tokens": ApiToken[] }
{
"tokens": [
{
"id": "01924c3e-3c40-7d51-8e62-9f7a8b9c0d1e",
"name": "Jira sync",
"prefix": "saq_a1B2c3D4",
"kind": "delegated",
"scopes": ["read", "write"],
"projectIds": ["01924c3e-6b2a-7d11-9d0e-3a1f4b5c6d7e"],
"cidr": "203.0.113.0/24",
"expiresAt": "2027-03-19T00:00:00.000Z",
"lastUsedAt": "2026-09-19T10:15:00.000Z",
"createdAt": "2026-09-01T09:00:00.000Z",
"identity": {
"id": "01924c3e-0004-7000-8000-000000000001",
"email": "kari@acme.example",
"name": "Kari Nordmann"
},
"workspaceSlug": "acme",
"workspaceName": "Acme"
}
]
}
| Field | Meaning |
|---|---|
id, name |
Token id and the name given at minting |
prefix |
The first characters of the token, for recognising it; never the whole token |
kind |
delegated or agent |
scopes[] |
Granted scopes |
projectIds[] |
Projects the token is restricted to; empty means the grantor's full set |
cidr |
IPv4 network the token may be used from, or null |
expiresAt |
Expiry, or null for never |
lastUsedAt |
Last use, updated at most once a minute |
identity |
Whose token it is (id, email, name) |
workspaceSlug, workspaceName |
The workspace (present on this list) |
POST /api/me/tokens
Mint a delegated token. Token: no (browser session only). Needs a full login (password, passkey, or SSO), a re-authentication in the last ten minutes, two-factor authentication for Owners and Admins, and an SSO session when the person's domain forces SSO.
Body field (MintTokenRequest) |
Type | Required | Validation |
|---|---|---|---|
name |
string | yes | 1–80 characters |
workspaceSlug |
string | yes | 3–32 characters; a workspace the person belongs to |
scopes |
(read, write, triage)[] |
yes | At least one |
projectIds |
uuid[] | no | Subset of the person's own projects; required (≥ 1) for elevated client users |
cidr |
string (≤50) or null | no | IPv4 address or network in CIDR notation |
expiresAt |
datetime or null | no | Null is never |
Response 201 (CreatedApiToken): every ApiToken field plus token, the full secret, shown once:
{
"id": "01924c3e-3c40-7d51-8e62-9f7a8b9c0d1e",
"name": "Jira sync",
"prefix": "saq_a1B2c3D4",
"kind": "delegated",
"scopes": ["read", "write"],
"projectIds": [],
"cidr": null,
"expiresAt": null,
"lastUsedAt": null,
"createdAt": "2026-09-19T10:40:00.000Z",
"identity": {
"id": "01924c3e-0004-7000-8000-000000000001",
"email": "kari@acme.example",
"name": "Kari Nordmann"
},
"workspaceSlug": "acme",
"token": "saq_YOUR_TOKEN"
}
| Error | Status | details.reason |
|---|---|---|
reauth_required |
403 | No recent re-authentication |
mfa_required |
403 | Owner or Admin without two-factor authentication |
sso_required |
403 | Forced-SSO domain without an SSO session |
forbidden |
403 | client_token_forbidden (regular client user), client_triage_scope, a project outside the grantor's memberships, agent accounts, or a Bearer request |
read_only |
403 | The workspace is read-only |
not_found |
404 | Not a member of workspaceSlug |
DELETE /api/me/tokens/{id}
Revoke one of the person's own tokens. Token: no (browser session only). Response 200 { "ok": true }; 404 not_found for a token that is not theirs.
GET /api/w/{slug}/tokens
Every token bound to the workspace: live ones and those revoked in the last 30 days. Token: no (browser session only). Owner or Admin.
Response 200 { "tokens": WorkspaceApiToken[] }, each an ApiToken plus revokedAt (datetime or null). Error: forbidden 403.
DELETE /api/w/{slug}/tokens/{id}
Revoke any token in the workspace. Token: no (browser session only). Owner or Admin. Still allowed while the workspace is read-only. Response 200 { "ok": true }. Errors: forbidden 403, not_found 404.
GET /api/w/{slug}/agents
The workspace's agent accounts. Token: no (browser session only). Owner or Admin.
Response 200 { "agents": AgentAccount[] }
| Field | Meaning |
|---|---|
membershipId, identityId |
The agent's membership and identity ids |
email |
<handle>@<slug>; never a mailbox |
name |
Display name |
createdAt |
Creation time |
token |
The live ApiToken, or null when revoked |
POST /api/w/{slug}/agents
Create an agent account and its first key. Token: no (browser session only). Owner or Admin with recent re-authentication. Agents are workspace Members, join every project, and count as workspace users.
Body field (CreateAgentRequest) |
Type | Required | Validation |
|---|---|---|---|
handle |
string | yes | Lower case, ^[a-z0-9][a-z0-9._-]{0,63}$, not ending in a separator; becomes <handle>@acme |
name |
string | yes | 1–120 characters |
scopes |
scope[] | yes | At least one |
cidr |
string (≤50) or null | no | IPv4 CIDR restriction |
curl -X POST https://saq.no/api/w/acme/agents \
-H 'Cookie: <browser session>' \
-H 'Content-Type: application/json' \
-d '{ "handle": "release-bot", "name": "Release bot", "scopes": ["read", "write"] }'
Response 201 (CreatedApiToken) with kind: "agent" and the one-time token. Errors: reauth_required 403, forbidden 403 (admin_only), seat_limit 409, conflict 409 (handle taken).
POST /api/w/{slug}/agents/{identityId}/rotate
Revoke the agent's current key and issue a new one with the same name, scopes, CIDR, expiry, and projects. Token: no (browser session only). Owner or Admin with recent re-authentication. Response 201 (CreatedApiToken). Errors: reauth_required 403, forbidden 403, not_found 404.
GET /api/w/{slug}/mcp
Copy-and-paste MCP client configuration for this workspace, as shown under Settings → Channels → MCP setup. Token: no (browser session only).
Response 200 (McpSetup)
{
"url": "https://saq.no/mcp",
"workspace": "acme",
"claude": "{\n \"mcpServers\": {\n \"saq\": {\n \"type\": \"http\",\n \"url\": \"https://saq.no/mcp\",\n \"headers\": { \"Authorization\": \"Bearer saq_YOUR_TOKEN\" }\n }\n }\n}",
"cursor": "{\n \"mcpServers\": {\n \"saq\": {\n \"url\": \"https://saq.no/mcp\",\n \"headers\": { \"Authorization\": \"Bearer saq_YOUR_TOKEN\" }\n }\n }\n}"
}
claude and cursor are JSON strings ready to paste into Claude Code and Cursor; replace saq_YOUR_TOKEN with a real token. See MCP.