I sell verified B2B lead packs. For a one-off campaign, a $49/month data seat is a terrible deal — and for an AI agent, a subscription signup is a non-starter. Agents can't fill out billing forms. So I put the product behind x402: HTTP 402 "Payment Required", resurrected as a machine checkout. Here's exactly how it works, with the real shapes and flows.
x402 (from Coinbase) turns the 402 status code into a payment handshake an agent can complete without human help:
402 with machine-readable payment terms in the body.X-Payment header carrying proof of payment.
No accounts, no API keys, no OAuth dance for the buyer — the wallet is the identity, and the retry loop is the session.
The 402 body isn't an error page. It's an offer. Here's the actual structure I return:
{
"x402Version": 1,
"error": "Payment required",
"accepts": [
{
"scheme": "exact",
"network": "base",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xYourTreasuryWallet",
"maxAmountRequired": "9000000",
"maxTimeoutSeconds": 300,
"resource": "https://scoutpacks-tunnel-1.loca.lt/buy/pack-25",
"description": "25 verified B2B leads, JSON",
"mimeType": "application/json"
}
]
}
The fields that matter:
payTo — your treasury wallet. The agent pays this address directly.maxAmountRequired — in the asset's base units. USDC has 6 decimals, so 9000000 = $9.00. This is the single most common bug I see: people write dollars and charge micro-cents.asset — the token contract (above is USDC on Base).network — base here. Match your payTo wallet to a chain you actually monitor.scheme — exact means exact-amount settlement.resource — which endpoint the terms apply to.
I also serve a machine-readable manifest at /.well-known/x402 so agents can discover the catalog and terms before touching a paid endpoint — cheaper than learning via 402s.
Client side (the agent's wallet code):
import requests
URL = "https://scoutpacks-tunnel-1.loca.lt/buy/pack-25"
resp = requests.get(URL)
if resp.status_code == 402:
terms = resp.json()["accepts"][0]
payment_payload = sign_transfer(terms) # base64, per the x402 spec
resp = requests.get(URL, headers={"X-Payment": payment_payload})
resp.raise_for_status()
leads = resp.json() # 200 — the goods
Server side (FastAPI-style):
@app.get("/buy/{pack_id}")
def buy(pack_id: str, x_payment: str | None = Header(default=None)):
pack = CATALOG[pack_id]
if x_payment is None:
return JSONResponse(status_code=402, content=payment_terms(pack))
verify_onchain(x_payment, pack) # re-check amount, asset, recipient, settlement
return deliver(pack) # JSON leads: instant for the 25-pack
Three things worth internalizing:
maxTimeoutSeconds bounds how long the quote is valid; re-quote after it lapses./.well-known/x402 manifest, and delivery should all read the same catalog object. If price lives in three places, it will drift.
Raw HTTP is fine, but agents live in tool-calling land. The repo ships an MCP server with two tools:
list_packs() — returns the catalog: pack sizes, prices, and exactly what each pack contains. No payment needed; this is the menu.buy_pack(pack_id) — runs the pay-and-retry flow above and returns the leads JSON.
The MCP server reads the same catalog the 402 endpoint uses, so the menu and the checkout can never disagree. The pattern generalizes: list_* (free, discoverable) + buy_* (402-gated) is a good shape for any agent-sold data product.
Scout Packs is the live implementation of everything above: verified B2B lead packs for AI agents — 25 leads for $9, 50 for $15, 100 for $25. Every lead ships as JSON: company, contact, title, published email, plus the source URL it was verified against.
list_packs / buy_pack): If you're selling anything to agents — data, compute, API access — stop minting API keys and start returning 402s. The wallet is the account.