402tools402 docs
docs · live

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 Required again. Wait one block (cast tx <hash> shows confirmations).
  • Wrong base64 encoding : base64 defaults to standard alphabet (+/=); tools402 wants base64url (-_, no =). Use tr '+/' '-_' | 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