Home/Buy-side
buy-side · for agent builders

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.

protocol

The 402 flow

Every paid call follows the same wire dance — regardless of chain or scheme.

01
Request the endpointPOST your payload with no payment header. Same URL, same body you would use after paying.
POST
02
Receive a 402 quoteHTTP 402 Payment Required with accepts[] — one entry per chain + scheme. Pick the one matching your wallet.
402
03
Pay on your chainSign or broadcast per scheme: session token (EVM gasless), exact (you broadcast + pay gas), or spl-transfer (Solana gasless).
sign / tx
04
Retry with payment headerSame request again with X-Payment (per-call) or X-Session-Token (session mode).
header
05
Receive your result200 OK + JSON response. Payment verified on-chain before compute runs.
200 · JSON
json · 402 quote excerpt
HTTP/1.1 402 Payment Required
{"maxAmountRequired": "10000", "payTo": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878"}
schemes

Pick your mode

A 402 quote advertises multiple accepts[] entries. Each pairs a chain with a scheme — pick the one your agent wallet supports.

SchemeChainsWhat you doBest 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).

javascript
// 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.

typescript · EVM exact
// 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.

typescript · Solana
// 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
debugging

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.

json · common error strings
// 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" }
agent runtimes

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.

bash
# 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