Sera Sera for Agents / Docs / Tutorials / x402-paid API
Tutorial 05

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.

25 min Express EIP-3009 USDC
What you'll have at the end

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

server.ts
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

x402-mw.ts
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

Common gotchas

Next: AI agent tutorial for the inverse — your agent paying x402 endpoints out in the wild.