Curl
The raw 402 dance in bash. Best for one-off scripts, CI pipelines, sanity
checks, or languages without a tools402 SDK. Pair curl (HTTP layer) with
cast send from Foundry (on-chain layer)
for EVM chains, or with solana-cli for Solana.
#Base
bash
# 1 · ask the endpoint, get a 402 quote
curl -i -X POST https://api.tools402.dev/v1/pdf-md \
-F "file=@receipt.pdf"
# → HTTP/1.1 402 Payment Required
# → { "accepts": [{"scheme":"exact","network":"base","payTo":"0xD6E8…2878",
# "asset":"0x833589…2913","maxAmountRequired":"10000",…}], … }
# 2 · pay 0.010 USDC on Base
cast send 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
"transfer(address,uint256)" \
0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878 10000 \
--rpc-url https://mainnet.base.org
# → tx hash : 0xabc123…
# 3 · base64url-encode the receipt header
X_PAYMENT=$(echo -n '{"x402Version":1,"scheme":"exact","network":"base","payload":{"tx_hash":"0xabc123…"}}' \
| base64 | tr '+/' '-_' | tr -d '=')
# 4 · retry with X-Payment receipt
curl -X POST https://api.tools402.dev/v1/pdf-md \
-H "X-Payment: $X_PAYMENT" \
-F "file=@receipt.pdf"
# → 200 OK · { "markdown": "# Receipt …" }#Polygon
bash
# Same 4-step flow, swap USDC contract + RPC at step 2
cast send 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 \
"transfer(address,uint256)" \
0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878 10000 \
--rpc-url https://polygon-rpc.com
# Then build X-Payment with network: "polygon" and the resulting tx hash.#Solana
bash
# Solana uses scheme "spl-transfer" — the buyer partial-signs the SPL token
# transfer and the facilitator broadcasts. Pure-curl on Solana is doable but
# verbose ; use @solana/web3.js (Node) or solders (Python) instead.
# Minimal partial-sign flow with @solana/web3.js :
node -e '
import { Connection, PublicKey, Keypair, Transaction } from "@solana/web3.js";
import { createTransferInstruction, getAssociatedTokenAddress } from "@solana/spl-token";
import bs58 from "bs58";
const USDC = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const RECIPIENT = new PublicKey("Gt9EC4XYqD9pUmTFAfBy9b3gbGG8eiv3ZNLMLCuyU8w8");
const buyer = Keypair.fromSecretKey(bs58.decode(process.env.SOL_PRIVATE_KEY));
const connection = new Connection("https://api.mainnet-beta.solana.com");
const fromAta = await getAssociatedTokenAddress(USDC, buyer.publicKey);
const toAta = await getAssociatedTokenAddress(USDC, RECIPIENT);
const tx = new Transaction().add(createTransferInstruction(fromAta, toAta, buyer.publicKey, 10000));
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
tx.feePayer = RECIPIENT; // facilitator pays
tx.partialSign(buyer);
console.log(Buffer.from(tx.serialize({ requireAllSignatures: false })).toString("base64"));
'For Solana, full details : /chains/solana.
#What X-Payment looks like
The X-Payment header is a base64url-encoded JSON of the receipt :
json
{
"x402Version": 1,
"scheme": "exact",
"network": "base",
"payload": {
"tx_hash": "0xabc123…"
}
}Same shape on Polygon (just "network": "polygon"). Solana uses :
json
{
"x402Version": 1,
"scheme": "spl-transfer",
"network": "solana",
"payload": {
"signature": "5DkH…",
"from": "...",
"to": "Gt9EC4…U8w8",
"amount": "10000",
"mint": "EPjFW…Dt1v"
}
}#Pitfalls
- Forget to confirm before retry : if you retry step 3 before the tx is
confirmed on-chain, you get
402 Payment Requiredagain. Wait one block (cast tx <hash>shows confirmations). - Wrong base64 encoding :
base64defaults to standard alphabet (+/=); tools402 wants base64url (-_, no =). Usetr '+/' '-_' | tr -d '='. - Re-use a tx hash : second call with the same tx hash returns
409 Conflict. One on-chain transfer = one endpoint call.
#Full SDKs
- TypeScript / Node / Bun : /buy-side/typescript
- Python : /buy-side/python
- Wire protocol deep dive : /buy-side/the-402-dance