Buying (agents)
This gateway — and x402 itself — is agent-first: the protocol exists so software can pay per request without accounts or checkouts. Human browser purchases work, but they are the secondary path. This page covers both, agents first.
Discover
GET /catalog.json returns every product with its price, URL, and (for folders) each purchasable file — including excerpt teasers when the operator enabled previews:
{ "products": [ { "sku": "library", "title": "Whole Library", "price": "$0.01",
"route": "GET /library/*",
"files": [ { "path": "guide.md", "url": "/library/guide.md", "size": 2048,
"excerpt": "Soil Regeneration Basics…" } ] } ] }
Products marked discoverable are also announced to x402 discovery registries, so agents can find the store without knowing the URL.
Pay — with @x402/fetch (TypeScript)
import { privateKeyToAccount } from "viem/accounts";
import { wrapFetchWithPayment, x402Client, decodePaymentResponseHeader } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
const signer = privateKeyToAccount(process.env.BUYER_PRIVATE_KEY);
const client = new x402Client();
registerExactEvmScheme(client, { signer });
const payFetch = wrapFetchWithPayment(fetch, client);
const res = await payFetch("https://store.example.com/library/guide.md");
const receipt = decodePaymentResponseHeader(res.headers.get("payment-response"));
// receipt = { success: true, payer: "0x…", transaction: "0x…", network: "eip155:…" }
The wallet needs USDC on the gateway's network (testnet: free from faucet.circle.com, Base Sepolia). No ETH needed.
Query products (the most agent-native shape)
Some products are endpoints, not files — e.g. paid retrieval: POST /ask with {"query": "why do insects attack low brix plants", "top_k": 5} returns the most relevant passages with source citations. Same x402 flow: the 402 challenge arrives on the POST, pay and retry with the payment header. Products like this declare an input schema in discovery metadata so agents know how to call them.
Pay — raw HTTP (any language)
GETthe paid URL →402; decode the base64payment-requiredresponse header → JSON withaccepts[](scheme, network, amount in atomic units, asset contract, payTo)- Sign an EIP-3009
transferWithAuthorizationfor exactly that amount, encode per the x402 spec, retry the request with it in thepayment-signatureheader (SDK ≥2.18; older clients useX-PAYMENT) 200+ content; the base64PAYMENT-RESPONSEheader carries{ success, payer, transaction, network }
A missing file 404s before any payment challenge — you can never pay for something that doesn't exist. A repriced product accepts both old and new price for one window, so a quote you just received always verifies.
Test buyer
The repo ships one: BUYER_PRIVATE_KEY=0x… node scripts/buy.mjs <url>
Buying as a human (secondary path)
Browsers hitting a paid URL get a wallet-connect paywall page instead of JSON. What to know:
- Best path — desktop + wallet extension (Coinbase Wallet, MetaMask, Rabby): click the file, approve the USDC signature in the extension popup, the file loads in the tab.
- Mobile: open the store inside your wallet app's built-in browser and buy there. The paywall's QR code is only for lending a phone wallet to a desktop page — scanning it from inside a wallet app is a dead end ("already in wallet").
- Delivery is the HTTP response: the file arrives in whichever browser context paid — a desktop tab, or the wallet's in-app browser (save/share from its menu).
- The wallet needs USDC on the store's network and zero gas — the payment is a gasless authorization.
- Paid but the download failed? Just request the same URL again: for a window after a settled purchase (default 60 min,
REDELIVERY_MINUTES), the same source gets the file again free — no second charge. Large files are the usual culprit; the payment always settles server-side first.
The paywall page is Coinbase's stock component from @x402/paywall; its connect UX is theirs. If human sales matter to your deployment, a custom paywall provider is a supported middleware hook.