Authentication has two tiers. This guide covers moving between them and keeping a session healthy.
Tenant credentials
Two headers, on exactly five routes — creating, listing, and deleting workspaces, reading quota, and minting a token:
-H "X-Api-Key: $API_KEY" -H "X-Api-Secret: $API_SECRET"Missing or wrong credentials return 401 with code bad_api_key.
Mint a token
curl -X POST "$API_URL/auth/token" \
-H "X-Api-Key: $API_KEY" \
-H "X-Api-Secret: $API_SECRET" \
-H "content-type: application/json" \
-d '{"workspace":"demo","password":"…","scopes":["read","sign"]}'scopes is optional; omitting it grants ["read","write","sign"]. Request the
narrowest set you need — see Scopes and permissions.
A 201 returns the token and its expiry:
{
"token": "eyJhbGciOi...",
"expiresAt": "2026-08-18T12:34:56.000Z",
"workspace": "demo",
"scopes": ["read", "sign"]
}Use it as a bearer token on every other route:
-H "Authorization: Bearer $TOKEN"Refresh before the idle clock runs out
curl -X POST "$API_URL/auth/token/refresh" \
-H "Authorization: Bearer $TOKEN"The body must be empty — sending anything else returns 400 with
invalid_body. The response adds sessionExpiresAt, the absolute ceiling that
refresh cannot move:
{
"token": "eyJhbGciOi...",
"expiresAt": "2026-08-18T12:49:56.000Z",
"sessionExpiresAt": "2026-08-18T20:15:00.000Z",
"workspace": "demo",
"scopes": ["read", "sign"]
}Each refresh returns a new token for the same lease. Replace the one you
hold — but note the previous JWT is not cancelled by the exchange. It keeps
working until its own expiresAt, because refresh extends the lease rather
than rotating it. Treat refresh as buying time, never as containing a leak.
Handle expiry
An expired session returns 401 with session_expired. The recovery is always
the same: mint a new token with the workspace password and retry. A robust
client treats this as routine rather than exceptional.
Note that refresh cannot rescue a session past its absolute ceiling — once
sessionExpiresAt is in the past, only a fresh mint works.
Revoke when finished
curl -X DELETE "$API_URL/auth/token" \
-H "Authorization: Bearer $TOKEN"Returns 204. This revokes the lease of the token you sent, and only that
one. Other tokens minted for the same workspace keep working — the workspace
locks when its last lease goes.
That makes revoke the tool for containing a specific token: send the compromised one with itself. It is not a way to end everything at once.
Failures worth handling
| Code | Status | What happened |
|---|---|---|
bad_api_key |
401 | Tenant credentials missing or wrong |
bad_password |
401 | Workspace password wrong |
workspace_not_found |
404 | No such workspace for this tenant |
session_expired |
401 | Idle or absolute clock elapsed |
scope_denied |
403 | Valid token, but the scope was not granted |
mint_rate_limited |
429 | Too many mint attempts for this tenant |
session_capacity_exceeded |
429 | Too many workspaces open at once |
export_disabled |
403 | Asked for export without an operator-registered key |
Related
- Workspaces and tokens — the model behind this
- Auth endpoints — exact shapes