Set up
Approvals
When a policy holds a purchase for a person, how they approve or deny it, how the agent finds out, and how to make sure a model can never approve its own spend.
When an approval: rule matches, the purchase stops before any money moves. The agent gets APPROVAL_REQUIRED with an approvalId, and the gateway records an approval request:
{
"id": "apr_7c1e…",
"workspaceId": "wsp_…",
"agentId": "invoice-bot",
"walletId": "wal_91c2…",
"capabilityId": "cap_7f3a…",
"amountMinor": "1200",
"currency": "USD",
"rule": "price > 5",
"status": "pending",
"createdAt": "…"
}The flow
The agent is refused
Nothing is charged. With the SDK, invoke throws ZanoraPolicyRejection with code: "APPROVAL_REQUIRED" and approvalId. With MCP, the tool result says the same in plain language.
A person decides
In the console: Buyer → Approvals shows pending requests with the amount, capability, wallet and the rule that held it. Click approve or deny. With the API:
curl -s "https://api.zanora.dev/v1/approvals?status=pending" -H "x-api-key: $WKEY"
curl -s -X POST https://api.zanora.dev/v1/approvals/apr_7c1e…/approve -H "x-api-key: $WKEY" \
-H 'content-type: application/json' -d '{"resolvedBy":"dana@northwind.example"}'The agent retries once
An approval covers one purchase by that wallet of that capability, at up to the approved amount. It's used up by the next matching purchase, and a third call is held again. The agent learns the decision with zanora_approval_status (MCP), or by polling GET /v1/approvals.
Approving inside an MCP client
If the client supports MCP elicitation, @zanora/mcp asks the user directly at the moment of the purchase, and retries once if they approve:
Approve a $12.00 USD purchase?
Buying Invoice OCR
Capability cap_7f3a…
Wallet wal_91c2…
Held by price > 5
[ Approve — spend $12.00 USD ] [ Deny — do not buy this ]- The prompt is built from the gateway's approval record, never from the tool call. A model that assembled the purchase can't write the text the person approves.
- Deny returns
APPROVAL_DENIED, and the agent is told not to retry or ask again. - Dismissing the prompt leaves the request pending. Silence never counts as approval.
- Clients without elicitation are never prompted. They get the approval id and stop.
Keeping approval with people
Recording an approval is an authenticated call, so it only succeeds if the agent's key has the approvals:write scope. A workspace root key has it. Two ways to stop a model approving its own spend:
| Setting | Effect |
|---|---|
Give the agent a key without approvals:write | the gateway refuses to record the answer (APPROVAL_NOT_PERMITTED). Enforced by the platform. Recommended. |
ZANORA_APPROVAL_ELICITATION=off | the MCP server never shows the prompt. Enforced only by that process's configuration |
# An agent key that can buy and read, but not approve
curl -s -X POST https://api.zanora.dev/v1/auth/keys -H "x-api-key: $ROOT_KEY" \
-H 'content-type: application/json' \
-d '{"label":"invoice-bot","scopes":["discovery:read","capabilities:read","wallets:read","receipts:read","approvals:read","ratings:write","rail:read"]}'