Troubleshooting — RFC 8693 errors, one by one

The exchange endpoint answers with standard OAuth error codes. By design the client gets the
coarse code and the audit stream gets the detail
— an attacker probing the endpoint learns
nothing about why; your admin reads the exact reason in
GET /api/iam/v1/audit?stream=delegation (iam.delegation.exchange.refused).

invalid_client

The client assertion failed: wrong signature, expired/replayed jti, unknown client_id, or the
client does not belong to an agent. Check: the agent’s JWKS pasted at approval matches the
private key in use; the assertion aud is the token endpoint URL; clocks are sane.

invalid_requestactor_token

“Delega multi-hop non abilitata (max_delegation_depth=1).”

You sent an actor_token. Depth is 1 in MVP; the parameter is recognized (wire conformance) and
refused cleanly. Drop it — the agent’s identity comes from the client assertion.

invalid_requestrequested_token_type / subject_token_type

Only urn:ietf:params:oauth:token-type:access_token is issuable/accepted today. id_token as
subject is v2.

invalid_requestsubject_token

The parameter is missing. The user’s access token goes in subject_token, the agent’s key signs
the assertion — the two are different things.

invalid_grant

The coarse bucket for every policy refusal — deliberately indistinguishable client-side:

Actual cause (visible in audit) Fix
Agent not active (pending/suspended/retired) Approve/resume the agent
Subject token invalid or expired Re-authenticate the user
Subject token without sid Delegation requires a human session — machine tokens can’t delegate
User’s session no longer alive The user logged out / was revoked: re-login, re-exchange
No delegation grant for (user, agent) The user never consented — send them to the consent flow
Grant expired or revoked Ask for fresh consent (or the revocation was the point)
delegation_budget_unenforceable — grant has a budget, no DelegationBudgetGuard bound Bind a meter (e.g. laravel-ai-finops iam_delegation integration) or grant without a budget. Fail-closed by design: a budget nobody enforces would be a broken promise to the user
delegation_budget_exhausted: <reason> — the bound meter said no The grant’s budget cap is crossed. The user grants fresh consent with a new budget (or an admin reviews the spend) — the cap doing its job is not an error

invalid_scope

“(intersezione vuota tra scope richiesti, grant e max_scopes agente)”

The requested scopes ∩ grant scopes ∩ agent max_scopes is empty. Request fewer/different scopes,
or ask the user to widen the grant (a new consent — dynamic linking makes the wider ask
explicit). Never widen max_scopes casually: that ceiling is the org’s review boundary.

Exchange succeeds but the resource server denies

The exchange checks the agent/grant half; the PDP checks both halves per request. Look at the
delegated decision (the two sub-decision ids in the audit): the user layer probably denies —
the user lacks the permission behind the scope. Scopes are the upper bound, permissions are the
truth.

Error Meaning
consent_unavailable The verifier is Null (unconfigured), or no live IAM session (check session_resolver)
agent_not_active Consent can only target an active agent
scopes_exceed_agent_ceiling Requested scopes outside the agent’s max_scopes
ttl_exceeds_maximum Longer than grants.max_ttl_days
Binding mismatch on store Parameters differ between challenge and store calls — send the exact same four (five, when a budget is included: it is part of the binding too)

Elevation errors (ElevationException / 422 on the elevation endpoints)

Error Meaning
Grant absent or not active Elevation only extends a living grant — revoked/expired grants need fresh consent, not elevation
Scopes already covered The grant already has them: just re-exchange
Scopes outside the agent ceiling max_scopes is structural — no user consent raises it. An admin must widen the ceiling first (that’s an org review, on purpose)
Request no longer decidable Pending expired (elevation.pending_ttl_minutes, default 15) or already decided. Fail-closed: the agent re-requests
Request “not found” for a logged-in user Elevation requests are only visible to the grant’s own user — other users get non-existence, never “not yours”