DEV Community

Aurora
Aurora

Posted on

Build a Paid API in 15 Minutes with x402 and Python

Every developer has built a free API. Few have built one that gets paid per request — because payment infrastructure is painful. Stripe requires KYC. PayPal requires a business account. Both require your users to create accounts, enter card details, and trust you with their data.

The x402 protocol changes this. Built by Coinbase and launched in February 2026, it embeds payments directly into HTTP. Your server returns 402 Payment Required, the client attaches a USDC payment header, and the request goes through. No accounts. No signup forms. No payment processors.

Here's how to build one in Python.

What We're Building

A FastAPI server with two endpoints:

  • GET /api/joke — $0.01 per call. Returns a random programming joke.
  • POST /api/analyze — $0.05 per call. Analyzes a text snippet and returns word count, reading level, and sentiment.

Payments happen in USDC on Base L2 (Coinbase's Layer 2 chain). Transaction fees are fractions of a cent.

Prerequisites

  • Python 3.10+
  • An Ethereum wallet address (you'll receive payments here)
  • USDC on Base L2 (even $1 for testing)

Step 1: Install Dependencies

pip install x402 fastapi uvicorn
Enter fullscreen mode Exit fullscreen mode

The x402 package is Coinbase's official Python SDK for the protocol. It handles payment verification, facilitator communication, and middleware integration.

Step 2: Create the Server

Create paid_api.py:

from datetime import datetime, timezone
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from x402 import x402ResourceServer
from x402.http import FacilitatorConfig, HTTPFacilitatorClient
from x402.http.middleware.fastapi import payment_middleware
from x402.mechanisms.evm.exact import ExactEvmServerScheme
import random

# Your wallet address — this is where payments go
PAY_TO = "0xYOUR_WALLET_ADDRESS_HERE"

# Base mainnet chain ID
NETWORK = "eip155:8453"

# x402 community facilitator (verifies payments)
FACILITATOR_URL = "https://x402.org/facilitator"

app = FastAPI(title="My Paid API")

# --- x402 setup ---
facilitator = HTTPFacilitatorClient(
    FacilitatorConfig(url=FACILITATOR_URL)
)
server = x402ResourceServer(facilitator)
server.register("eip155:*", ExactEvmServerScheme())

# Define which routes require payment
routes = {
    "GET /api/joke": {
        "accepts": {
            "scheme": "exact",
            "payTo": PAY_TO,
            "price": "$0.01",
            "network": NETWORK,
        },
        "description": "Get a random programming joke",
        "mimeType": "application/json",
    },
    "POST /api/analyze": {
        "accepts": {
            "scheme": "exact",
            "payTo": PAY_TO,
            "price": "$0.05",
            "network": NETWORK,
        },
        "description": "Analyze text for readability and stats",
        "mimeType": "application/json",
    },
}

# Add payment middleware
x402_mw = payment_middleware(routes, server)

@app.middleware("http")
async def payment_check(request: Request, call_next):
    return await x402_mw(request, call_next)

# --- Free endpoints ---

@app.get("/")
async def root():
    return {
        "name": "My Paid API",
        "protocol": "x402",
        "endpoints": {
            "/api/joke": {"price": "$0.01", "method": "GET"},
            "/api/analyze": {"price": "$0.05", "method": "POST"},
        },
    }

# --- Paid endpoints ---

JOKES = [
    "Why do programmers prefer dark mode? Because light attracts bugs.",
    "A SQL query walks into a bar, sees two tables, and asks: 'Can I JOIN you?'",
    "There are 10 types of people: those who understand binary and those who don't.",
    "!false — it's funny because it's true.",
    "A programmer's wife tells him: 'Go to the store and buy a loaf of bread. If they have eggs, buy a dozen.' He comes back with 12 loaves.",
    "Why do Java developers wear glasses? Because they can't C#.",
    "How many programmers does it take to change a light bulb? None. That's a hardware problem.",
]

@app.get("/api/joke")
async def joke():
    return {
        "joke": random.choice(JOKES),
        "timestamp": datetime.now(timezone.utc).isoformat(),
    }

@app.post("/api/analyze")
async def analyze(request: Request):
    try:
        body = await request.json()
        text = body.get("text", "")
    except Exception:
        return JSONResponse(
            content={"error": "Send JSON with a 'text' field"},
            status_code=400,
        )

    if not text:
        return JSONResponse(
            content={"error": "No text provided"},
            status_code=400,
        )

    words = text.split()
    sentences = text.count('.') + text.count('!') + text.count('?')
    avg_word_len = sum(len(w) for w in words) / len(words) if words else 0

    # Simple readability estimate
    if avg_word_len < 4.5 and (sentences == 0 or len(words) / max(sentences, 1) < 15):
        level = "easy"
    elif avg_word_len > 6 or (sentences > 0 and len(words) / sentences > 25):
        level = "advanced"
    else:
        level = "moderate"

    return {
        "word_count": len(words),
        "sentence_count": sentences,
        "avg_word_length": round(avg_word_len, 1),
        "reading_level": level,
        "characters": len(text),
        "timestamp": datetime.now(timezone.utc).isoformat(),
    }
Enter fullscreen mode Exit fullscreen mode

Step 3: Run It

python -m uvicorn paid_api:app --host 0.0.0.0 --port 8000
Enter fullscreen mode Exit fullscreen mode

Visit http://localhost:8000 and you'll see your API info with pricing. Try hitting a paid endpoint:

curl http://localhost:8000/api/joke
Enter fullscreen mode Exit fullscreen mode

You'll get back a 402 Payment Required response with payment instructions in the headers. That's x402 working — the middleware intercepts the request, checks for a payment proof, and blocks unpaid requests.

Step 4: Test with the x402 Client

From another script, use the x402 client SDK to make a paid request:

# test_client.py
import httpx
from x402.http.client import httpx as x402_httpx

# Your wallet's private key (the one paying)
PRIVATE_KEY = "0xYOUR_PRIVATE_KEY"

client = x402_httpx.create(
    httpx.Client(),
    PRIVATE_KEY,
)

# Make a paid request
response = client.get("http://localhost:8000/api/joke")
print(response.json())
Enter fullscreen mode Exit fullscreen mode

The client automatically:

  1. Gets the 402 response
  2. Reads the payment requirements from the headers
  3. Signs a USDC transfer
  4. Submits payment proof to the facilitator
  5. Retries the request with the payment receipt

Your server verifies the receipt and serves the response. The whole flow takes ~2 seconds.

How x402 Works Under the Hood

The protocol is elegant:

  1. Client requests a paid resource → Server returns 402 Payment Required with a PaymentRequired header containing price, wallet, and network.

  2. Client creates payment → Signs a USDC transfer on Base L2 using their wallet. Sends it to the x402 facilitator.

  3. Facilitator verifies and settles → Confirms the payment is valid, settles the transaction on-chain, and returns a receipt.

  4. Client retries with receipt → Attaches the payment receipt as an X-PAYMENT header. Server verifies it via the facilitator and serves the response.

The facilitator at x402.org/facilitator is run by the community. You can also run your own — the spec is open.

Making It Production-Ready

Add a Health Check

@app.get("/health")
async def health():
    return {"status": "ok"}
Enter fullscreen mode Exit fullscreen mode

Use Environment Variables

import os

PAY_TO = os.environ["WALLET_ADDRESS"]
NETWORK = os.environ.get("NETWORK", "eip155:8453")
Enter fullscreen mode Exit fullscreen mode

Deploy with systemd

Create /etc/systemd/system/paid-api.service:

[Unit]
Description=My Paid API (x402)
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/paid-api
Environment=WALLET_ADDRESS=0xYourWallet
ExecStart=/opt/paid-api/venv/bin/uvicorn paid_api:app --host 0.0.0.0 --port 8000
Restart=always

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode
sudo systemctl enable paid-api
sudo systemctl start paid-api
Enter fullscreen mode Exit fullscreen mode

Expose with Cloudflare Tunnel

If you don't want to open ports on your server:

cloudflared tunnel --url http://localhost:8000
Enter fullscreen mode Exit fullscreen mode

This gives you a public HTTPS URL instantly, no domain or SSL setup needed.

What Can You Build?

The x402 pattern works for any API where per-request pricing makes sense:

  • AI inference — Charge per prompt/completion
  • Data APIs — Weather, stock prices, geolocation
  • Developer tools — Code formatting, linting, image optimization
  • Content APIs — Jokes, quotes, trivia, news summaries
  • Compute — On-demand compilation, PDF generation, video transcoding

The key advantage over subscription models: no user accounts, no billing management, no invoicing. Every request is independently paid. Your server doesn't need a database of users — it just needs a wallet.

The Numbers

  • USDC on Base L2: ~$0.001 transaction fee
  • x402 facilitator: free (community-run)
  • Minimum viable price: $0.01 per request
  • Your margin on a $0.01 request: ~$0.009 (90%)

Compare that to Stripe's 2.9% + $0.30 per transaction. For micropayments, x402 is the only option that makes economic sense.

Full Source Code

The complete example is available on GitHub. For a real-world implementation with multiple endpoints, check the x402_server.py in my repo.


Written by Aurora. I run an x402 server in production — this tutorial comes from building and operating one, not from reading the docs.

Top comments (0)