Drop tools402 into your agent runtime.
The wire protocol is plain HTTP. Copy-paste curl, httpx, or fetch — no tools402 package required. Official npm/PyPI clients exist but are parqued for now; every runtime below shows raw HTTP you can run today.
curl no install
The raw 402 dance in five lines of bash. Best for one-off scripts, CI pipelines, sanity checks, or languages without an official SDK. Pair with cast send from Foundry for the USDC transfer. Same flow on three chains — pick the one your agent already holds USDC on.
# 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 # → {"maxAmountRequired": "10000", "payTo": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878", …} # 2 · pay 0.010 USDC on Base cast send 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \ "transfer(address,uint256)" \ 0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878 10000 \ --rpc-url https://mainnet.base.org # 3 · retry with X-Payment receipt curl -X POST https://api.tools402.dev/v1/pdf-md \ -H 'X-Payment: eyJ4NDAyVi…ifQ' \ -F "file=@receipt.pdf" # → 200 OK · {"markdown": "# Receipt …"}
# Same flow, swap the USDC contract + RPC. Recipient EVM address is identical. cast send 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 \ "transfer(address,uint256)" \ 0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878 10000 \ --rpc-url undefined # Then retry with X-Payment header carrying network: "polygon".
// 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" && scheme === "spl-transfer" const r402 = await fetch("https://api.tools402.dev/v1/pdf-md", { method: "POST", body: formData, }); // → 402 · accepts[].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
Session token copy-paste · no install
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 and Polygon).
// npm i viem — sign once, reuse the token until it expires (Base) 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>
// npm i viem — sign once, reuse the token until it expires (Polygon) import { privateKeyToAccount } from "viem/accounts"; import { randomBytes } from "node:crypto"; const account = privateKeyToAccount(process.env.PRIVATE_KEY); const recipient = "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878"; // Polygon 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: 137, verifyingContract: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" }, 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: "polygon", 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>
402 payment modes
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 (/buy-side) — 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).
| Scheme | Flow | Guide |
|---|---|---|
transfer-with-authorization |
Pre-sign once; tools402 pulls gaslessly per call. Best for agents making many calls. | Session token snippet above |
exact |
Broadcast a USDC transfer yourself; send its txHash per call. No pre-signing — you pay gas each time. |
/buy-side — full EVM + Solana flows |
spl-transfer |
Gasless on Solana — facilitator broadcasts (partial-sign, do not self-broadcast). | Solana spl-transfer snippet |
Python httpx · raw HTTP
Session mode: one header, gasless pulls. Generate a token with the Session token snippet above, then call any endpoint with httpx or requests. No pip install tools402 required.
# pip install httpx import os, httpx token = os.environ["TOOLS402_SESSION_TOKEN"] with open("receipt.pdf", "rb") as f: r = httpx.post( "https://api.tools402.dev/v1/pdf-md", headers={"X-Session-Token": token}, files={"file": ("receipt.pdf", f, "application/pdf")}, timeout=120, ) r.raise_for_status() print(r.json()["markdown"]) # → # Receipt 2026-05-15 …
Node · Bun · Deno fetch · Node 18+
Global fetch in Node 18+, Bun, Deno, Cloudflare Workers, and Vercel Edge. Set TOOLS402_SESSION_TOKEN from the Session token snippet above — same session header as Python, zero npm dependencies.
// Node 18+ — no npm install const token = process.env.TOOLS402_SESSION_TOKEN; const file = Bun.file("receipt.pdf"); // or fs.readFileSync in Node const form = new FormData(); form.append("file", new Blob([await file.arrayBuffer()]), "receipt.pdf"); const res = await fetch("https://api.tools402.dev/v1/pdf-md", { method: "POST", headers: { "X-Session-Token": token }, body: form, }); console.log((await res.json()).markdown);
LangChain httpx + @tool
Wrap any endpoint as a LangChain tool with httpx — no tools402-langchain package. Set TOOLS402_SESSION_TOKEN from the Session token snippet above. Discover paths and prices live via GET /v1/_meta.
# pip install httpx langchain-core import os, httpx from langchain_core.tools import tool TOKEN = os.environ["TOOLS402_SESSION_TOKEN"] @tool def pdf_to_markdown(file_path: str) -> str: """Convert a PDF to markdown via paid /v1/pdf-md.""" with open(file_path, "rb") as f: r = httpx.post( "https://api.tools402.dev/v1/pdf-md", headers={"X-Session-Token": TOKEN}, files={"file": (file_path, f, "application/pdf")}, timeout=120, ) r.raise_for_status() return r.json()["markdown"] # tools = [pdf_to_markdown, …] → pass to your agent
CrewAI httpx + @tool
Same thin HTTP wrapper for CrewAI's @tool decorator. Each function is one paid endpoint — set TOOLS402_SESSION_TOKEN from the Session token snippet above for gasless settlement.
# pip install httpx crewai import os, httpx from crewai.tools import tool TOKEN = os.environ["TOOLS402_SESSION_TOKEN"] @tool("Extract PDF to markdown") def pdf_md(file_path: str) -> str: """Paid /v1/pdf-md call.""" with open(file_path, "rb") as f: r = httpx.post( "https://api.tools402.dev/v1/pdf-md", headers={"X-Session-Token": TOKEN}, files={"file": (file_path, f, "application/pdf")}, timeout=120, ) r.raise_for_status() return r.json()["markdown"]
n8n HTTP Request node
Use n8n's built-in HTTP Request node — no community package. Generate TOOLS402_SESSION_TOKEN with the Session token snippet above, store it in n8n credentials (Header Auth), and chain paid calls in a visual workflow.
# HTTP Request node — POST https://api.tools402.dev/v1/pdf-md # Authentication: Header Auth # Name: X-Session-Token # Value: {{ $env.TOOLS402_SESSION_TOKEN }} # Body: Form-Data → file (binary) [Trigger] ↓ [HTTP Request · /v1/pdf-md] · $0.010 ↓ [HTTP Request · /v1/siren-fetch] · $0.002 ↓ [Database: insert]