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 the first probe did not surface MPP and there’s reason to look for it (for example, dual-protocol services that 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

HTTP/1.1 402 Payment Required
payment-required: <base64 JSON with an "accepts" array>
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 a rail

SELAT pays a 402 two ways — DIRECT (Gateway-batched, settled via Circle Gateway on a supported chain) and ROUTED (through the SELAT Router, over x402 or MPP). The default selection:
ConditionMode
Upstream advertises a GatewayWalletBatched exact accept on the requested chaindirect (no router markup)
Routed, MPP availablerouted-mpp (MPP wins by default)
Routed, only x402 availablerouted-x402
Free upstreamrouted-free
Flags adjust this: --prefer-mpp forces MPP even over a direct Gateway-batched accept, and --prefer-x402 opts out of the MPP default. When routing, 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. Direct mode skips the router and signs against the upstream’s own challenge.

4. Sign the authorization

For the Gateway-batched path, selat-pay builds an EIP-712 / ERC-3009 TransferWithAuthorization in the GatewayWalletBatched domain (via the SDK’s 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 How the router works.
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 on Base & Solana

Open 402 payment standard, settled in USDC on Base (EVM) and Solana.

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