ZanoraBetaDocs

Core concepts

The error envelope, what each error code means, whether any money moved, and what to do next. The page to keep open while you integrate.

The envelope

Every error from the gateway has the same shape:

JSON
{ "error": { "code": "POLICY_DENIED", "message": "denied by rule: price > 20", "details": { "matchedRule": "price > 20" } } }

Handle errors by code. The message is written for a person and can change between releases. An unexpected server error returns only "internal error" and an errorId. Quote the errorId when you ask for help.

Refusals while buying

These are the platform answering a purchase, not faults. In every row except the last, no money moved.

CodeHTTPMeansDo this
POLICY_DENIED403a workspace rule, the daily budget, or a missing allow rule said nodon't retry. Pick another capability, or change the policy
APPROVAL_REQUIRED403a rule needs a person; the response has an approvalIdstop and report the id. A person approves in the console, then retry once
APPROVAL_DENIED—a person refused this spend (MCP tools)don't retry, and don't ask again
PRICE_ABOVE_CLIENT_LIMIT—the price is above your own maxPriceMicros. Your client refused before signingraise your ceiling, or pick something cheaper
INSUFFICIENT_FUNDS402the wallet's available balance is below the pricestop, and fund the wallet
WALLET_FROZEN403the wallet is frozenunfreeze it (POST /v1/wallets/:id/unfreeze)
ACCOUNT_FROZEN403Zanora has frozen the account the wallet or key belongs to. Reads still workdon't retry. Tell your user; the console says why
ACCOUNT_CLOSED403the account was closedstop. A closed account can't be reopened
WALLET_KEY_COOLING_DOWN409the wallet's signing key was replaced less than 24 hours ago. The message says when it can paywait. If nobody on your side replaced it, revoke your API keys and replace it again
WALLET_PROVIDER_PAUSED429three seller failures in a row (5xx, thrown handler, MCP isError or timeout) paused them for this wallet. 4xx responses don't countwait, or lift the pause
CHALLENGE_EXPIRED410the signed price quote expired before paymentcall again. The SDKs do this for you
INVALID_PAYMENT_PROOF400the payment proof was malformed, its signature didn't verify against the wallet's registered key, it didn't match the challenge's terms, or the challenge's nonce was unknown or expiredget a fresh challenge and sign again. If it keeps failing, check that the private key matches the wallet's public key
REPLAY_DETECTED409this payment proof was already usednothing: the first use counted. Don't resend proofs
CANCELLED—you aborted before the payment proof was sentretry freely. Nothing was charged, and the SDK can only promise that for this code
a failed receipt200you paid, the seller's handler failed, and you were refundedthe refund is a new ledger entry. Consider rating the seller

Cancelling after payment

Once the payment proof has been sent, cancelling no longer stops the purchase. The seller is already running, and dropping the connection would only throw away an answer you paid for. The call completes and appears in your purchase history.

Errors while integrating

CodeHTTPUsually
VALIDATION_FAILED400an amount sent as a number instead of a string, a missing required field, or an unknown enum value. The message names the field
MONEY_UNIT_CHANGED400an amount named the way it was before micro-dollars (priceMinor, amountMinor, …, which were cents). details.use names the …Micros field to send. Upgrade the Zanora package that sent it
UNAUTHORIZED401a missing, expired, revoked or mistyped key. Run GET /v1/whoami
FORBIDDEN403the key is missing a scope (the message names it), or the record isn't yours
NOT_FOUND, CAPABILITY_NOT_FOUND, PROVIDER_NOT_FOUND, WALLET_NOT_FOUND404a wrong id, or one owned by another tenant
NOT_SUPPORTED501this deployment has no rail for that request, e.g. card top-ups without a card processor, or a preview rail (ach, bank_transfer) it hasn't turned on. Ask GET /v1/rail/providers first. Also returned when you try to buy a package capability (payable: false), which can't be paid for yet; nothing was charged
INSECURE_TRANSPORT403the request reached the gateway over plain HTTP. Use https://
RATE_LIMITED429too many requests for this key, or too many failed authentications from your address (the message says which). Wait for the retry-after header; for the second, fix the key first

Paying with a generic x402 client

What a seller who accepts any x402 client answers a buyer paying in USDC on Base. The middleware sends these; a seller's own code never has to.

CodeHTTPMeansDo this
X402_PAYMENT_INVALID402the facilitator refused the payment: a bad signature, the wrong amount or payee, an expired or reused authorization, or not enough USDC. Nothing movedsign a fresh payment against the new offer
X402_SETTLEMENT_FAILED402the payment verified, but the facilitator refused to settle it after the call ran. The answer was withheld and nothing was chargedsign a fresh payment against the new offer
X402_SETTLEMENT_PENDING503the payment was submitted on-chain and isn't confirmed yet, so the answer was withheld. It may still settledon't sign a new payment for this call: that could pay twice. No new offer comes with it
X402_NOT_ACCEPTED403this seller doesn't take x402 paymentspay with a Zanora wallet

Errors about money moving in or out

CodeHTTPMeans
DEPOSIT_HELD409a USDC deposit arrived while USDC was outside ±0.5% of $1. It's held, not credited at a guessed rate, and is retried automatically
DESTINATION_COOLING_DOWN409the payout destination changed less than 24 hours ago. Payouts resume on their own after that
SANCTIONED_ADDRESS403sanctions screening blocked the payout destination
TREASURY_UNAVAILABLE503a pricing or screening service is down, so the payout is paused rather than guessed. Retry later
COVERAGE_SHORTFALL503payouts are paused while the platform reconciles its holdings. Retry later
DUPLICATE_EXTERNAL_REF409this deposit was already credited. Duplicates are ignored
LIMIT_EXCEEDED429a money limit said no: the largest single top-up or payout, the amount per 24 hours, or how many top-ups you can start per hour. The message names the limit and, for a 24-hour cap, how much is left
PAYOUT_HELD409the payout asks for earnings held because a buyer who paid them is disputing their card payment. They're released when the dispute is decided in the platform's favour
FUNDS_CLEARING409the payout asks for earnings that are still in the clearing period. They become available on their own