Set up
Spending policies and budgets
Rules the gateway applies to every purchase before any money moves — deny, require approval, or allow-list — plus a daily budget for the whole workspace.
A policy is a short text document attached to your workspace. The gateway evaluates it on the server, before the payment is taken, for every purchase by every wallet in the workspace. An agent can't skip it, and a model can't talk its way past it.
The language
deny:
category == "Adult"
provider.reputation < 0.3
approval:
price > 5
allow:
provider.verified == true
currency == "USD"There are three kinds of section, and each line is field comparison value:
| Section | A purchase is caught when… | Result |
|---|---|---|
deny: | any line matches | POLICY_DENIED: refused |
approval: | any line matches | APPROVAL_REQUIRED: held for a person |
allow: | all lines in one allow policy match, for at least one allow policy | if allow policies exist and none matches: POLICY_DENIED |
Fields
| Field | Type | Example |
|---|---|---|
price | number, in dollars | price > 5 |
category | string | category == "OCR" |
currency | string | currency == "USD" |
provider.verified | boolean | provider.verified == true |
provider.reputation | number, 0 to 1 | provider.reputation < 0.3 |
provider.id | string | provider.id == "prv_3652ec9e…" |
capabilityId | string | capabilityId != "cap_…" |
agentId | string | agentId == "experiments" |
walletId | string | walletId == "wal_…" |
Comparisons: == != < <= > >=. Put strings in quotes. # starts a comment. A line that refers to a field the purchase doesn't have never matches.
Evaluation order
- Deny rules. The first matching line refuses the purchase.
- The workspace daily budget. If this purchase would take today's spending over the budget, it's refused.
- Approval rules. The first matching line holds the purchase, unless a person has already approved this exact spend. See Approvals.
- Allow rules. If any exist, at least one must match completely.
- Otherwise the purchase is allowed.
Adding a policy
In the console: Buyer → Policies → Add a rule, choose the section, and type one rule per line.
With the API:
curl -s -X POST https://api.zanora.dev/v1/policies -H "x-api-key: $WKEY" \
-H 'content-type: application/json' \
-d '{"document":"approval:\n price > 5\ndeny:\n provider.verified == false"}'The response lists the parsed policies, each with its own id. A document with several sections becomes several policies.
| Call | Does |
|---|---|
GET /v1/policies | list your workspace's policies, including disabled ones |
POST /v1/policies/:id/disable | stop applying one, from the next purchase |
POST /v1/policies/:id/enable | start applying it again |
Policies can't be edited. To change one, add the corrected version and then disable the old one. That keeps a record of which rules applied to which purchases.
Daily budget
The workspace's dailyBudgetMinor, set at signup, caps total spending across all its wallets per UTC day. When a purchase would exceed it, the purchase is refused with POLICY_DENIED and matchedRule: "workspace.dailyBudget".
Recipes
approval:
price > 5allow:
provider.verified == true
provider.reputation >= 0.6deny:
agentId == "experiments"
price > 0.25allow:
provider.id == "prv_3652ec9e…"The pause after repeated failures isn't a policy
Separately from your rules, if one seller fails three calls in a row for a wallet (a 5xx, a crash, an MCP isError or a timeout, not a 4xx for a bad request), that wallet is paused from buying from that seller (WALLET_PROVIDER_PAUSED). This protects your balance from a broken seller. It lifts on its own, or you can lift it now.