Mandates
Create, read, and revoke an agent's authority to spend.
A mandate is authority written down as data — the object core concepts describes, managed over the API with an organization key.
Create a mandate
| Field | Type | Required | Meaning |
|---|---|---|---|
principal | string | yes | Who grants the authority: role:procurement_lead, user:jkim |
agent | string | yes | The agent it applies to |
limits.per_transaction | number | yes | Cap per spend |
limits.per_month | number | no | Rolling monthly cap |
limits.currency | string | yes | Currency the limits are stated in |
categories | array | yes | Allowed spend categories; anything else returns deny_category |
review_above | number | no | Above this, the verdict is review and the principal confirms |
expires | string | yes | Date the mandate stops matching; after it, deny_expired |
escrow.required_above | string | no | Force escrow above a threshold, e.g. "1000 USDC" — see escrow |
curl -X POST https://api.saifuro.com/v1/mandates \
-H "Authorization: Bearer sk_live_..." \
-d '{
"principal": "role:procurement_lead",
"agent": "agt_procurement_01",
"limits": { "per_transaction": 500, "per_month": 5000, "currency": "USD" },
"categories": ["saas"],
"review_above": 250,
"expires": "2026-11-18"
}'The response is the mandate as the system holds it — the same shape, plus mandate id and status: "active".
An agent can hold several mandates. A request has to fit inside at least one, and the decision names the mandate it was evaluated against; with no applicable mandate the verdict is deny_no_mandate, because spending without written authority is not possible.
Mandates do not change
There is no update endpoint, deliberately. A mandate that could be edited in place would break the promise that makes the log worth keeping — that a verdict from March can be explained with the rules that applied in March. To change limits, revoke and create a new mandate; both actions are records, and decisions keep pointing at the mandate that was in force when they were made.
Revoke
Takes effect on the next request: the agent's next call under this mandate returns deny_revoked, with no change to the agent's code and no action at any payment provider. The response is the mandate with status: "revoked" and revoked_at set.
Read
Filters: agent, principal, status (active, revoked, expired). Paginated.
The mandate.revoked and mandate.expiring webhooks cover the lifecycle events worth acting on.

