AI agents are starting to pay for API calls on their own. They don't sign up or use a card. They just pay per request in USDC. The protocol for this is x402, and it builds on an HTTP status code that sat unused for decades: 402 Payment Required.
This post shows how we put an API behind x402 with a small open-source Cloudflare Worker, what the 402 response really looks like, and how we checked our setup. It also covers what doesn't work yet.
The loop: 402 → pay → retry
- A client calls your endpoint with no payment.
- You reply
402with a JSON body saying what to pay: amount, asset, network and receiving address. - The client signs a USDC authorization and retries with an
X-PAYMENTheader. - Your server asks a facilitator to verify the payment, runs the request, then asks the facilitator to settle it on-chain.
- You return
200plus anX-PAYMENT-RESPONSEheader carrying the transaction hash.
Your server never holds a private key. It only receives money.
Deploy the kit
The x402 Paywall Kit is an MIT-licensed Worker with no runtime dependencies. It sits in front of an existing API (or serves its own handlers) and does the loop above.
git clone https://github.com/astratc/x402-paywall-kit && cd x402-paywall-kit
# edit wrangler.toml, then:
npm install && npx wrangler deploy
The config lives in wrangler.toml:
[vars]
PAY_TO = "0xYourReceivingWalletAddress000000000000" # your Base address (receive only)
NETWORK = "base" # or "base-sepolia" for testing
FACILITATOR_URL = "https://facilitator.payai.network"
UPSTREAM_URL = "https://api.example.com" # the API you are monetizing
SERVICE_NAME = "My x402 API"
ROUTES = '{"/v1/data": {"price": "$0.01", "description": "Example paid route"}}'
If your upstream API needs a key, keep it out of the repo by storing it as a Worker secret:
npx wrangler secret put UPSTREAM_AUTH_VALUE # e.g. "Bearer sk_..."
# and in wrangler.toml: UPSTREAM_AUTH_HEADER = "authorization"
Buyers never see that key. They pay the Worker, and the Worker calls your API with it.
The test suite runs offline against a stub facilitator and a stub upstream:
npm test
# pass 10
# fail 0
It covers the 402 shape, verify → run → settle, a failed verify that never calls your API, a failed settle that withholds the response, and upstream errors that never get settled.
What a real 402 looks like
Here's our live demo route ($0.001 on Base), fetched as I wrote this:
curl -i https://x402-paywall-demo.astratc.workers.dev/v1/fortune
HTTP/2 402
content-type: application/json
access-control-allow-origin: *
access-control-expose-headers: PAYMENT-REQUIRED, X-PAYMENT-RESPONSE
payment-required: eyJ4NDAyVmVyc2lvbiI6MSwi... (base64 of the body)
{"x402Version":1,"error":"X-PAYMENT header is required","accepts":[{
"scheme":"exact","network":"base","maxAmountRequired":"1000",
"resource":"https://x402-paywall-demo.astratc.workers.dev/v1/fortune",
"description":"A random fortune, paid per call in USDC",
"mimeType":"application/json",
"payTo":"0x1A7Dc2677391D49FB3001AD5a1aa89A573c9b865",
"maxTimeoutSeconds":60,
"asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"outputSchema":{"input":{"type":"http","method":"GET","discoverable":true},
"output":{"type":"object","properties":{"fortune":{"type":"string"}}}},
"extra":{"name":"USD Coin","version":"2"}}]}
maxAmountRequired is in USDC's 6-decimal atomic units, so 1000 = $0.001. The asset is the USDC contract on Base.
A paying client looks like this with x402-fetch:
import { wrapFetchWithPayment } from "x402-fetch";
const pay = wrapFetchWithPayment(fetch, walletClient); // viem wallet with USDC on Base
const r = await pay("https://x402-paywall-demo.astratc.workers.dev/v1/fortune");
Discovery files
Agents need to find your paid endpoint and know what it returns. The kit generates these automatically:
-
/.well-known/x402: your paid routes and prices -
/.well-known/did.json: adid:webidentity for your service -
/llms.txt: a plain-text description for LLM agents -
/openapi.json: withx-payment-infoon paid operations -
/health: free, so monitors don't have to pay
Write a real description and outputSchema for each route. That's what an agent reads before deciding whether to pay you.
Check it: AgentReady
We built AgentReady to check this. It probes a URL for a valid 402, the discovery files, agent cards, MCP, llms.txt, robots rules and CORS, then gives a score from 0 to 100 with a list of fixes. You get 3 free checks a day.
We ran it on our own paid data API first. It scored 77/100 (B). Here's why:
- No MCP endpoint. We don't offer one yet.
-
No
llms.txton the API host. We have one, but it lives on our website, not on the API's own domain where agents look. - No CORS headers. Browser-based agents can't read the 402.
The llms.txt and CORS fixes are small, and we're adding them next. Those two alone are worth about 13 points. The kit's demo scored 80/100. It loses points for having no agent card and no MCP, which the kit doesn't generate yet.
Honest limits
-
Settlement happens after your handler runs. If verification passes but settlement then fails, the response is withheld, but your upstream has already done the work. For expensive calls, keep
maxTimeoutSecondstight and make your upstream idempotent. -
No real paid call through the kit yet. Unpaid 402s work, and a forged
X-PAYMENTis rejected by the real facilitator (invalid_exact_evm_signature). But a full settle needs an outside buyer's wallet, and we don't sign test payments ourselves. Our separate x402 data API has had its first real paid call settle. -
Bazaar listing is not verified. The 402 is marked
discoverable, but we haven't confirmed it appears in Coinbase's x402 Bazaar index. AgentReady says so in its output instead of guessing. - AgentReady can't check its own hostname, because of a Cloudflare same-worker fetch limitation.
- The kit supports x402 v1 with the
exactscheme on Base only. Facilitators change which networks they support, so checkGET <facilitator>/supported.
Setup help
The kit is free. If you'd rather have it done for you, we'll deploy it on your Cloudflare account, write your route descriptions and schemas, add the discovery files and run an AgentReady check. Prices start at $25 for one route ($40 for up to 5, $60 with custom handlers or MCP/agent-card setup), paid in USDC. Email carl.astratc@proton.me.
Not affiliated with Coinbase or Cloudflare.
Top comments (0)