x402-paid API.
Charge AI agents per call in USDC over plain HTTP. Standard 402 → pay → 200 flow. No paywall pages, no Stripe, no API keys, no contracts. Sera's own x402 service is the reference implementation; this tutorial shows you how to wrap any API the same way.
An Express middleware that gates any route behind a USDC payment. Agents pay $0.001 / call (or whatever you charge), retry with the X-PAYMENT header, get the response. Works with any agent that speaks x402 — no custom client SDK.
The flow
# 1. Agent makes initial request
GET /v1/data
→ 402 Payment Required
{
"x402_version": 1,
"accepts": [{
"scheme": "exact",
"network": "base",
"asset": "0xUSDC...",
"max_amount_required": "1000", # 1000 = $0.001
"pay_to": "0xYourVault...",
"resource": "/v1/data",
"max_timeout_seconds": 60
}]
}
# 2. Agent signs USDC transferWithAuthorization (EIP-3009) off-chain
# 3. Agent retries with X-PAYMENT header
GET /v1/data
X-PAYMENT: <base64-encoded payment payload with signature>
→ 200 OK { ... your data ... }
# Server submits the payment authorization on-chain (or batches)
Steps
1Install the x402 verifier
npm install x402 viem express
2Wrap your route with the middleware
import express from 'express';
import { x402Middleware } from './x402-mw';
const app = express();
app.get('/v1/data',
x402Middleware({
price_atomic: 1000n, // 0.001 USDC (6-decimal)
pay_to: process.env.VAULT_ADDR!,
asset: process.env.USDC_ADDR!,
network: 'base'
}),
async (req, res) => {
res.json({ data: 'whatever your API returns' });
}
);
app.listen(3000);
3The middleware itself
import { verifyEip3009Authorization } from './verify';
export function x402Middleware(opts) {
return async (req, res, next) => {
const header = req.headers['x-payment'];
if (!header) {
// First-time request — return 402 with payment instructions
return res.status(402).json({
x402_version: 1,
accepts: [{
scheme: 'exact', network: opts.network,
asset: opts.asset, max_amount_required: opts.price_atomic.toString(),
pay_to: opts.pay_to, resource: req.path, max_timeout_seconds: 60
}]
});
}
// Retry with X-PAYMENT header
const payload = JSON.parse(Buffer.from(header, 'base64').toString());
const ok = await verifyEip3009Authorization(payload, opts);
if (!ok.valid) {
return res.status(402).json({ error: 'invalid payment', reason: ok.reason });
}
// Submit the EIP-3009 authorization on-chain (or batch)
await submitOnchain(payload);
// Pass through to the actual handler
next();
};
}
The verifier (in ./verify.ts) checks the EIP-3009 signature, validates the recipient + amount + token + nonce + expiry against your opts, returns { valid: boolean, reason: string }. Sera's x402-service/ in github.com/Josh-sera/sera-agents/x402-service has a working reference for this code.
4Test with curl (demo mode first)
While developing, set a flag that accepts X-PAYMENT: any:demo-authorization instead of verifying real signatures. Lets you exercise the protocol without spending USDC.
# 1. First call returns 402
curl -i localhost:3000/v1/data
# 2. Retry with demo header
curl localhost:3000/v1/data \
-H 'X-PAYMENT: anything:demo-authorization'
# → 200 OK with your data
5Wire to a payment facilitator (production)
You can submit the EIP-3009 authorization to USDC's contract yourself (you pay gas), OR use a facilitator service like Coinbase's x402 facilitator which batches and pays gas for you. The latter is cheaper at low volume.
6Pricing variants
- Per-call flat — what we showed above
- Per-token (LLM-style) — read the response size, charge proportionally on the next call (or use a deposit-and-deduct pattern)
- Tiered — different routes have different prices; same middleware, different opts
- Free trial — first 100 calls per source address are free; check a counter before requiring payment
Common gotchas
- Replay protection. Each EIP-3009 authorization has a unique nonce. Reject reused nonces; the verifier should track them in a database.
- Settlement time. If you call
next()before the on-chain transfer confirms, you've shipped data on credit. Default behavior should wait for confirmation; only fire-and-forget for low-value calls. - Discoverability. Add a
GET /endpoint that returns a service-discovery JSON (price, asset, network, supported endpoints). Agents use it to learn what they can do. - Caching. 402 responses can be cached aggressively (no payment received yet). 200 responses with paid data should NOT be cached unless you want to give the next caller free data.
Next: AI agent tutorial for the inverse — your agent paying x402 endpoints out in the wild.