Authentication
Every request carries a bearer token:
Authorization: Bearer <credential>There are two kinds of credential, and the choice is about who is acting, not about convenience.
| User token | API key | |
|---|---|---|
| Represents | a person | a system |
| Reads | everything its role allows | cases, documents and decisions only |
| Looks like | a JWT from your sign-in service | ug_live_… or ug_test_… |
| Comes from | OAuth 2.0 against your deployment’s sign-in service | Admin → Developers in the dashboard |
| Carries | a role | a set of scopes |
| May review a decision | yes | no |
A machine integration usually wants an API key. Use a user token when the caller genuinely is a person — for example, when your product signs someone in and then acts strictly on their behalf.
API keys
Section titled “API keys”A workspace admin mints a key in the dashboard, under Admin → Developers.
- The key is shown once. It cannot be read back afterwards. Put it where your servers can read it and a browser cannot, and mint a new one rather than trying to recover an old one.
ug_live_andug_test_authenticate identically. The tag exists so that a key pasted into the wrong place is recognisable at a glance.- A key may carry an expiry. From that instant it stops authenticating; there is no grace period.
- Give a key the least it needs. A key with no scopes reads the cases, documents and decisions described below and changes nothing. Scopes are not addable later, so a key that needs more is a new key.
What a key can read
Section titled “What a key can read”Any key can read the cases, documents and decisions in this workspace. It can never read your people, your workspace settings, your sign-on setup, your operator-access records, the audit export or your keys — no scope grants any of those, because they are administrative acts of a person and the record has to name one.
Scopes
Section titled “Scopes”Scopes are about what a key may change. Reading the resources above needs no scope at all, which is why a key with none is a read-only key.
| Scope | Lets the key |
|---|---|
scenarios:write |
create cases, attach and finalise documents, start runs |
guidelines:write |
upload and manage guideline sources |
question_sets:write |
author, edit and publish question sets |
document-types:write |
manage the workspace’s document-type vocabulary |
notifications:write |
create and change task-notification subscriptions and signing secrets |
embed:mint |
mint an embed handoff for one case |
An unknown scope is rejected when the key is created, rather than accepted into a key that silently authorises nothing.
What a key can never do
Section titled “What a key can never do”These are acts of a person, and they are refused to an API key at every scope — for reading as well as for writing — because the record has to name a human:
- reviewing a decision, and overriding an answer — the sign-off that makes a proposal final;
- membership — invitations, and the workspace roster;
- the workspace’s own administration — settings, sign-on configuration, operator-access records, the audit export, and the keys themselves.
User tokens
Section titled “User tokens”Get an access token from the sign-in service your deployment is configured against: authorization code with PKCE for a person, client credentials for a machine user. Send it verbatim.
Three rules catch out almost everyone:
-
Send the access token, not the ID token. A token carrying ID-token markers (
at_hash,nonce) is refused. -
Include this scope, exactly. It is a constant — the same literal string for every caller of every deployment, with nothing to substitute in:
urn:zitadel:iam:user:resourceownerIt asks the sign-in service to state which organization your subject belongs to, which is checked against the organization your workspace is bound to. Send it from the first request: it is required once your workspace is bound, and harmless before that.
-
Nothing else in the token is read. Exactly three things reach a decision:
sub, which must resolve to a known user;iss, which must be your deployment’s issuer; and the organization, when the scope above was sent. Extra claims never break a request and never grant anything. No claim selects a workspace or sets a role — both follow fromsub.
A user principal carries one role, and each contains the one below it.
| Role | Can |
|---|---|
viewer |
read everything in the workspace |
reviewer |
everything above, plus write resources, run cases, and review decisions |
admin |
everything above, plus membership, settings, identity configuration and API keys |
Every failure is one 401
Section titled “Every failure is one 401”An expired token, a bad signature, an unknown subject and a wrong organization all return
the same response: 401, urn:ug:error:unauthorized, a WWW-Authenticate: Bearer header,
and an identical body. They are deliberately not told apart, so the API cannot be used to
probe which of them it was.
{ "type": "urn:ug:error:unauthorized", "title": "Missing or invalid credential", "status": 401, "detail": "credential does not correspond to a known user", "instance": "/v1/scenarios", "request_id": "0198c5f2-1c2e-7c9b-9f10-2a3b4c5d6e7f"}The request_id is what support correlates against the log that does say. Keep it.
A 403 means something different and is worth reading as such: the credential is valid and
re-presenting it will not help. Either the principal lacks the role or scope, or it reached
across into another workspace.
If tokens that used to work start failing and nothing else changed, check the scope in rule 2 first. It is the single most common cause.
Keep credentials out of everything but the header
Section titled “Keep credentials out of everything but the header”Never put a token or an API key in a URL, a log line, a bug report or a shared example. A
URL ends up in browser history, in proxy logs and in referrer headers; the Authorization
header does not.
Related
Section titled “Related”- Quickstart — a worked path from a credential to a running underwrite.
- Conventions — errors, pagination, idempotency and rate limits.
- API reference — every route, request and response.