ZanoraBetaDocs

Endpoints

Register, list and switch spending rules on or off, and resolve purchases that a rule held for a person.

Policies

POST/v1/workspaces/:id/budget

Scope policies:write (workspace key; your own workspace only). Sets the daily spending cap, the time zone its day runs in, or both. Body { dailyBudgetMicros?, timeZone? }, with at least one:

  • dailyBudgetMicros: string micro-dollars such as "15000000" ($15.00), or null to clear your own cap (the maximum then applies, if there is one). Zero is refused, and so is anything above the maximum: the 24-hour funding limit ($100 on the hosted platform), reported as maxDailyBudgetMicros, since a workspace can't spend more in a day than it can fund.
  • timeZone: "UTC" or an IANA name such as "Africa/Nairobi", or null for UTC. Abbreviations are refused.

A refused value applies nothing. Returns the budget view below, and takes effect on the next purchase. SDK: client.setDailyBudget(workspaceId, minor | null), client.setBudgetTimeZone(workspaceId, zone | null).

GET/v1/workspaces/:id/budget

Scope policies:read. The budget as the gateway enforces it right now: { workspaceId, dailyBudgetMicros?, ownDailyBudgetMicros?, maxDailyBudgetMicros?, timeZone, day, dayStartsAt, dayEndsAt, spentTodayMicros, remainingMicros? }. dailyBudgetMicros is the cap purchases are held to: your own (ownDailyBudgetMicros) clamped to the maximum (maxDailyBudgetMicros), or the maximum alone when you set none. day is the local date in timeZone; dayStartsAt/dayEndsAt are UTC instants. spentTodayMicros is what counts toward the cap: payments since dayStartsAt, less refunds of those payments. Show this rather than computing "today" yourself. SDK: client.budget(workspaceId).

POST/v1/policies

Scope policies:write (workspace key). Registers a policy document. workspaceId comes from your key.

FieldTypeRequired
documentstring, in the policy languageyes
Terminal
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"}'
# → {"policies":[{"id":"pol_…","policyType":"approval","enabled":true,…}]}

A document with several sections becomes several policies. A line that can't be parsed rejects the whole document with 400.

GET/v1/policies

Scope policies:read. Your workspace's policies, including disabled ones: { policies }.

POST/v1/policies/:id/disable

Scope policies:write. Stops applying a policy from the next purchase. No body. Returns { policy }.

POST/v1/policies/:id/enable

Scope policies:write. Starts applying it again. No body.

Approvals

GET/v1/approvals

Scope approvals:read. Your workspace's approval requests: { approvals }. Filter with ?status=pending (or approved, denied, expired, consumed). Leave it off for the full audit trail.

JSON
{ "approvals": [{ "id": "apr_…", "agentId": "invoice-bot", "walletId": "wal_…", "capabilityId": "cap_…",
  "amountMicros": "12000000", "currency": "USD", "rule": "price > 5", "status": "consumed", "createdAt": "…",
  "resolvedAt": "…", "resolvedBy": "console:acc_…", "consumedAt": "…" }] }
FieldMeaning
statuspending → approved or denied (a person decided) → consumed (the purchase it released went through). approved means granted but not used yet.
resolvedAtwhen the person decided. Using the approval doesn't change it
resolvedBywho decided, as a label: console:<account id> from the console, mcp-elicitation:<agent> from a prompt in the agent's MCP client, or whatever resolvedBy you sent
consumedAtwhen the purchase it released went through. Only on consumed
POST/v1/approvals/:id/approve

Scope approvals:write. Approves the request. Body is optional: { resolvedBy? }. The approval covers one later purchase by that wallet of that capability, at up to the approved amount.

POST/v1/approvals/:id/deny

Scope approvals:write. Denies it. Body is optional: { resolvedBy? }.

Keep approvals:write away from agents

Give agents a key without approvals:write, so a model can never approve its own held spend. See Approvals.