The independent payment layer for AI agents.
Pay for any API. Get paid for yours. Same endpoint, two rails — Stripe for humans, tools402 for agents. Multi-chain USDC, verifiable on-chain, no lock-in.
Already on Stripe? Keep it.
Stripe for humans. tools402 for agents. Same endpoint, two payment surfaces.
We complement Stripe, we don't replace it.
Coinbase x402 + Google AP2. Both live.
The only bridge between the two standards of agent payments. tools402 was wire-protocol-first since day one; AP2 is the mandate layer that wraps it. 3 of 4 AP2 touchpoints live today.
Announced September 2025 with 60 partners — Visa · Mastercard · Stripe · Adyen · Coinbase.
Wrap. Price. Get paid.
Any HTTPS endpoint becomes a paid API in ~10s. EIP-712 sign once. Choose your payout chain (Base, Polygon, Arbitrum, Optimism, Avalanche, or Solana) — paid in USDC there; cross-chain handled via CCTP. Settlement runs daily at 00:00 UTC, in USDC. A balance ≥ 1 USDC is paid at the next run (within 24h). A balance below 1 USDC carries over until it reaches 1 USDC, then it's paid. No KYC.
// npm i viem — register a seller slug (multi-wallet body, payout_chain set)
import { keccak256, toBytes } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const DOMAIN = { name: "tools402", version: "1", chainId: 8453 } as const;
const TYPES = {
SellerAction: [
{ name: "wallet", type: "address" },
{ name: "action", type: "string" },
{ name: "payloadHash", type: "bytes32" },
{ name: "timestamp", type: "uint256" },
],
} as const;
const SLUG = "your-slug"; // ^[a-z0-9-]{3,32}$
const payoutChain = "base"; // base · polygon · arbitrum · optimism · avalanche · solana
const account = privateKeyToAccount(process.env.KEY as `0x${string}`);
const ts = Math.floor(Date.now() / 1000);
// payout_chain set → hash binds slug:payoutChain (hash and body MUST match)
const payloadHash = keccak256(toBytes(`${SLUG}:${payoutChain}`));
// 1. Sign EIP-712 SellerAction (wallet = primary identity)
const sig = await account.signTypedData({
domain: DOMAIN, types: TYPES, primaryType: "SellerAction",
message: { wallet: account.address, action: "register", payloadHash, timestamp: BigInt(ts) },
});
// 2. POST /v1/_seller/register (add solana/polygon to wallets+signatures for multi-chain receive)
// EIP-712 message.wallet = primary_wallet for ALL EVM chains (not the per-chain address)
await fetch("https://api.tools402.dev/v1/_seller/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
primary_wallet: account.address,
wallets: { base: account.address },
signatures: { base: sig },
slug: SLUG,
ts,
payout_chain: payoutChain,
}),
});
// → 200 · seller active in ~10s
HTTP that pays per call.
HTTP 402 + a USDC transfer. No account, no key — your wallet is your identity.
# Rail EXACT — you broadcast USDC and pay gas. Needs: foundry (cast), jq, python3.
# Prereq: curl -L https://foundry.paradigm.xyz | bash && foundryup
export USDC=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
export RPC=https://mainnet.base.org
export KEY=$PRIVATE_KEY # never commit, never paste in clear
ENDPOINT=https://api.tools402.dev/v1/pdf-md
# 1. POST → 402 quote (body JSON)
RESP=$(curl -si -X POST "$ENDPOINT" -F "file=@doc.pdf")
BODY=$(echo "$RESP" | awk 'BEGIN{b=0} /^\r?$/{b=1;next} b{print}')
QUOTE=$(echo "$BODY" | jq '.accepts[] | select(.network=="base" and .scheme=="exact")')
RECIPIENT=$(echo "$QUOTE" | jq -r .payTo)
AMOUNT=$(echo "$QUOTE" | jq -r .maxAmountRequired)
ASSET=$(echo "$QUOTE" | jq -r .asset)
# 2. Settle USDC on-chain (you pay gas)
TX_HASH=$(cast send $USDC "transfer(address,uint256)" \
$RECIPIENT $AMOUNT \
--private-key $KEY --rpc-url $RPC)
FROM=$(cast wallet address --private-key $KEY)
# X-Payment is base64url(JSON) — encode it:
X_PAYMENT=$(TX_HASH="$TX_HASH" FROM="$FROM" RECIPIENT="$RECIPIENT" \
AMOUNT="$AMOUNT" ASSET="$ASSET" python3 -c "
import json, base64, os
p = {'x402Version': 1, 'scheme': 'exact', 'network': 'base',
'payload': {'txHash': os.environ['TX_HASH'], 'from': os.environ['FROM'],
'to': os.environ['RECIPIENT'], 'amount': os.environ['AMOUNT'],
'asset': os.environ['ASSET']}}
print(base64.urlsafe_b64encode(json.dumps(p).encode()).decode().rstrip('='))
")
# 3. Retry with X-Payment header
curl -X POST "$ENDPOINT" \
-F "file=@doc.pdf" \
-H "X-Payment: $X_PAYMENT"
# → 200 OK · markdown returned
# ─────────────────────────────────────────────
# Rail GASLESS (EIP-3009) — Base — scheme "transfer-with-authorization"
# Sign offline (see https://tools402.dev/docs/integrations#session-token), then:
curl -X POST "$ENDPOINT" \
-F "file=@doc.pdf" \
-H "X-Session-Token: $TOOLS402_SESSION_TOKEN"
# Token chain field must be "base" · tools402 pays gas
# ─────────────────────────────────────────────
# Rail GASLESS (EIP-3009) — Polygon — scheme "transfer-with-authorization"
# Sign offline (see https://tools402.dev/docs/integrations#session-token), then:
curl -X POST "$ENDPOINT" \
-F "file=@doc.pdf" \
-H "X-Session-Token: $TOOLS402_SESSION_TOKEN"
# Token chain field must be "polygon" · tools402 pays gas
# ─────────────────────────────────────────────
# Rail GASLESS (spl-transfer) — Solana — see integrations #curl for full walkthrough
# Scheme "spl-transfer" · network "solana" · X-Payment header
# Rail EXACT — you broadcast USDC and pay gas. pip install web3 requests
import json
import base64
import os
import requests
from web3 import Web3
USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
TRANSFER_ABI = [{"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],
"name":"transfer","outputs":[{"type":"bool"}],
"stateMutability":"nonpayable","type":"function"}]
w3 = Web3(Web3.HTTPProvider(os.environ["RPC"]))
account = w3.eth.account.from_key(os.environ["KEY"])
usdc = w3.eth.contract(address=Web3.to_checksum_address(USDC), abi=TRANSFER_ABI)
ENDPOINT = "https://api.tools402.dev/v1/pdf-md"
# 1. POST → 402 quote
r = requests.post(ENDPOINT, files={"file": open("doc.pdf", "rb")})
quote = next(a for a in r.json()["accepts"]
if a["network"] == "base" and a["scheme"] == "exact")
# 2. Settle USDC on-chain (chainId 8453)
tx = usdc.functions.transfer(quote["payTo"], int(quote["maxAmountRequired"])).build_transaction({
"from": account.address,
"nonce": w3.eth.get_transaction_count(account.address),
"chainId": 8453,
"gas": 100_000,
"maxFeePerGas": w3.eth.gas_price * 2,
"maxPriorityFeePerGas": w3.to_wei(0.001, "gwei"),
})
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction).hex()
# 3. Retry with X-Payment header
x_payment = base64.urlsafe_b64encode(json.dumps({
"x402Version": 1, "scheme": "exact", "network": "base",
"payload": {
"txHash": tx_hash, "from": account.address, "to": quote["payTo"],
"amount": quote["maxAmountRequired"], "asset": quote["asset"],
},
}).encode()).decode().rstrip("=")
r = requests.post(ENDPOINT, files={"file": open("doc.pdf", "rb")},
headers={"X-Payment": x_payment})
print(r.text) # markdown
# ─────────────────────────────────────────────
# Rail GASLESS (EIP-3009) — Base — scheme "transfer-with-authorization"
# Generate token locally (viem signTypedData, chainId 8453), then:
token = os.environ["TOOLS402_SESSION_TOKEN"] # chain="base" in payload
r = requests.post(ENDPOINT, files={"file": open("doc.pdf", "rb")},
headers={"X-Session-Token": token})
print(r.text)
# ─────────────────────────────────────────────
# Rail GASLESS (EIP-3009) — Polygon — scheme "transfer-with-authorization"
# Generate token locally (viem signTypedData, chainId 137), then:
token = os.environ["TOOLS402_SESSION_TOKEN"] # chain="polygon" in payload
r = requests.post(ENDPOINT, files={"file": open("doc.pdf", "rb")},
headers={"X-Session-Token": token})
print(r.text)
# ─────────────────────────────────────────────
# Rail GASLESS (spl-transfer) — Solana — partial-sign SPL tx; facilitator broadcasts
# Scheme "spl-transfer" · network "solana"
// Rail EXACT — you broadcast USDC and pay gas. npm i viem
import { createWalletClient, http, parseAbi } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";
import { randomBytes } from "node:crypto";
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const ENDPOINT = "https://api.tools402.dev/v1/pdf-md";
const account = privateKeyToAccount(process.env.KEY);
const walletClient = createWalletClient({
account, chain: base, transport: http(process.env.RPC),
});
// 1. POST → 402 quote
const r = await fetch(ENDPOINT, { method: "POST", body: formData });
const { accepts } = await r.json();
const q = accepts.find(a => a.network === "base" && a.scheme === "exact");
// 2. Settle USDC on-chain (you pay gas)
const txHash = await walletClient.writeContract({
address: q.asset,
abi: parseAbi(["function transfer(address,uint256) returns (bool)"]),
functionName: "transfer",
args: [q.payTo, BigInt(q.maxAmountRequired)],
});
// 3. Retry with X-Payment header
const xPayment = Buffer.from(JSON.stringify({
x402Version: 1, scheme: "exact", network: "base",
payload: {
txHash, from: account.address, to: q.payTo,
amount: q.maxAmountRequired, asset: q.asset,
},
})).toString("base64url");
const result = await fetch(ENDPOINT, {
method: "POST", body: formData,
headers: { "X-Payment": xPayment },
});
// ─────────────────────────────────────────────
// Rail GASLESS (EIP-3009) — Base — you sign; tools402 submits the tx and pays gas.
// Scheme "transfer-with-authorization" · network "base" · header X-Session-Token
const rGas = await fetch(ENDPOINT, { method: "POST", body: formData });
const { accepts: acceptsGas } = await rGas.json();
const qGas = acceptsGas.find(a => a.scheme === "transfer-with-authorization"
&& a.network === "base");
const validBefore = BigInt(Math.floor(Date.now() / 1000) + 3600);
const nonce = `0x${randomBytes(32).toString("hex")}`;
const value = BigInt(qGas.maxAmountRequired);
const signature = await walletClient.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: qGas.payTo, value,
validAfter: 0n, validBefore, nonce,
},
});
const token = Buffer.from(JSON.stringify({
chain: "base", from: account.address, to: qGas.payTo,
value: `0x${value.toString(16)}`, validAfter: "0x0",
validBefore: `0x${validBefore.toString(16)}`, nonce, signature,
})).toString("base64url");
const resultGas = await fetch(ENDPOINT, {
method: "POST", body: formData,
headers: { "X-Session-Token": token },
});
// ─────────────────────────────────────────────
// Rail GASLESS (EIP-3009) — Polygon — you sign; tools402 submits the tx and pays gas.
// Scheme "transfer-with-authorization" · network "polygon" · header X-Session-Token
const rGas = await fetch(ENDPOINT, { method: "POST", body: formData });
const { accepts: acceptsGas } = await rGas.json();
const qGas = acceptsGas.find(a => a.scheme === "transfer-with-authorization"
&& a.network === "polygon");
const validBefore = BigInt(Math.floor(Date.now() / 1000) + 3600);
const nonce = `0x${randomBytes(32).toString("hex")}`;
const value = BigInt(qGas.maxAmountRequired);
const signature = await walletClient.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: qGas.payTo, value,
validAfter: 0n, validBefore, nonce,
},
});
const token = Buffer.from(JSON.stringify({
chain: "polygon", from: account.address, to: qGas.payTo,
value: `0x${value.toString(16)}`, validAfter: "0x0",
validBefore: `0x${validBefore.toString(16)}`, nonce, signature,
})).toString("base64url");
const resultGas = await fetch(ENDPOINT, {
method: "POST", body: formData,
headers: { "X-Session-Token": token },
});
// ─────────────────────────────────────────────
// Rail GASLESS (spl-transfer) — Solana — partial-sign; facilitator pays gas.
// Scheme "spl-transfer" · network "solana"
const rSol = await fetch(ENDPOINT, { method: "POST", body: formData });
const { accepts: acceptsSol } = await rSol.json();
const qSol = acceptsSol.find(a => a.scheme === "spl-transfer"
&& a.network === "solana");
const serializedTx = tx.serialize({ requireAllSignatures: false }).toString("base64");
const xPaymentSol = Buffer.from(JSON.stringify({
x402Version: 1, scheme: "spl-transfer", network: "solana",
payload: {
serializedTx, signature: buyerSig, from: buyerPubkey, to: qSol.payTo,
mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", amount: qSol.maxAmountRequired,
},
})).toString("base64url");
const resultSol = await fetch(ENDPOINT, {
method: "POST", body: formData, headers: { "X-Payment": xPaymentSol },
});
# pip install mcp web3 requests
import json
import base64
import os
import requests
from web3 import Web3
from mcp.server.fastmcp import FastMCP
USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
TRANSFER_ABI = [{"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],
"name":"transfer","outputs":[{"type":"bool"}],
"stateMutability":"nonpayable","type":"function"}]
w3 = Web3(Web3.HTTPProvider(os.environ["RPC"]))
account = w3.eth.account.from_key(os.environ["KEY"])
usdc = w3.eth.contract(address=Web3.to_checksum_address(USDC), abi=TRANSFER_ABI)
def _tools402_http(endpoint: str, files=None, json_body=None) -> str:
r = requests.post(endpoint, files=files, json=json_body)
if r.status_code != 402:
r.raise_for_status()
return r.text
quote = next(a for a in r.json()["accepts"]
if a["network"] == "base" and a["scheme"] == "exact")
tx = usdc.functions.transfer(quote["payTo"], int(quote["maxAmountRequired"])).build_transaction({
"from": account.address,
"nonce": w3.eth.get_transaction_count(account.address),
"chainId": 8453,
"gas": 100_000,
"maxFeePerGas": w3.eth.gas_price * 2,
"maxPriorityFeePerGas": w3.to_wei(0.001, "gwei"),
})
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction).hex()
x_payment = base64.urlsafe_b64encode(json.dumps({
"x402Version": 1, "scheme": "exact", "network": "base",
"payload": {
"txHash": tx_hash, "from": account.address, "to": quote["payTo"],
"amount": quote["maxAmountRequired"], "asset": quote["asset"],
},
}).encode()).decode().rstrip("=")
r2 = requests.post(endpoint, files=files, json=json_body,
headers={"X-Payment": x_payment})
r2.raise_for_status()
return r2.text
mcp = FastMCP("tools402")
@mcp.tool()
def tools402_call(endpoint: str, payload: dict | None = None) -> str:
"""Call a paid tools402 API endpoint via HTTP 402 (exact rail)."""
files = json_body = None
if payload:
if "file_path" in payload:
files = {"file": open(payload["file_path"], "rb")}
else:
json_body = payload
return _tools402_http(endpoint, files=files, json_body=json_body)
if __name__ == "__main__":
mcp.run() # stdio — wire in Claude Desktop / Cursor yourself
# Client config — point at YOUR server script (no tools402 npm package):
# "command": "python", "args": ["/path/to/server.py"],
# "env": {"RPC": "https://mainnet.base.org", "KEY": "$PRIVATE_KEY"}
# pip install web3 requests langchain-core
import json
import base64
import os
import requests
from web3 import Web3
from langchain_core.tools import StructuredTool
USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
TRANSFER_ABI = [{"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],
"name":"transfer","outputs":[{"type":"bool"}],
"stateMutability":"nonpayable","type":"function"}]
w3 = Web3(Web3.HTTPProvider(os.environ["RPC"]))
account = w3.eth.account.from_key(os.environ["KEY"])
usdc = w3.eth.contract(address=Web3.to_checksum_address(USDC), abi=TRANSFER_ABI)
def _tools402_http(endpoint: str, files=None, json_body=None) -> str:
r = requests.post(endpoint, files=files, json=json_body)
if r.status_code != 402:
r.raise_for_status()
return r.text
quote = next(a for a in r.json()["accepts"]
if a["network"] == "base" and a["scheme"] == "exact")
tx = usdc.functions.transfer(quote["payTo"], int(quote["maxAmountRequired"])).build_transaction({
"from": account.address,
"nonce": w3.eth.get_transaction_count(account.address),
"chainId": 8453,
"gas": 100_000,
"maxFeePerGas": w3.eth.gas_price * 2,
"maxPriorityFeePerGas": w3.to_wei(0.001, "gwei"),
})
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction).hex()
x_payment = base64.urlsafe_b64encode(json.dumps({
"x402Version": 1, "scheme": "exact", "network": "base",
"payload": {
"txHash": tx_hash, "from": account.address, "to": quote["payTo"],
"amount": quote["maxAmountRequired"], "asset": quote["asset"],
},
}).encode()).decode().rstrip("=")
r2 = requests.post(endpoint, files=files, json=json_body,
headers={"X-Payment": x_payment})
r2.raise_for_status()
return r2.text
def tools402_call(endpoint: str, file_path: str | None = None) -> str:
files = {"file": open(file_path, "rb")} if file_path else None
return _tools402_http(endpoint, files=files)
tools402_tool = StructuredTool.from_function(
func=tools402_call,
name="tools402_call",
description="Call a paid tools402 API endpoint (HTTP 402 exact rail)",
)
agent = create_react_agent(llm, [tools402_tool, ...])
agent.invoke({"input": "Extract doc.pdf via https://api.tools402.dev/v1/pdf-md"})
# pip install web3 requests crewai
import json
import base64
import os
import requests
from web3 import Web3
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
TRANSFER_ABI = [{"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],
"name":"transfer","outputs":[{"type":"bool"}],
"stateMutability":"nonpayable","type":"function"}]
w3 = Web3(Web3.HTTPProvider(os.environ["RPC"]))
account = w3.eth.account.from_key(os.environ["KEY"])
usdc = w3.eth.contract(address=Web3.to_checksum_address(USDC), abi=TRANSFER_ABI)
def _tools402_http(endpoint: str, files=None, json_body=None) -> str:
r = requests.post(endpoint, files=files, json=json_body)
if r.status_code != 402:
r.raise_for_status()
return r.text
quote = next(a for a in r.json()["accepts"]
if a["network"] == "base" and a["scheme"] == "exact")
tx = usdc.functions.transfer(quote["payTo"], int(quote["maxAmountRequired"])).build_transaction({
"from": account.address,
"nonce": w3.eth.get_transaction_count(account.address),
"chainId": 8453,
"gas": 100_000,
"maxFeePerGas": w3.eth.gas_price * 2,
"maxPriorityFeePerGas": w3.to_wei(0.001, "gwei"),
})
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction).hex()
x_payment = base64.urlsafe_b64encode(json.dumps({
"x402Version": 1, "scheme": "exact", "network": "base",
"payload": {
"txHash": tx_hash, "from": account.address, "to": quote["payTo"],
"amount": quote["maxAmountRequired"], "asset": quote["asset"],
},
}).encode()).decode().rstrip("=")
r2 = requests.post(endpoint, files=files, json=json_body,
headers={"X-Payment": x_payment})
r2.raise_for_status()
return r2.text
class Tools402Input(BaseModel):
endpoint: str = Field(..., description="Full URL e.g. https://api.tools402.dev/v1/pdf-md")
file_path: str | None = Field(None, description="Local file for multipart endpoints")
class Tools402Tool(BaseTool):
name: str = "tools402_call"
description: str = "Call a paid tools402 API endpoint via HTTP 402 exact rail"
args_schema: type[BaseModel] = Tools402Input
def _run(self, endpoint: str, file_path: str | None = None) -> str:
files = {"file": open(file_path, "rb")} if file_path else None
return _tools402_http(endpoint, files=files)
researcher = Agent(
role="Data Researcher",
tools=[Tools402Tool()],
goal="Extract structured data from documents",
)
// n8n workflow — native nodes only (no community package)
// Env: RPC + KEY (never paste private keys in exported workflows)
//
// Node 1 · HTTP Request: POST https://api.tools402.dev/v1/pdf-md
// Body: Form-Data → file (binary) → 402 JSON with accepts[]
//
// Node 2 · Code (Python): paste tools402_call helper (web3 + requests)
// Reads accepts[] from Node 1 → settles USDC → outputs x_payment
//
// Node 3 · HTTP Request: same POST
// Header: X-Payment = {{ $json.x_payment }}
//
[Trigger] → [HTTP Request · quote] → [Code · settle USDC] → [HTTP Request · paid]
Never goes silent.
Buyers pay across 3 chains, 9 facilitators. Sellers settle across 6.
9 facilitators · 3 per chain · sub-500ms failover · 1,649 tests / 0 fail · Sentry 22 metrics
Cross-chain · CCTP V2
Pay on one chain. Settle on another.
Cross-chain settlement live via CCTP V2. Atomic, ~15s, zero protocol fee, zero inventory. 30 cross-chain settlement paths · CCTP V2 across 6 chains.
Live · proven Base→Solana on-chain today.
Settlement flow
Positioning
Independent by design.
A chain's own marketplace keeps value on its chain. We don't. Route payments wherever they need to go — across any catalog, any chain. We're not aligned to one network; we're aligned to you.
Not a chain marketplace. A neutral payment layer agents can route through.
Proof
Verify everything.
Every transaction is on-chain and queryable: 159 endpoints, real-time facilitator health, signed events.
Open /proof →Ship your endpoint. Pay your agent. Same rail.
Wire your agent in five minutes. Publish your API in ten seconds.