A refusal is the system working. Most Router errors mean a guardrail caught something. Before you widen a cap or drop an idempotency key to make an error go away, read what it was actually protecting you from.
Your budget
The Delegation is the control you own. It carries a hard cap in cents and an expiry, and it’s checked server-side on every single payment.
Any of those failing gives you
402 BCK.ROUTER.0003. Check the current state any time with GET /api/v1/delegation/{id} — remainingBudgetCents and expiresAt tell you which it was.
An agent cannot spend past its cap by looping, by retrying, or by being confidently wrong about its own budget. That’s the point of the mechanism, and it’s why an agent should treat 0003 as a stop signal rather than something to route around.
Your wallet
A cap authorizes a spend; it doesn’t provide funds. Both rails pull from your own custodial wallet, so it has to hold the asset on the target network before you call — and what happens when it doesn’t differs by rail. On MPP-tempo, the Router reads your on-chain balance before signing. If it’s short you get402 BCK.ROUTER.0009, and nothing happens: no credential is minted, no budget is reserved, no partial state exists to clean up.
On MPP-stripe no balance is read at all — the card is attempted, and a decline comes back as that same 402 BCK.ROUTER.0009. The outcome matches (nothing minted, nothing reserved), but it’s a real decline on your card rather than a check that spared you one.
On x402 there is no balance check anywhere. The Router signs and reserves your budget, and the shortfall surfaces only when the transfer is attempted on-chain — the payment lands as Failed and the reserved merchant leg is not given back. Only a routing fee reserved on top of it can be credited back, and only on the closed list of paths that release one. Fund for the merchant’s amount plus any routing fee, which is a second transfer from the same wallet.
On any rail, read the wallet address from the live Delegation (providerPaymentMethodId) rather than from anything you saved earlier. Funding a stale address is the most common cause of an underfunded wallet and the one that takes longest to spot — 0009 deliberately doesn’t echo the address it checked, so it can’t tell you that’s what happened.
Who can be paid
A Delegation may restrict which addresses it will pay. When it carries a recipient list, the merchant’s pay-to address must be on it — checked before the expensive signing step, so a disallowed recipient costs you nothing.Recipient scope is optional, and unset means unrestricted. A Delegation with no recipient list can pay any merchant the Router can reach; its cap and expiry are then the only limits on it. Don’t assume a Delegation is address-bound unless you deliberately made it so. Card-funded Delegations are vendor-agnostic by design and never carry one.
splits — additional payout recipients beyond the primary one — is rejected outright, whether or not your Delegation has a recipient list. Only the primary recipient is ever validated, so honouring splits would move real money to addresses nobody checked. The Router refuses the whole challenge rather than paying the part it can vouch for.
Paying twice
requestId is the idempotency key, and it’s required in mode B — because the Router pays automatically, a retry after a dropped connection must not buy the same thing again.
At most one payment is minted per (caller, requestId). A duplicate returns 409 BCK.ROUTER.0002 carrying the original paymentId, which is usually what you actually wanted.
Use one stable id per logical purchase, not per HTTP attempt:
- Retrying a timed-out call with the same id →
409 BCK.ROUTER.0002with the originalpaymentId, not the resource. Safe — but don’t escape that 409 by minting a fresh id. - Retrying with a fresh id → buys again. Also safe, but only if that’s what you meant.
What the Router won’t fetch
The Router makes server-side HTTP requests to URLs you supply, so it will not let you point it at infrastructure you shouldn’t reach. Targets resolving to loopback, private (RFC 1918), link-local, or cloud-metadata addresses are blocked — both literal IPs and public hostnames that resolve to internal addresses, so DNS rebinding doesn’t get around it. The connection is then pinned to the address that was validated, so it can’t be swapped underneath. Redirects are not followed at all, and thelocation header is stripped from the relayed response — so a merchant can’t bounce the Router toward an internal target, and can’t hand your client one either.
Operators can lift this for local development with ROUTER_ALLOW_PRIVATE_TARGETS=true. It should never be on in a shared environment.
Control headers are also stripped in both directions: your X-Router-* headers aren’t forwarded upstream, and any X-Router-* headers a merchant returns are removed before the response reaches you — so an upstream can’t forge a payment signal that makes a free response look paid.
Relay limits
Mode B relays live traffic, so it’s bounded:
Exceeding the concurrency limit returns
429 BCK.ROUTER.0007, which is retryable — let some calls finish and try again. The idle timer is re-armed by your client draining the response, so a slow-but-healthy large transfer won’t trip it.
Every error code
Every code below also appears in the canonical API error catalogue, which carries thecategory and retryable fields your client can branch on without parsing a message. Error handling describes the envelope they arrive in.
Across the whole Router surface, only
0006 and 0007 are worth retrying automatically. The rest are decisions, and retrying them unchanged will produce the same answer. On the paying path the set is narrower still — see below.
Notes for autonomous agents
If you’re writing an agent that spends without a human in the loop, four rules:- Treat
0003and0009as stop conditions. They mean “out of budget” and “out of money”. Report them; don’t work around them. - Never widen a Delegation in response to a refusal. The cap is the user’s decision, not a runtime obstacle.
- Generate one
requestIdper purchase, and reuse it across retries of that purchase. Deriving it from the work you’re doing beats a fresh UUID per HTTP attempt. - Check the price before you commit.
settlement.approxCentson the response tells you what the merchant charged, andfee.capChargedCentswhat your cap was actually debited — the two differ when a routing fee applies. A run of sub-cent calls still burns a cent of budget each.
Next
Payment ledger
What actually got spent, and how to reconcile it.
How it works
Where these checks sit in the flow.
Full error catalogue
The catalogue entry for each
BCK.ROUTER.* code, with its category and remediation hint.Router API reference
The five endpoints these rules apply to, with request and response shapes you can try.