The 402 dance
The "402 dance" is the canonical name for the 3-step exchange between a buyer agent and a tools402 endpoint. It's the same on all three chains; only the on-chain transfer mechanism differs. Below is the EVM version (Base / Polygon). The Solana version differs only in step 2.
#The 3 steps
#1. Ask the endpoint — get a 402 quote
POST /v1/pdf-md HTTP/1.1
Host: api.tools402.dev
Content-Type: multipart/form-data
[your request body]Response :
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"x402Version": 1,
"accepts": [
{
"scheme": "exact",
"network": "base",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
"maxAmountRequired": "10000",
"maxTimeoutSeconds": 60
},
{
"scheme": "exact",
"network": "polygon",
"asset": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"payTo": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
"maxAmountRequired": "10000",
"maxTimeoutSeconds": 60
},
{
"scheme": "spl-transfer",
"network": "solana",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"payTo": "Gt9EC4XYqD9pUmTFAfBy9b3gbGG8eiv3ZNLMLCuyU8w8",
"maxAmountRequired": "10000",
"maxTimeoutSeconds": 60
}
],
"error": "X-PAYMENT header is required"
}Pick one entry in accepts[] — the chain you have USDC on. The other
entries describe the same payment on different chains; pay on one.
#2. Pay USDC on-chain
EVM (Base / Polygon)
Transfer the maxAmountRequired atomic amount in USDC to payTo. The
simplest path is a vanilla ERC-20.transfer() :
cast send 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
"transfer(address,uint256)" \
0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878 10000 \
--rpc-url https://mainnet.base.orgWait for at least one block confirmation before step 3. On Base that's ~1.2 s. On Polygon ~2 s.
For gas-less payments via EIP-3009 transferWithAuthorization, see
/buy-side/typescript.
Solana
The Solana flow is different : you partial-sign an SPL transfer and the facilitator broadcasts. See /chains/solana for the full flow.
#3. Retry with X-Payment receipt
POST /v1/pdf-md HTTP/1.1
Host: api.tools402.dev
Content-Type: multipart/form-data
X-Payment: eyJ4NDAyVi…ifQ
[your request body — same as step 1]The X-Payment header is a base64url-encoded JSON with the tx hash :
{
"x402Version": 1,
"scheme": "exact",
"network": "base",
"payload": { "tx_hash": "0xabc…" }
}The marketplace verifies the tx on-chain (correct to, amount, asset,
not already consumed) and resolves your endpoint call.
Response (200 OK) :
HTTP/1.1 200 OK
Content-Type: application/json
{
"markdown": "# Your receipt …",
"page_count": 1
}#Idempotency
The marketplace deduplicates by tx hash. If your network drops and you
retry step 3 with the same X-Payment, you get the same response. The
endpoint runs once; you don't pay twice for the same retry.
#Replay protection
Tx hashes are recorded in a replay log (SQLite WAL, R2-backed). A second
attempt to use the same tx hash at step 3 — on a different endpoint or
after the first call — returns 409 Conflict. One on-chain transfer
authorises one endpoint call.
#Failure modes
- Quote expired (
> maxTimeoutSecondssince step 1) → re-quote at step 1, pay again at step 2. Your previous USDC is lost (the marketplace doesn't refund off-chain). - Tx not confirmed yet at step 3 → wait one block, retry. The marketplace polls the chain for ~10 s before declaring the tx missing.
- Tx amount mismatch (paid less than
maxAmountRequired) → returns402with the same quote. Pay the correct amount. - Tx already consumed (replay attempt) →
409 Conflict. - Upstream 5xx after settlement (proxy mode) → marketplace retries upstream for up to 60 s. If still 5xx, returns
502with the tx hash inX-Tools402-Txheader. The buyer's payment is not refunded — the tx is auditable on chain.
#Why no off-chain refund
By design. The payment is verified on the request, not on the response. If the upstream compute fails after settlement, the buyer has on-chain proof of payment (the tx hash) and can dispute with the seller directly. The marketplace stays neutral — it cannot freeze either party's funds.
A persistent failure (seller's endpoint consistently returns 5xx) flips
the listing to degraded then suspended. New buyer calls are blocked
until the seller resumes (signs POST /v1/_seller/<wallet>/resume).