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.
Delegation cap, expiry, or status failures give 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. A quote above maxTotalCents gives 402 BCK.ROUTER.0018 without a charge or budget reserve, although signing may already have occurred; raise that per-call ceiling only if the call is intended.
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 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 released once the reconciler has confirmed the authorization expired unconsumed, not at the moment of failure. Your cap stays reserved until then. 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.
Bound quotes and required services — API 1.55+
A payment-requiredPOST /api/v1/router/quote response carries an opaque quoteId and an
expiresAt 60 seconds later. Pass the id to /route with the exact quoted target, method,
headers, body, credential header and Delegation. The Router pays the sealed merchant challenge at
the exact fee-inclusive amount; maxTotalCents remains an independent ceiling. Invalid or
wrong-account ids (0029), expired ids (0032), and any request, Delegation, rail or exact-total
mismatch (0033) are refused before a charge. Re-quote whenever the call changes or the id expires.
Clients pinned below API 1.55 receive no quoteId/expiresAt and retain the legacy re-probe flow.
The same API version adds exact opaque-slug controls to POST /router/select and MCP
route_by_intent: filters.require, filters.prefer and filters.exclude. require selects that
slug or fails closed with 0031; it never substitutes another service. prefer permits normal
fallback, while exclude removes its slug from consideration.
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.
What the merchant sees of you
/proxy relays a live request, so the headers reaching it are not only the ones you set — every proxy between you and the Router appends its own (X-Forwarded-Host, X-Real-Client-IP, traceparent, …). Those describe our infrastructure, not your request, and a merchant has no business receiving them. So /proxy forwards an allowlist of standard request headers and drops everything else, Cookie, Origin and Referer included. Add what a merchant genuinely needs by stating its value — X-Router-Forward-x-api-key: sk-… sends x-api-key: sk-… upstream. That channel carries a value rather than naming a header to replay, so it can never be used to read back something our own infrastructure set: nothing replayed off the wire is forwarded except the allowlist.
The Router also adds nothing of its own on the way out: no Forwarded or X-Forwarded-For is synthesised, so your IP never reaches the merchant.
POST /route carries no such risk and applies no allowlist — its headers are a JSON object you wrote, so it forwards exactly what you ask for.
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.
Per-endpoint request size
Some services accept smaller request bodies than the Router does. The catalog records that limit asmaxRequestBytes (UTF-8 bytes) on each endpoint of the service detail (GET /api/v1/catalog/services/{slug}) and in the MCP get_service result. Every Locus gateway (*.mpp.paywithlocus.com) publishes 8000: its load balancer refuses a larger body before it ever asks for payment.
When you call a service by slug, the Router checks your body against that limit before contacting the service. A larger body returns 413 BCK.ROUTER.0027 with bodyBytes and maxRequestBytes in params. Nothing was paid, so shrink the body or pick another service. POST /router/select (and the MCP route_by_intent) skips endpoints whose limit is below the body you send, so a long prompt is routed to a service that takes it. An endpoint without maxRequestBytes has no known limit, which is not the same as having none.
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. Codes 0029 and 0031–0033 require API 1.55+.
Across the whole Router surface, only
0006, 0007, 0020 and quote-only 0028 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. To learn the fee-inclusive price before paying, quote the call: a quote charges nothing, but it does reach the service and spends the same rate budget as a payment, so quote once per decision. 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.