Spending mandates
x402 and AP2 describe how an agent is authorized to spend and how a payment moves. Neither enforces the budget in the request path, and neither hands you a signed proof that spend stayed inside the authorization. A mandate is that missing layer: a signed, bounded authorization plus a constrained API key that the gateway enforces on every call, and a proof you can produce afterward pairing the authorized cap with what was actually spent.
Minting a mandate
POST /v1/mandates (session-authed) mints the authorization and its key together.
curl https://api.buildsable.com/v1/mandates \
-H "Authorization: Bearer $SABLE_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "procurement-agent-aug",
"cap_usd": 25,
"spend_window": "month",
"allowed_models": ["sable", "sable-llama-3.3-70b"],
"expires_in_days": 30
}'cap_usd(required): the spending ceiling this mandate authorizes.spend_window: how the cap accounts,day/week/month(default) /total, matching key spend windows.allowed_models: an allowlist the minted key is bound to.expires_in_days: when the authorization lapses.
{
"mandate_id": "mnd_7c02…",
"key": "sk-sable_…",
"statement": "eyJ2Ijoi…",
"signature": "0x4f8c…",
"signer": "0xA1b2…9F",
"payload": {
"mandate_id": "mnd_7c02…",
"name": "procurement-agent-aug",
"cap_usd": 25,
"spend_window": "month",
"allowed_models": ["sable", "sable-llama-3.3-70b"],
"expires_at": "2026-09-30T00:00:00Z",
"issued_at": "2026-08-31T12:00:00Z"
},
"verify": "POST /v1/receipts/verify { receipt: statement, signature }",
"note": "The key is returned once. The statement is the signed mandate certificate."
}
Two things come back. The key is a real, constrained API key, returned
exactly once: hand it to the agent and it is enforced on every metered call,
capped at cap_usd over spend_window and restricted to allowed_models, the
same enforcement as a scoped key and a
delegated sub-key. The statement is the signed mandate
certificate: proof the authorization exists with these bounds, signed secp256k1 /
EIP-191 and verifiable through the public receipt verify endpoint.
Listing mandates
GET /v1/mandates (session-authed) lists your mandates with live spend against
each cap:
{
"mandates": [
{
"mandate_id": "mnd_7c02…",
"name": "procurement-agent-aug",
"cap_usd": 25,
"spend_window": "month",
"spent_micro_usd": 4180000,
"within": true,
"expires_at": "2026-09-30T00:00:00Z"
}
]
}
spent_micro_usd is actual metered spend under the mandate's key; within is
true while that spend is inside the authorized cap.
Proving compliance
Compliance is two signed facts: the authorization (what was permitted) and the actual spend (what happened). A mandate gives you both, and both verify through the same public endpoint.
POST /v1/mandates/{id}/proof (session-authed) mints a signed proof that pairs the
cap with measured spend:
curl https://api.buildsable.com/v1/mandates/$MANDATE_ID/proof \
-H "Authorization: Bearer $SABLE_SESSION_TOKEN"{
"proof": "eyJ2Ijoi…",
"signature": "0x4f8c…",
"signer": "0xA1b2…9F",
"payload": {
"mandate_id": "mnd_7c02…",
"cap_usd": 25,
"spent_micro_usd": 4180000,
"within_authorization": true,
"period": { "since": "2026-08-01T00:00:00Z", "until": "2026-08-31T12:00:00Z" },
"issued_at": "2026-08-31T12:00:03Z"
},
"verify": "POST /v1/receipts/verify { receipt: proof, signature }"
}
within_authorization is the machine-checkable answer: true when actual spend
stayed inside the authorized cap. Present the mandate certificate (the cap) and
this proof (the actual, with within_authorization) together, and a verifier
confirms both signatures against the pinned deployment signer without trusting
Sable and without an account. This pairs naturally with a
signed spend statement when you want the
by-model breakdown behind the total.
Revoking
DELETE /v1/mandates/{id} (session-authed) revokes the mandate and its key. In-flight
requests on the key stop; already-signed certificates and proofs remain valid as
historical records of what was authorized and spent while it was live.
Where a mandate sits
x402 and AP2 authorize and settle. A mandate is the layer above them that enforces the budget in the path and proves the outcome:
| Layer | What it does |
|---|---|
| x402 / AP2 | Authorize an agent to pay, and move the money |
| Mandate (enforce) | Cap and scope the spend on every request, in-path |
| Mandate (prove) | Sign the cap, sign the actual, pair them for a verifier |
A mandate interoperates with x402 / AP2 in shape: it is a bounded, signed authorization, so it slots alongside them rather than replacing them. An agent funded inline via x402 can run under a mandate's key and the mandate still caps and records its spend.
Wallet-signed authorization (AP2 shape)
By default a mandate is authorized by your SIWE session (itself a wallet
signature). For an AP2-style flow where a specific wallet signs the terms out of
band, pass an authorization_message (a human-readable statement of the terms)
and its authorization_signature (EIP-191) when creating the mandate:
curl https://api.buildsable.com/v1/mandates \
-H "authorization: Bearer $SABLE_SESSION" \
-H 'content-type: application/json' \
-d '{
"name": "trading agent",
"cap_usd": 500,
"spend_window": "day",
"authorization_message": "Sable spending mandate: up to $500/day on inference. 2026-09-01.",
"authorization_signature": "0x..."
}'Sable verifies the signature recovers a wallet linked to your account and records the authorizing wallet plus the signed message on the mandate certificate, so the mandate proves a specific wallet authorized these exact terms. Provide both fields or neither.
Honest scope
A mandate is an enforceable, signed budget authorization on this gateway, not a legal contract. It proves what was authorized and what was spent, and it stops spend at the cap. It does not bind any party outside Sable, and it makes no claim beyond the metered activity it governs. Like every Sable artifact, its certificate and proofs are metadata only: caps, windows, model ids, and costs, never prompt or completion content.