Authorization mandates
An authorization mandate is a scoped, action-capped, revocable grant that lets an AI agent perform non-payment actions on a principal’s behalf — read a profile, book or reschedule an appointment, send a message on-behalf, invoke a specific tool, or access a data category. A principal (an account holder, contact, or end-user) issues the mandate once; an agent — one of your own, or an external agent arriving over A2A or MCP — then acts against it, and either party can revoke it. It is the general-purpose sibling of the payment-scoped commerce mandate, which only ever authorizes a spend. A mandate here decides whether an action is allowed; the action itself still runs through your own rails.How it works
Every mandate carries a consent digest — a SHA-256 commitment over its immutable scope (the principal, the agent, the allowlists, the invocation cap, the expiry, and — for a delegated mandate — its parent pointer). Any later tampering with the scope changes the digest, so the runtime gate (and any auditor) can prove the mandate presented at act-time is the exact one the principal consented to. A mandate is a serializable snapshot. You own storage: the API returns the mandate object, you persist it, and you round-trip it verbatim in each subsequent request body. Handlers never trust a client-supplied mandate blindly — every action re-verifies the consent digest before authorizing. The scope fields are fixed at issue time and bound by the digest. Onlystatus, invocationCount, updatedAt, and revokedAt advance over a mandate’s life.
The mandate object
An empty allowlist means “any” — omit
allowedActions to authorize any action, or pass a list to restrict to exactly those verbs. Allowlists are stored trimmed, sorted, and de-duped.
Roles and scopes
Issuing, committing, delegating, and revoking are write operations — they require theagents:write scope and an owner, admin, or developer role, and they are audit-logged. The dry-run checks (/authorize, /verify, /authorize-chain, /audit-chain) never mutate anything and are available to any authenticated caller with agent read access.
Endpoints
See the full request/response schemas in the API reference.
Issue a mandate
Grant an agent a capped set of actions on a principal’s behalf.data is the full mandate snapshot, including its consentDigest. Store it — you will send it back on every subsequent call. maxInvocations must be a positive integer, and expiresAt (when set) must be a future epoch-ms timestamp; otherwise the call returns 422 VALIDATION_ERROR.
Authorize an action (dry-run) vs. commit it
/authorize evaluates an action against a mandate and returns the decision without advancing the invocation count — call it from your dispatch layer before performing an action. /act authorizes and commits, returning the advanced mandate (its invocationCount incremented, flipped to exhausted once the cap is reached).
action.reference is your correlation id — the A2A task id, MCP call id, or tool-invocation id this authorization covers. It is echoed back on the decision for the audit trail.
An action passes only when the mandate is intact and active, the action verb is non-empty and within allowedActions, any tool / dataCategory is within its allowlist, and the invocation cap has headroom. When you call /authorize on an action that fails, you get 200 with authorized: false and a deny reason. When you call /act on an action that fails, the mandate is left untouched and the call returns 422 VALIDATION_ERROR carrying the deny reason — you can never act past consent by ignoring a decision.
Deny reasons
authorized is true only when reason is ok. Otherwise:
The chain endpoints add
empty_chain, broken_ancestry_link, scope_not_attenuated, and ancestor_revoked / ancestor_expired / ancestor_exhausted — see Delegation.
Verify a mandate
Recompute the consent digest and report whether the scope is intact — tamper-evidence for an auditor, without committing anything.data.intact is false when any scoped field was changed after issue.
Revoke a mandate
Withdraw consent. The mandate moves to the terminalrevoked status and authorizes nothing further — and, because revocation cascades down a delegation chain, neither does anything delegated from it. Revoking an already-revoked mandate returns 422 VALIDATION_ERROR.
Delegation: sub-agent chains
When an orchestrator agent hands work to another agent — an “agent-as-tool” call, or a hand-off over A2A — the sub-agent needs authority of its own. Delegation mints a child mandate from a live parent whose scope is always attenuated: its allowlists are a subset of the parent’s, and its invocation cap and expiry are each no broader than the parent’s. A child can never end up with more authority than it was given. The child carries aparentMandateId and a parentDigest — a pointer to the exact parent scope it descends from, baked into the child’s own consent digest so the ancestry link cannot be forged or repointed.
Delegate a sub-mandate
allowedActions / allowedTools / allowedDataCategories / maxInvocations / expiresAt to inherit the parent’s unchanged. When you do pass them, they can only narrow: the effective invocation cap is the minimum of your request and the parent’s, and the effective expiry is the earlier of the two. Requesting an allowlist that shares nothing with a restricted parent — which would broaden the child — returns 422 VALIDATION_ERROR. Delegation is also refused from a parent that is revoked, expired, exhausted, or fails its own integrity check.
Authorize and act across a chain
To authorize a delegated action, present the full chain, ordered root-first and leaf-last ([root, ..., leaf]). The gate walks the entire ancestry back to the principal’s root mandate — re-verifying every hop’s integrity, terminal status, expiry, ancestry link, and attenuation — and only then evaluates the leaf’s own scope. A revoked, expired, or exhausted mandate anywhere in the lineage denies the action.
/authorize-chain is the dry-run — it returns 200 with the decision and never advances the leaf. /act-chain commits the leaf action, returning the advanced leaf mandate; when the chain doesn’t hold it returns 422 VALIDATION_ERROR with the deny reason. A well-formed chain that simply isn’t authorized comes back from /authorize-chain as 200 with authorized: false and an ancestor_*, broken_ancestry_link, or scope_not_attenuated reason.
Audit a chain
Reconstruct and verify the full principal → orchestrator → sub-agent → … lineage for review. Read-only.valid, reason, failedAt, rootPrincipalId) plus a per-hop breakdown: each hop’s depth, identity, parentMandateId, status, whether its integrity is integrityIntact, its allowlists, and its invocation state. This is how an auditor proves a sub-agent’s action traces back to authority the principal actually granted.
Error handling
All four dry-run endpoints (/authorize, /verify, /authorize-chain, /audit-chain) return 200 for a well-formed request even when the verdict is a denial — inspect authorized / intact / valid in the body. The committing and mutating endpoints (/, /act, /revoke, /delegate, /act-chain) return 422 VALIDATION_ERROR when the action or mandate is not authorized, and the error message carries the deny reason. A malformed request body is always 422 VALIDATION_ERROR on every endpoint.
See also
- Agents API — full request/response schemas for every mandate endpoint.
- A2A federation — task remote agents across peers; delegation is how you carry a principal’s consent into an A2A hop.
- Human-in-the-loop oversight — approval gates for high-risk agent actions.