Complete reference for AuthPI JWT claims: access, ID, and refresh token payloads, organization claims, aud precedence rules, and TTL configuration.
Last updated 2026-06-20
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.
| 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 | Audience. 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 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. |
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:
org=org_... authorization parameter, which narrows the token set to one organization.settings.restrictions.organizations policy: all, none, or allowlist.See Multi-org tokens for the propagation contract.
Refresh tokens are JWTs produced by the same generator as access tokens, with these differences:
aud is always the client_id, regardless of any requested resource audience.auth_time is omitted.organizations is omitted. Refresh re-reads current memberships instead of copying old organization claims.org_id is present only when the original authorization selected an organization; refresh keeps that same restriction.scope always 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.default_refresh_token_age).refresh_token grant returns a new refresh token and invalidates the old one’s jti. 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.
aud) precedenceThe access-token aud is resolved in this order: explicit requested audience → client_id → issuer ID. The issuer-ID fallback only applies when no client is involved; in practice all flows supply a client_id.
| Token | aud value |
|---|---|
| Access token (authorization code) | The audience parameter from /authorize (RFC 8707), if sent; otherwise the client_id. A custom audience must equal the client_id or appear in the client’s allowed_audiences, else the request fails with invalid_request. |
| Access token (refresh grant) | The original session audience is preserved across refreshes; falls back to the client_id. |
| 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 aud against the audience they expect (your API’s identifier if you use RFC 8707, otherwise the client ID).
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.
A user in two organizations, with an RFC 8707 audience requested at /authorize:
{
"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
}
]
}
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
}
]
}