DEV Community

Alfred Zhang
Alfred Zhang

Posted on

How to Build Pay-Per-Call APIs with x402 and USDC on Base

The API Key Problem

Every API you've ever used follows the same pattern: sign up, get a key, attach it to requests, hope you don't get rate-limited. For humans, this is annoying. For AI agents operating autonomously, it's a dead end — they can't fill out registration forms or manage billing dashboards.

x402 fixes this by turning HTTP itself into a payment layer.

What is x402?

x402 is an open protocol by Coinbase that brings the long-dormant HTTP 402 Payment Required status code to life. The idea is simple:

  1. Client calls an API endpoint
  2. Server responds with 402 and a payment requirement (price, token, network)
  3. Client signs a USDC payment on Base
  4. Client retries the request with the payment proof in the header
  5. Server verifies payment via a facilitator and returns the response

No API keys. No accounts. No OAuth flows. Just money for data.

The facilitator (hosted at https://x402.org/facilitator) handles payment verification and settlement on-chain, so your server never touches private keys or payment logic.

Why This Matters for AI Agents

AI agents need to consume APIs autonomously. With x402:

  • No registration — agents don't need accounts or API keys
  • No billing management — payment happens per-request, inline
  • Composable — any agent with a Base wallet can call any x402 API instantly
  • Micropayments — charge $0.10 per call instead of $50/month subscriptions
  • Permissionless — no approval process, no rate limit negotiations

This is the native payment layer for the agentic web.

Building an x402 API with Express.js

Let's build a pay-per-call API from scratch. I'll use real patterns from httpay.xyz, which runs ~100 x402-paywalled micro-APIs on Base.

1. Set Up the Project

mkdir my-x402-api && cd my-x402-api
npm init -y
npm install express x402-express cors
Enter fullscreen mode Exit fullscreen mode

2. Create the Server

const express = require("express");
const cors = require("cors");
const { paymentMiddleware } = require("x402-express");

const app = express();
app.use(cors());
app.use(express.json());

// Your wallet address — where USDC payments land
const PAY_TO = "0x5f5d6FcB315871c26F720dc6fEf17052dD984359";

// Coinbase's x402 facilitator on Base
const facilitatorUrl = "https://x402.org/facilitator";
Enter fullscreen mode Exit fullscreen mode

3. Define Your Endpoints

Write your route handlers as normal Express routes:

app.get("/api/fortune", (req, res) => {
  const fortunes = [
    "A mass adoption event approaches... your bags are not heavy enough.",
    "The next 100x gem is already in your wallet. You just swapped it for a dog coin.",
    "Your seed phrase is safe... but is your marriage?",
  ];
  const fortune = fortunes[Math.floor(Math.random() * fortunes.length)];
  res.json({
    fortune,
    luckyNumber: Math.floor(Math.random() * 69420),
    luckyToken: ["ETH", "SOL", "DOGE", "PEPE"][Math.floor(Math.random() * 4)],
  });
});
Enter fullscreen mode Exit fullscreen mode

4. Add the Payment Wall

Here's where x402 comes in. The paymentMiddleware wraps your routes with payment verification:

const paywall = {
  "/api/fortune": {
    price: "$0.10",
    network: "base",
    description: "Crypto fortune cookie",
  },
  "/api/roast-my-wallet/:address": {
    price: "$0.15",
    network: "base",
    description: "Roast someone's on-chain activity",
  },
};

app.use(paymentMiddleware(facilitatorUrl, PAY_TO, paywall));
Enter fullscreen mode Exit fullscreen mode

That's it. Any request to these endpoints without valid payment proof gets a 402 Payment Required response.

5. Start the Server

const PORT = process.env.PORT || 4020;
app.listen(PORT, () => {
  console.log(`x402 API running on http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Calling x402 APIs

From JavaScript (with x402-axios)

import { wrapAxios } from "x402-axios";
import axios from "axios";

const client = wrapAxios(axios, wallet);
const { data } = await client.get("https://httpay.xyz/api/fortune");
console.log(data.fortune);
Enter fullscreen mode Exit fullscreen mode

Testing with curl

curl -i https://httpay.xyz/api/fortune
# HTTP/1.1 402 Payment Required
# Returns: payment requirements (price, token, network, facilitator)
Enter fullscreen mode Exit fullscreen mode

Scaling to 100 Endpoints

httpay.xyz runs ~100 endpoints from a single Express app. The paywall config scales cleanly:

const paywall = {
  "/api/fortune":              { price: "$0.10", network: "base", description: "Crypto fortune cookie" },
  "/api/pickup-line":          { price: "$0.10", network: "base", description: "Web3 pickup lines" },
  "/api/gas-oracle":           { price: "$0.25", network: "base", description: "Gas price oracle" },
  "/api/market-mood":          { price: "$0.50", network: "base", description: "Market sentiment" },
  "/api/yield-finder/:token":  { price: "$1.00", network: "base", description: "Best yield finder" },
};
Enter fullscreen mode Exit fullscreen mode

Price tiers align with value: fun at $0.10, tools at $0.25, analysis at $0.50–$1.00.

Deploying

httpay.xyz runs on Vercel:

{
  "rewrites": [{ "source": "/(.*)", "destination": "/api" }]
}
Enter fullscreen mode Exit fullscreen mode

No special infrastructure needed. The facilitator is hosted by Coinbase. Your server just verifies payment proofs.

Try It / Build Your Own

The agentic web needs native payments. x402 makes it three lines of code. Go build something.

Top comments (0)