Pay any endpoint from your agent — 3 chains, no account, no SDK.
The complete buyer guide for tools402: HTTP 402 quotes in USDC on Base, Polygon, or Solana. Copy-paste snippets only — no npm package, no MCP install, no account. Pick a payment scheme, sign locally, retry with X-Payment or X-Session-Token.
The 402 flow
Every paid call follows the same wire dance — regardless of chain or scheme.
402 Payment Required with accepts[] — one entry per chain + scheme. Pick the one matching your wallet.exact (you broadcast + pay gas), or spl-transfer (Solana gasless).X-Payment (per-call) or X-Session-Token (session mode).200 OK + JSON response. Payment verified on-chain before compute runs.HTTP/1.1 402 Payment Required {"maxAmountRequired": "10000", "payTo": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878"}
Pick your mode
A 402 quote advertises multiple accepts[] entries. Each pairs a chain with a scheme — pick the one your agent wallet supports.
| Scheme | Chains | What you do | Best for |
|---|---|---|---|
transfer-with-authorization |
Base · Polygon (EVM) | Sign EIP-3009 once → reuse as X-Session-Token. Gasless per call — facilitator broadcasts. |
Many calls — sign 1×, reuse until cap or expiry. |
exact |
Base · Polygon · Solana | You broadcast a USDC transfer yourself, send txHash in X-Payment per call. You pay gas. |
No pre-signing — one tx per call, full control. |
spl-transfer |
Solana | Partial-sign a TransferChecked tx (do not broadcast). Facilitator co-signs fee payer + broadcasts. | Solana gasless — no SOL needed in buyer wallet. |
Base / Polygon — session token recommended · copy-paste
Most snippets below use one header, X-Session-Token: sign an EIP-3009 spend authorization once, then call any endpoint gasless until it expires. Generate it locally — your private key never leaves your machine, no CLI, no package to trust. Always send funds to the payTo returned by the live 402 quote (shown here for Base).
Two payment modes appear in a 402 quote. transfer-with-authorization (this session-token flow) — you pre-sign once, tools402 pulls gaslessly per call; best for agents making many calls. exact (below) — you broadcast a USDC transfer yourself and send its txHash per call; no pre-signing, but you pay gas each time. Solana adds spl-transfer (gasless, facilitator broadcasts).
// npm i viem — sign once, reuse the token until it expires import { privateKeyToAccount } from "viem/accounts"; import { randomBytes } from "node:crypto"; const account = privateKeyToAccount(process.env.PRIVATE_KEY); const recipient = "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878"; // Base payTo (always trust the 402 quote) const value = 1_000_000n; // 1 USDC ceiling (atomic, 6 decimals) const validBefore = BigInt(Math.floor(Date.now() / 1000) + 30 * 86400); const nonce = `0x${randomBytes(32).toString("hex")}`; const signature = await account.signTypedData({ domain: { name: "USD Coin", version: "2", chainId: 8453, verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" }, types: { TransferWithAuthorization: [ { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" }, { name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" } ] }, primaryType: "TransferWithAuthorization", message: { from: account.address, to: recipient, value, validAfter: 0n, validBefore, nonce }, }); const token = Buffer.from(JSON.stringify({ chain: "base", from: account.address, to: recipient, value: `0x${value.toString(16)}`, validAfter: "0x0", validBefore: `0x${validBefore.toString(16)}`, nonce, signature, })).toString("base64url"); console.log(token); // → export TOOLS402_SESSION_TOKEN=<token>
Base / Polygon — exact broadcast yourself
Per-call mode: you broadcast the USDC transfer and prove it with the on-chain txHash. Proven on Polygon — tx 0xb7efb1468136b05633a132de1c6ac77014437114ede64a9dd5542d84a041e465. Swap base for polygon and the matching viem chain import.
// EVM `exact` — broadcast a USDC transfer yourself, prove it with the txHash. import { createWalletClient, http, parseAbi } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base } from "viem/chains"; // or `polygon` const ENDPOINT = "https://api.tools402.dev/v1/base64-encode"; const body = JSON.stringify({ data: "hello" }); // 1 · quote const q = await fetch(ENDPOINT, { method:"POST", headers:{ "Content-Type":"application/json" }, body }); const quote = (await q.json()).accepts.find(a => a.network === "base" && a.scheme === "exact"); // 2 · pay (you pay gas on this chain) const account = privateKeyToAccount(process.env.PRIVATE_KEY); const wallet = createWalletClient({ account, chain: base, transport: http() }); const txHash = await wallet.writeContract({ address: quote.asset, // USDC on the chain abi: parseAbi(["function transfer(address,uint256) returns (bool)"]), functionName: "transfer", args: [quote.payTo, BigInt(quote.maxAmountRequired)], }); // 3 · retry with X-Payment carrying the txHash const xPayment = Buffer.from(JSON.stringify({ x402Version: 1, scheme: "exact", network: "base", payload: { txHash, from: account.address, to: quote.payTo, amount: quote.maxAmountRequired, asset: quote.asset }, })).toString("base64url"); const r = await fetch(ENDPOINT, { method:"POST", headers:{ "Content-Type":"application/json", "X-Payment": xPayment }, body }); console.log(await r.json()); // → 200, your result
Solana — spl-transfer gasless
Solana buyer-side scheme: partial-sign a TransferChecked transaction, send it in X-Payment. The facilitator stack pays gas and broadcasts.
// npm install @solana/web3.js @solana/spl-token // Scheme "spl-transfer" — buyer partial-signs; facilitator is fee payer (gasless). // 1 · ask for 402 — pick accept where network === "solana" from the body const r402 = await fetch("https://api.tools402.dev/v1/pdf-md", { method: "POST", body: formData, }); // → 402 · accepts[0].scheme === "spl-transfer" // → extra.feePayer === "facilitator" · payTo: Gt9EC4XYqD9pUmTFAfBy9b3gbGG8eiv3ZNLMLCuyU8w8 // → mint EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v (Circle USDC) // 2 · build TransferChecked tx, partial-sign (buyer only — do NOT broadcast) const serializedTx = tx.serialize({ requireAllSignatures: false }) .toString("base64"); // 3 · retry with X-Payment (facilitator co-signs fee payer + broadcasts) const xPayment = Buffer.from(JSON.stringify({ x402Version: 1, scheme: "spl-transfer", network: "solana", payload: { serializedTx, signature: buyerSig, from: buyerPubkey, to: quote.payTo, mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", amount: quote.maxAmountRequired, }, })).toString("base64url"); await fetch("https://api.tools402.dev/v1/pdf-md", { method: "POST", headers: { "X-Payment": xPayment }, body: formData, }); // → 200 OK · facilitator stack (PayAI → Kobaru → local-key-solana) pays gas
Errors
An invalid payment returns 402 Payment Required again — with an explicit error field telling you what failed. Fix the payload and retry with a valid payment.
// Session token expired or malformed { "error": "invalid session token" } // X-Payment header not valid base64url JSON { "error": "X-Payment: invalid base64url JSON" } // Payload schema mismatch (wrong fields, bad txHash, etc.) { "error": "payload: Invalid input" }
MCP transport adapter
The one buyer-side package exception — @tools402/mcp wraps the same HTTP 402 flow as a Model Context Protocol server for Claude Code and other MCP hosts.
MCP · Claude Code @tools402/mcp@0.2.2
Two steps to add tools402 as a Model Context Protocol server on Base, Polygon, or Solana. Full setup, chain matrix, revoke guide, and copy-paste examples live on the dedicated page — /docs/mcp.
# 1 · generate <token> with the Session token snippet above (#session-token · copy-paste, no CLI) # 2 · add MCP server (Claude Code) claude mcp add tools402 \ --command "bunx @tools402/mcp@0.2.2" \ --env TOOLS402_SESSION_TOKEN=<token> \ --env TOOLS402_CHAIN=base # → 159 tools · see https://tools402.dev/docs/mcp