Token Claims Reference
Complete reference for AuthPI JWT claims: access, ID, and refresh token payloads, organization claims, aud precedence rules, and TTL configuration.
Last updated 2026-07-19
AuthPI issues access tokens, ID tokens, and refresh tokens as signed JWTs. All are verifiable against the issuer’s JWKS endpoint ({AUTHPI_ISSUER_URL}/jwks.json). The JWT header contains alg (ES256, RS256, or EdDSA, per the client’s response_signature_alg) and kid; access tokens additionally declare typ: "at+jwt" (RFC 9068), so resource servers can reject non-access tokens by header alone. Refresh and ID tokens carry no typ. This page lists every claim AuthPI sets in each token type.
Access token claims
| Claim | Type | Description |
|---|---|---|
iss | string | Issuer URL: https://idp.authpi.com/{issuer_id} (issuer IDs use the i_ prefix). |
sub | string | Subject. The user ID (usr_...) for user tokens; the client ID (c_...) or agent ID (agt_...) for client_credentials tokens. |
aud | string or string[] | Audience. A single selected resource is a string; several selected resources are an array. See aud precedence below. |
exp | number | Expiration time (Unix seconds): iat + the configured TTL. |
iat | number | Issued-at time (Unix seconds). |
auth_time | number | Set to the token’s own iat at issuance—including access tokens minted on refresh—so it reflects issuance time, not the original login. Omitted from refresh tokens. For the user’s actual authentication time, use the ID token’s auth_time. |
jti | string | Unique token ID (18-character random string). Always present. Used for refresh-token rotation and replay detection. |
sid | string | Session ID (s_...). Present on user tokens; absent on client_credentials tokens, which have no session. |
client_id | string | The client that requested the token (RFC 9068). |
dat | object | Data attestation: { "type": "identity" | "agent" | "key" | "token" }. identity for user tokens, agent for agent client_credentials tokens. Use it as a typed guard alongside the sub prefix. |
scope | string | Space-separated granted scopes, preserved across refreshes. A refresh request may narrow (never widen) the scope via the optional scope parameter (RFC 6749 §6); the rotated refresh token always retains the full grant. Defaults to "openid" when no scopes were set; omitted on agent tokens that carry no scopes. |
organizations | array | The user’s active org memberships after selected-org and client restriction filtering, as [{ "id": "org_...", "title": null, "scopes": [...], "joined_at": 1767312000 }]. Present (possibly []) on user access tokens; omitted from refresh and client_credentials tokens. |
org_id | string | Present only when the authorization request selected one organization with org=org_.... Marks that the token set is restricted to that organization. |
nonce | string | Echo of the client-supplied nonce, when one was sent. |
ID token claims
ID tokens are issued when the openid scope is granted, alongside the access token (and again on refresh). Standard claims are gated on the granted scopes per OIDC Core §5.4: the email claims require the email scope, and the profile claims require the profile scope. Within a granted scope, a claim is present only when the corresponding profile data exists.
| Claim | Type | Description |
|---|---|---|
iss | string | Issuer URL, identical to the access token’s iss. |
sub | string | The user ID (usr_...). |
aud | string | Always the client_id (OIDC Core §2). Never a custom resource audience. |
exp | number | Expiration (Unix seconds). The ID token lifetime is the client’s default_id_token_age, falling back to its access-token age. |
iat | number | Issued-at time (Unix seconds). |
auth_time | number | Time of the user’s authentication. Set by the authorization-code flow; absent on ID tokens issued via refresh. |
dat | object | Always { "type": "identity" }. |
email | string | The user’s primary email address. Requires the email scope. |
email_verified | boolean | Verification status of the primary email. Requires the email scope. |
name | string | Display name. Requires the profile scope. |
given_name | string | First name. Requires the profile scope. |
family_name | string | Last name. Requires the profile scope. |
picture | string | Photo URL. Requires the profile scope. |
country | string | Country from the user’s address, when set. Requires the profile scope. |
organizations | array | Same array shape as access tokens and /userinfo: [{ "id": "org_...", "title": null, "scopes": [...], "joined_at": 1767312000 }]. AuthPI extension—not scope-gated. |
org_id | string | Present only when the authorization request selected one organization with org=org_.... |
updated_at | number | Last profile update (Unix seconds). Requires the profile scope. |
nonce | string | Echo of the client-supplied nonce, when one was sent. |
at_hash | string | Access-token hash, binding this ID token to its access token. |
acr | string | Authentication context class reference (e.g. urn:authpi:acr:password). Added by the authorization-code flow. |
amr | array | Authentication method references—the method(s) used to sign in (e.g. ["password"], ["sso"]). Added by the authorization-code flow. |
Organization claim filtering
Access tokens, ID tokens, and /userinfo responses use the same organizations array shape. Refresh tokens do not carry full organization memberships.
The array is computed at issuance from the user’s current active memberships, then filtered by:
- The optional
org=org_...authorization parameter, which narrows the token set to one organization. - The client’s
settings.restrictions.organizationspolicy:all,none, orallowlist. - Active status at both levels: the membership and the organization. Memberships of suspended organizations are excluded from newly issued tokens.
See Multi-org tokens for the propagation contract.
Refresh token claims
Refresh tokens are JWTs produced by the same generator as access tokens, with these differences:
audis always theclient_id, regardless of any requested resource audience.auth_timeis omitted.organizationsis omitted. Refresh re-reads current memberships instead of copying old organization claims.org_idis present only when the original authorization selected an organization; refresh keeps that same restriction.scopealways carries the full original grant — it is the grant of record. Narrowing a refresh request (RFC 6749 §6) narrows the issued access token only; the rotated refresh token keeps the complete grant, so a later refresh can return to the full scope set.- The refresh-token family retains the full resource set authorized by the original authorization request as an immutable ceiling. Code exchange and each refresh choose a current access-token subset inside that ceiling. Under an optional policy, omitting
resourceon refresh retains the most recently selected subset; a required policy rejects omission. A later refresh may choose a different subset of the original ceiling but can never add a resource outside it. - The default lifetime is 7 days (
default_refresh_token_age). - Rotation: every
refresh_tokengrant returns a new refresh token and invalidates the old one’sjti. Reusing a rotated refresh token marks the session compromised and revokes all of its tokens.
Treat refresh tokens as opaque credentials—their claims are an implementation detail of the rotation machinery, not an API for your application.
Audience (aud) precedence
The access-token aud is resolved in this order: selected RFC 8707 resource set → legacy audience → client_id → issuer ID. The issuer-ID fallback only applies when no client is involved; in practice all flows supply a client_id. resource and audience are mutually exclusive.
| Token | aud value |
|---|---|
| Access token (authorization code) | The resource set selected at code exchange: a string for one resource or an array for several. The selection must be a subset of the authorization request. If the code exchange omits resource, it uses the full authorized set. Without RFC 8707 resources, the legacy audience or client_id applies. |
| Access token (refresh grant) | The requested subset of the original authorization ceiling. Under an optional policy, omission retains the session’s most recently selected set; a required policy rejects omission. Without RFC 8707 resources, the original legacy audience or client_id is preserved. |
| Access token (client_credentials) | The resource parameter, if sent; otherwise the client_id (c_... or agt_...). |
| Refresh token | Always the client_id. |
| ID token | Always the client_id. |
Resource servers should verify that aud contains the exact audience they expect. Do not compare only with a scalar string: JWT libraries represent one audience as a string and several audiences as an array, and array order has no meaning. See Resource Indicators for request binding and policy.
Token lifetimes
TTLs are configured per client under settings.openid (values in seconds):
| Setting | Applies to | Default |
|---|---|---|
default_access_token_age | Access tokens (authorization code, refresh, and M2M client_credentials) | 1800 (30 minutes) |
default_refresh_token_age | Refresh tokens | 604800 (7 days) |
default_id_token_age | ID tokens | Same as the access-token age when unset |
| — | Agent client_credentials tokens (agt_...) | Fixed 300 (5 minutes), not configurable—agents re-mint per cycle so scope changes propagate within one TTL window |
All three configurable lifetimes are capped at 21 days (1814400 seconds); the API rejects any higher value. This ceiling exists because signing keys are retired ~45 days after creation, so no token may outlive its key’s publication—see JWKS & key rotation.
Example: decoded access token
A user in two organizations, with resource=https://api.example.com selected at authorization and code exchange:
{
"iss": "https://idp.authpi.com/i_8fk2mqzr4tw1ab",
"sub": "usr_0bk7qmxw2e9rj4t8vhzn3a5cd",
"aud": "https://api.example.com",
"exp": 1781262000,
"iat": 1781260200,
"auth_time": 1781260200,
"jti": "Qw7Rt2Xk9Lm4Np6Zs1",
"sid": "s_7d3f9a1c5e8b2f4d6a0c9e7b3f5d8a1c",
"client_id": "c_0fj9qkw2tx8mre4hbz7n3vc5a",
"dat": { "type": "identity" },
"scope": "openid profile email",
"organizations": [
{
"id": "org_0gw3hcq8r2kfn7xj9tzm4be5a",
"title": "Founder",
"scopes": ["owner", "billing:write"],
"joined_at": 1767312000
},
{
"id": "org_0hk2tqvw8m3rfe9pjx5zcn4ba",
"title": null,
"scopes": ["member", "projects:read"],
"joined_at": 1773100800
}
]
}
Example: decoded ID token
The same user and session—note the client_id audience and the same organizations array shape:
{
"iss": "https://idp.authpi.com/i_8fk2mqzr4tw1ab",
"sub": "usr_0bk7qmxw2e9rj4t8vhzn3a5cd",
"aud": "c_0fj9qkw2tx8mre4hbz7n3vc5a",
"exp": 1781262000,
"iat": 1781260200,
"auth_time": 1781260185,
"dat": { "type": "identity" },
"email": "jane@acme.example",
"email_verified": true,
"name": "Jane Doe",
"given_name": "Jane",
"family_name": "Doe",
"picture": "https://cdn.acme.example/avatars/jane.png",
"country": "FR",
"updated_at": 1780531200,
"nonce": "n-0S6_WzA2Mj",
"at_hash": "qYrCZTjHTTjjLMZS6Wf5xg",
"acr": "urn:authpi:acr:password",
"amr": ["password"],
"organizations": [
{
"id": "org_0gw3hcq8r2kfn7xj9tzm4be5a",
"title": "Founder",
"scopes": ["owner", "billing:write"],
"joined_at": 1767312000
},
{
"id": "org_0hk2tqvw8m3rfe9pjx5zcn4ba",
"title": null,
"scopes": ["member", "projects:read"],
"joined_at": 1773100800
}
]
}
Next steps
- Multi-org tokens — default multi-org tokens, selected-org tokens, and the propagation contract
- OIDC & OAuth 2.0 compliance — grants, PKCE, introspection, revocation, and discovery
- Clients — registered-client configuration, TTLs, and signature algorithms
- TypeScript backend quickstart — verify these tokens in your API