Core concepts
Errors and refusals
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:
{ "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.
| Code | HTTP | Means | Do this |
|---|---|---|---|
POLICY_DENIED | 403 | a workspace rule, the daily budget, or a missing allow rule said no | don't retry. Pick another capability, or change the policy |
APPROVAL_REQUIRED | 403 | a rule needs a person; the response has an approvalId | stop 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 maxPriceMinor. Your client refused before signing | raise your ceiling, or pick something cheaper |
INSUFFICIENT_FUNDS | 402 | the wallet's available balance is below the price | stop, and fund the wallet |
WALLET_FROZEN | 403 | the wallet is frozen | unfreeze it (POST /v1/wallets/:id/unfreeze) |
WALLET_PROVIDER_PAUSED | 429 | three seller failures in a row (5xx, thrown handler, MCP isError or timeout) paused them for this wallet. 4xx responses don't count | wait, or lift the pause |
CHALLENGE_EXPIRED | 410 | the signed price quote expired before payment | call again. The SDKs do this for you |
REPLAY_DETECTED | 409 | this payment proof was already used | nothing: the first use counted. Don't resend proofs |
CANCELLED | — | you aborted before the payment proof was sent | retry freely. Nothing was charged, and the SDK can only promise that for this code |
a failed receipt | 200 | you paid, the seller's handler failed, and you were refunded | the 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
| Code | HTTP | Usually |
|---|---|---|
VALIDATION_FAILED | 400 | an amount sent as a number instead of a string, a missing required field, or an unknown enum value. The message names the field |
UNAUTHORIZED | 401 | a missing, expired, revoked or mistyped key. Run GET /v1/whoami |
FORBIDDEN | 403 | the 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_FOUND | 404 | a wrong id, or one owned by another tenant |
NOT_SUPPORTED | 501 | this deployment has no rail for that request, e.g. card top-ups without a card processor. Ask GET /v1/rail/providers first |
INSECURE_TRANSPORT | 403 | the request reached the gateway over plain HTTP. Use https:// |
RATE_LIMITED | 429 | too many requests for this key. Wait for the retry-after header |
Errors about money moving in or out
| Code | HTTP | Means |
|---|---|---|
DEPOSIT_HELD | 409 | a 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_DOWN | 409 | the payout destination changed less than 24 hours ago. Payouts resume on their own after that |
SANCTIONED_ADDRESS | 403 | sanctions screening blocked the payout destination |
TREASURY_UNAVAILABLE | 503 | a pricing or screening service is down, so the payout is paused rather than guessed. Retry later |
COVERAGE_SHORTFALL | 503 | payouts are paused while the platform reconciles its holdings. Retry later |
DUPLICATE_EXTERNAL_REF | 409 | this deposit was already credited. Duplicates are ignored |