DEV Community

Astra Trading Company
Astra Trading Company

Posted on

Put any API behind x402 in 10 minutes (and check it's agent-ready)

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

  1. A client calls your endpoint with no payment.
  2. You reply 402 with a JSON body saying what to pay: amount, asset, network and receiving address.
  3. The client signs a USDC authorization and retries with an X-PAYMENT header.
  4. Your server asks a facilitator to verify the payment, runs the request, then asks the facilitator to settle it on-chain.
  5. You return 200 plus an X-PAYMENT-RESPONSE header 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
Enter fullscreen mode Exit fullscreen mode

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"}}'
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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"}}]}
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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: a did:web identity for your service
  • /llms.txt: a plain-text description for LLM agents
  • /openapi.json: with x-payment-info on 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.txt on 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 maxTimeoutSeconds tight and make your upstream idempotent.
  • No real paid call through the kit yet. Unpaid 402s work, and a forged X-PAYMENT is 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 exact scheme on Base only. Facilitators change which networks they support, so check GET <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)