Skip to content

Mint, refresh, and revoke workspace tokens

The three token routes — how a workspace session is opened, extended, and ended, and which credentials each one takes.

Updated View as Markdown

Minting is the only workspace-tier operation authenticated by tenant credentials — it is where the workspace password is presented.

POST /v1/auth/token

Mint a workspace token. Auth: tenant credentials. Returns 201.

Request

{
  "workspace": "demo",
  "password": "a-strong-workspace-password",
  "scopes": ["read", "sign"]
}
Field Type Required Notes
workspace string yes Workspace slug
password string yes At least 1 character
scopes string[] no Defaults to ["read","write","sign"]. 1–4 entries, each ≤32 chars

Only read, write, sign, and export are grantable. An unrecognised name returns 400 with invalid_body — unknown scopes are never silently dropped. A workspace token can hold export, but export itself needs a scoped API key carrying an inquiry key, so a workspace token can never actually reach the export routes.

Response

{
  "token": "eyJhbGciOi...",
  "expiresAt": "2026-08-18T12:34:56.000Z",
  "workspace": "demo",
  "scopes": ["read", "sign"]
}

Errors

Status Code
400 invalid_body, invalid_slug
401 bad_api_key, bad_password
403 permission_denied
404 workspace_not_found
429 mint_rate_limited, session_capacity_exceeded
500 storage_error — the workspace exists in the ledger but could not be opened

POST /v1/auth/token/refresh

Issue a new token and push out the idle clock. Auth: workspace token, any scope. Returns 200.

Request

The body must be empty: no body at all or a literal {}. A body carrying any field returns 400 with invalid_body.

Response

{
  "token": "eyJhbGciOi...",
  "expiresAt": "2026-08-18T12:49:56.000Z",
  "sessionExpiresAt": "2026-08-18T20:15:00.000Z",
  "workspace": "demo",
  "scopes": ["read", "sign"]
}

sessionExpiresAt is the absolute ceiling. Refresh moves expiresAt; it never moves sessionExpiresAt. Once the ceiling passes, mint a new token instead.

Each refresh returns a new token — replace the one you hold. Responses carry cache-control: no-store.

Refreshing does not invalidate the token you sent. It extends the same lease and issues a fresh JWT for it; the older one keeps working until its own expiresAt passes. If a token has leaked, refreshing does nothing to contain it — revoke it.

The new token names the same lease as the one it came from — refresh extends a lease, it never adds one. So revoking either token ends both: a revoke sent with the pre-refresh token kills the refreshed one too. That is the behaviour you want after a leak, and it is why refresh is not a way to rotate away from a compromised token.

Errors

Status Code
400 invalid_body
401 session_expired

DELETE /v1/auth/token

Revoke the lease this token names. Auth: workspace token, any scope. Returns 204 with no body. The workspace locks only once its last lease is gone — see below.

curl -X DELETE "$API_URL/auth/token" -H "Authorization: Bearer $TOKEN"
Navigation

Type to search…

↑↓ navigate↵ selectEsc close