Token and claim reference
GenAuth's delegation model uses two kinds of token. Their roles are different and so are their field shapes — confusing the two is the single most common integration mistake:
| Delegate token | Access token (after exchange) | |
|---|---|---|
| What it is | An authorization record: who delegated what to which agent, and for how long | An access credential: what the agent actually presents to a resource |
| Who consumes it | GenAuth (for token exchange and validation) | Your target resource service |
| Where it comes from | Issued after consent completes (see Quickstart) | Exchanged from a delegate token (RFC 8693) |
Has act? | No | Yes (a GenAuth extension — see below) |
Both are JWTs (RFC 7519) and are verified the standard way.
Table 1: complete delegate token fields
A JWT whose token_type is eak_delegation_token. Thirteen top-level fields:
| Field | Type | Meaning | Contract level |
|---|---|---|---|
token_type | string | Always eak_delegation_token | Implementation detail — fine for recognizing the token kind, but the value may change across versions; do not hard-code an equality check as your only test |
iss | string | Issuer | Stable contract |
sub | string | The delegating user's ID — the subject of a delegate token is always a person | Stable contract |
azp | string | The access key ID that started the delegation | Stable contract |
agent_id | string | The agent the authority was delegated to | Stable contract |
scope | string[] | Granted scopes (formatted service.capability:action, e.g. webagent.web_search:manage) | Stable contract |
aud | string | Always genauth:token-exchange — a delegate token is only accepted by the token exchange endpoint | Stable contract |
eak_tenant_id | string | The owning workspace ID | Stable contract |
genauth_userpool_id | string | The user pool the user belongs to | Implementation detail — this field is known to be subject to a rename; resource-side code must not depend on it. Treat the version noted on this page as authoritative |
resource_bindings | object | Product resources bound to the workspace (e.g. { gumem: { project_id }, webagent: { tenant_id } }) | Implementation detail |
jti | string | Unique token ID | Stable contract |
grant_id | string | The grant record ID (spans the authorization lifecycle) | Stable contract |
audit_id | string | Audit chain ID (see Audit and accountability chain) | Stable contract |
A delegate token has no act claim
act only appears in the exchanged access token. If you are looking for act in a delegate token, you are holding the wrong token.
Table 2: extra fields on the exchanged access token
The access token returned by token exchange (POST /api/v3/eak/token-exchange) carries these GenAuth extension fields in addition to the target resource's standard claims:
| Field | Type | Meaning |
|---|---|---|
act | object | The actor: { "type": "eak_delegation", "agent_id": "...", "grant_id": "...", "access_key_id": "..." } |
exchange_id | string | Unique ID for this exchange (enters the audit chain) |
product_resource | object | The bound target resource, shaped { "type": "gumem_project" | "webagent_tenant", "id": "..." }. This is an object, not a string — do not compare it directly against "gumem" |
act is a GenAuth extension, not the RFC 8693 shape
RFC 8693 defines act as a nested sub ("act": { "sub": "..." }). GenAuth's act is an extension object with a type discriminator — resource-side code must explicitly check act.type === "eak_delegation" rather than parsing the standard nested shape. The full resource-side checklist is in Protect your APIs.
The sub of the access token is still the user — "on whose behalf" always points to a person, and "who is acting" is expressed by act. That is where delegation semantics land inside the token.
Migration and the compatibility window
The current @qoniai/qoni v0.4.1 package retains two deprecated shapes for compatibility. New code should use the right-hand form only in silent mode; interactive mode does not pass a user field:
| Old | New |
|---|---|
delegateAgent({ ... }) | delegateToken({ ... }) |
top-level userId: "usr_x" | user: { id: "usr_x" } |
A two-track compatibility window
The server-side API still accepts the top-level userId during the compatibility window. That means: even after your own code has fully migrated to user: { id }, anyone holding your access key can still start a delegation with the old parameter. Treat access key custody and rotation as your first line of defense — threat analysis in Security considerations.
Validating a token
- Online validation (recommended):
POST /api/v3/eak/delegations/introspectwith{ "token": "..." }. A valid token returns{ "active": true, ...all claims }; anything invalid or expired returns{ "active": false }. The SDK equivalent isqoni.genauth.introspectDelegationToken({ token }). - Local verification: after standard JWT signature verification, always check
aud(alwaysgenauth:token-exchangefor a delegate token) and expiry. For access tokens, the resource side must also check the scope intersection (see Protect your APIs).
Next steps
- Reference: API reference
- Guide: Protect your APIs: resource-side integration
- Security: Security considerations