Provenance

Delegation mandates

A mandate lets a human grant an agent a bounded standing authority, such as "you may wire up to $500/day to these counterparties until Friday", instead of approving every single action. The agent then authorizes actions under the mandate, and each verify enforces the mandate's limits on top of your account policy.

Mandates are an Authorize Growth+ feature; a Developer account can grant a monthly Developer allowance of mandates (listing, revoking and heartbeat check-ins are always free). Trust primitives (JWKS, offline verify, revocation) are never paywalled.

The shape of a mandate

A mandate is a signed token (typ: "mandate+jwt", ES256, published in your JWKS) plus a server-side row that tracks spend. Its constraints:

| Constraint | Meaning | |---|---| | max_amount | Per-action cap. An action over this fails mandate_constraint_violated. | | budget_amount + budget_window | Cumulative cap per day | week | month (UTC buckets). | | allowed_action_types | Empty ⇒ any. Matched against context.action_type at verify. | | allowed_counterparties | null ⇒ any. Matched against context.counterparty at verify. | | expires_at | The mandate's hard expiry (also the token exp). |

Grant → authorize → verify

# 1. A human grants the mandate (intent scope; Growth+ or a Developer allowance). Optionally confirm
#    with Face ID / Touch ID (biometric_challenge + biometric_assertion).
curl -s "$API/v1/mandates" -H "Authorization: Bearer $ACCT" -H 'content-type: application/json' -d '{
  "device_id": "'$DEVICE'",
  "constraints": {
    "budget_amount": 500, "budget_window": "day",
    "allowed_action_types": ["wire"], "allowed_counterparties": ["acct-4021"],
    "expires_at": "2026-07-10T00:00:00Z"
  }
}'
# → { mandate_id, mandate_token, remaining_budget: 500, … }   (store mandate_token)

# 2. The agent authorizes an action UNDER the mandate.
curl -s "$API/v1/intent/authorize" -H "Authorization: Bearer $ACCT" -H 'content-type: application/json' -d '{
  "action_description": "wire $300 to acct-4021",
  "agent_session_id": "sess-42", "device_id": "'$DEVICE'",
  "mandate_token": "'$MANDATE_TOKEN'"
}'
# → { intent_token, … }   (the token is bound to mandate_id)

# 3. Verify — enforces constraints, then policy, then debits budget.
curl -s "$API/v1/intent/verify" -H "Authorization: Bearer $ACCT" -H 'content-type: application/json' -d '{
  "intent_token": "'$INTENT_TOKEN'", "action_description": "wire $300 to acct-4021",
  "context": { "transaction_amount": 300, "action_type": "wire", "counterparty": "acct-4021" }
}'
# → { valid: true }   (remaining_budget now 200)

In the SDK: prov.mandates.grant(...), then prov.intent.authorize({ …, mandateToken }), then prov.intent.verify(...). List/inspect/revoke with prov.mandates.list(), prov.mandates.get(id), prov.mandates.revoke(id).

What verify enforces, and in what order

  1. Signature / expiry / action-hash / payload binding — the normal token checks. A mutated payload still fails payload_hash_mismatch first.
  2. Mandate status — revoked or missing ⇒ mandate_revoked; past expiry ⇒ mandate_expired.
  3. Constraints — max_amount / allowed_action_types / allowed_counterparties ⇒ mandate_constraint_violated (with mandate_detail). Checked before policy.
  4. Account policy — the mandate never bypasses your policy. A policy deny returns the policy reason and spends no budget.
  5. Budget — only after a policy pass, transaction_amount is atomically debited against the window's budget_amount. Over the limit ⇒ mandate_budget_exceeded (with budget_spent / budget_limit) and no state change.

The debit is atomic in both stores (in-memory: a single-writer read-check-write; Postgres: SELECT … FOR UPDATE in a transaction), so concurrent verifies cannot double-spend — exactly one of N simultaneous verifies can consume the last of a window's budget.

Revocation

POST /v1/mandates/{id}/revoke flips the mandate to revoked and writes a revocation-table row (scope mandate) — the same kill-switch machinery as token/session/device revocation. Any token minted under the mandate then fails verify with mandate_revoked. Revoke is idempotent.

Offline verification & remaining budget

A relying party can verify a mandate-bound token offline against your JWKS: the signature proves authenticity and the token carries the static constraints and mandate_id. But remaining budget is online-only — it lives server-side and is debited at verify. An offline verifier sees "this mandate permits wires up to $500/day"; it cannot know how much is left today. For spend-sensitive actions, call /v1/intent/verify (which debits) or read prov.mandates.get(id).remainingBudget. We document this rather than pretend the budget travels in the token.

Notes