Skip to main content
402 Payment Required is an HTTP status code reserved since the early web for “you must pay to access this.” For most of its history it sat unused. Pay-per-use APIs and agentic payments have revived it as the standard way for a server to ask a client for payment and for the client to pay and retry — all in-band, over normal HTTP.

A status code reserved for decades

The 402 status code has been part of HTTP since the HTTP/1.1 drafts of the late 1990s (RFC 2068, carried into RFC 2616 and later RFC 7231), where it is described simply as “reserved for future use.” For most of the web’s history there was no interoperable way to attach a payment to a request, so 402 went essentially unused. That changed with agentic and pay-per-use workloads. A status code that lets a server say “pay, then try again” — without redirecting to a checkout page or provisioning an API key — is exactly what an autonomous agent needs. The protocols in this section all use 402 as that in-band signal.

The 402 flow

  1. The client requests a protected resource.
  2. The server responds 402 Payment Required with a payment challenge (amount, asset, recipient, accepted protocols/rails).
  3. The client constructs and signs a payment that satisfies the challenge.
  4. The client retries the request with the payment attached.
  5. The server verifies settlement and returns the resource.
SELAT implements this flow for agents: it discovers the challenge, picks a rail, signs, pays, and returns the final response — see the SELAT Router SDK.

How SELAT implements 402

The selat-pay payer (used under the hood by the SELAT CLI) walks the 402 handshake end to end. The stages below map directly to its implementation.

1. Probe the upstream

selat-pay first sends a single probe request with no Authorization header. From one 402 response it parses both supported protocols:
  • x402 — from a base64 payment-required: body header, or from a www-authenticate: X402 requirements="..." header (used by some services alongside the body header).
  • MPP — from a www-authenticate: Payment ... HTTP-auth challenge.
A second probe — sending Authorization: Payment probe — is fired only when needed: when MPP was asked for (--prefer-mpp) but the first probe didn’t surface it, or as a last resort when the first probe surfaced no payable protocol at all (some dual-protocol services gate the MPP path behind that header). Sending the header blindly breaks services that reject unknown auth, so it is conditional, not default.
If the upstream answers 2xx with no challenge, the call is treated as free and still routed through the SELAT Router so the call is logged — the client never signs anything.

2. Detect and decode the challenge

For x402, each entry in the accepts array carries a scheme, network (an eip155:<id>), asset, payTo, and an amount. For MPP, selat-pay base64-decodes the request parameter into a JSON object carrying amount, currency, recipient, and methodDetails.chainId. Amounts are USDC base units (6 decimals — divide by 1,000,000).

3. Select the outbound protocol

Every paid call is routed through the SELAT Router. What the probe decides is the router’s outbound protocol on the upstream leg — same-rail Gateway passthrough when the upstream is Gateway-capable, cross-protocol translation (carrying the router’s default ~5% markup, included in the live quote) otherwise. The default selection: Flags adjust this: --prefer-mpp forces MPP even for a Gateway-capable upstream, and --prefer-x402 opts out of the MPP default; the two are mutually exclusive. selat-pay targets ${routerUrl}/proxy?target=<encoded upstream>, sends the choice in an x-selat-prefer-protocol header, and expects a 402 carrying both a payment-required challenge and an x-selat-quote-id — the client always signs against the router’s challenge, never the upstream’s.
Par pricing on same-rail passthrough is the operator’s current live behavior, not a protocol guarantee — the per-call 402 quote is authoritative. Earlier selat-pay versions paid Gateway-capable upstreams directly, bypassing the router; that mode was removed so every call is quoted, recorded, and reconciled.

4. Sign the authorization

For the Gateway-batched path, selat-pay builds an EIP-712 / ERC-3009 TransferWithAuthorization in the GatewayWalletBatched domain (via Circle’s @circle-fin/x402-batching BatchEvmScheme). Signing routes through a Circle Agent Wallet (user-controlled MPC) by default; because that wallet is a smart-contract account, selat-pay first resolves its owner EOA so the authorization’s from matches the recovered signer. --raw-key signs locally with SELAT_PRIVATE_KEY for development and bypasses Circle MPC.

5. Retry, settle, and reconcile

The signed payload is base64-encoded into a Payment-Signature header and sent on the retried request (echoing x-selat-quote-id back when routing). The router verifies the payment and settles the upstream via the appropriate rail adapter, then returns the upstream body — see SELAT Router.
Because the signed Payment-Signature leaves the machine before the outcome is known, the router may settle USDC even on a non-2xx upstream. selat-pay therefore records every submitted payment — settled or failed — to a local JSONL ledger (rail, protocol, chain, amount, payTo, HTTP status, outcome) so spend is always reconcilable. Inspect it with selat history.
Paid calls require a --max-amount cap, enforced fail-closed: anything not satisfying price <= cap is rejected before signing.

Payment protocols

A 402 challenge can be satisfied by different payment protocols and settlement rails. SELAT supports:

x402

Agents pay in on EVM via Circle Gateway. The router settles x402 exact on Solana (SPL) outbound. Solana is not a fund chain. You still sign only the Gateway-batched quote.

MPP on Tempo

The MPP protocol, settled on Tempo.

Nanopayments via Circle Gateway

Batched nanopayments backed by a unified USDC balance on Circle Gateway.

Next