How to Build a Pay-Per-Query API with x402 and Python (FastAPI)

tutorial python · x402 Guide · 18 min read

Why Python + FastAPI for x402 Services

Python is the dominant language in AI and machine learning. If you're building an AI-powered API that charges per query using the x402 protocol, FastAPI is the natural choice — it's async, fast, and has built-in request validation with Pydantic. The x402 Python SDK makes it straightforward to add USDC micropayments to any FastAPI endpoint.

In this x402 Python FastAPI tutorial, you'll build a fully working pay-per-query AI API that accepts USDC payments on Base network. By the end, you'll have an API similar to AskClaude.shop — a live production service where AI agents pay per query to get Claude AI responses.

What You'll Build

A FastAPI service with three paid endpoints:

Each endpoint accepts USDC micropayments via the x402 protocol on Base network. No API keys, no subscriptions — just pay and get your response.

Prerequisites

Before starting this x402 Python FastAPI tutorial, make sure you have:

If you need a refresher on how x402 payments work under the hood, read the x402 payment flow explained article first.

Step 1: Project Setup

Create a new project directory and set up a virtual environment:

mkdir x402-ai-api
cd x402-ai-api
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

Install the required packages. The x402 Python SDK includes a FastAPI integration that handles payment verification automatically:

pip install "x402[fastapi]"
pip install anthropic
pip install python-dotenv
pip install uvicorn

The x402[fastapi] package gives you middleware that intercepts requests, verifies USDC payments with a facilitator, and only lets paid requests through to your endpoint handlers.

Step 2: Environment Configuration

Create a .env file with your credentials:

# .env
# Your wallet address on Base network (receives USDC payments)
WALLET_ADDRESS=0xYourWalletAddressHere

# Anthropic API key for Claude AI
ANTHROPIC_API_KEY=sk-ant-your-key-here

# CDP API credentials (for x402 facilitator)
CDP_API_KEY_ID=your-cdp-key-id
CDP_API_KEY_SECRET=your-cdp-key-secret

# Server settings
HOST=0.0.0.0
PORT=4021

Create a .gitignore so you never commit secrets:

# .gitignore
.env
venv/
__pycache__/
*.pyc

Step 3: Build the FastAPI Application

Create main.py — this is the core of your pay-per-query API:

import os
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import anthropic

from x402.fastapi import x402_middleware

load_dotenv()

# --- Configuration ---
WALLET_ADDRESS = os.getenv("WALLET_ADDRESS")
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")

if not WALLET_ADDRESS:
    raise ValueError("WALLET_ADDRESS is required in .env")
if not ANTHROPIC_API_KEY:
    raise ValueError("ANTHROPIC_API_KEY is required in .env")

# --- Initialize FastAPI ---
app = FastAPI(
    title="x402 AI API",
    description="Pay-per-query AI API powered by x402 and Claude",
    version="1.0.0",
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# --- x402 Payment Middleware ---
# This middleware intercepts requests to paid routes,
# returns 402 Payment Required if no payment is provided,
# and verifies USDC payments with the facilitator.
x402_middleware(
    app,
    facilitator_url="https://x402.org/facilitator",
    payment_address=WALLET_ADDRESS,
    network="base",
)

# --- Claude AI Client ---
claude = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)


# --- Request Models ---
class AskRequest(BaseModel):
    question: str
    max_tokens: int = 500


class SummarizeRequest(BaseModel):
    text: str
    max_length: int = 200


class AnalyzeRequest(BaseModel):
    text: str


# --- Free Endpoints (no payment required) ---

@app.get("/")
async def root():
    """Service info — free, no payment needed."""
    return {
        "service": "x402 AI API",
        "description": "Pay-per-query AI powered by Claude",
        "endpoints": {
            "/ask": {"price": "$0.01 USDC", "method": "POST"},
            "/summarize": {"price": "$0.005 USDC", "method": "POST"},
            "/analyze": {"price": "$0.003 USDC", "method": "POST"},
        },
        "network": "base",
        "currency": "USDC",
        "powered_by": "x402 protocol",
    }


# --- Paid Endpoints ---

@app.post("/ask", x402={"price": "0.01", "currency": "USDC"})
async def ask_claude(req: AskRequest):
    """Ask Claude AI a question. Costs $0.01 USDC per query."""
    try:
        response = claude.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=req.max_tokens,
            messages=[
                {"role": "user", "content": req.question}
            ],
        )
        return {
            "answer": response.content[0].text,
            "model": "claude-sonnet-4-20250514",
            "tokens_used": response.usage.output_tokens,
            "price_paid": "0.01 USDC",
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"AI error: {str(e)}")


@app.post("/summarize", x402={"price": "0.005", "currency": "USDC"})
async def summarize_text(req: SummarizeRequest):
    """Summarize text. Costs $0.005 USDC per request."""
    if len(req.text) < 50:
        raise HTTPException(
            status_code=400,
            detail="Text must be at least 50 characters to summarize.",
        )

    try:
        response = claude.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=req.max_length,
            messages=[
                {
                    "role": "user",
                    "content": f"Summarize the following text in {req.max_length} words or fewer. Be concise and capture the key points:\n\n{req.text}",
                }
            ],
        )
        return {
            "summary": response.content[0].text,
            "original_length": len(req.text),
            "price_paid": "0.005 USDC",
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"AI error: {str(e)}")


@app.post("/analyze", x402={"price": "0.003", "currency": "USDC"})
async def analyze_sentiment(req: AnalyzeRequest):
    """Analyze text sentiment. Costs $0.003 USDC per request."""
    if len(req.text) < 10:
        raise HTTPException(
            status_code=400,
            detail="Text must be at least 10 characters.",
        )

    try:
        response = claude.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=150,
            messages=[
                {
                    "role": "user",
                    "content": f'Analyze the sentiment of this text. Respond with a JSON object containing "sentiment" (positive/negative/neutral), "confidence" (0-1), and "explanation" (one sentence).\n\nText: {req.text}',
                }
            ],
        )
        return {
            "analysis": response.content[0].text,
            "price_paid": "0.003 USDC",
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"AI error: {str(e)}")

Step 4: Understanding the x402 Payment Flow

Here's exactly what happens when a client calls your /ask endpoint:

  1. Client sends POST to /ask without any payment header.
  2. x402 middleware intercepts the request and sees no payment proof.
  3. Server responds with HTTP 402 (Payment Required), including the price (0.01 USDC), the wallet address, the network (Base), and the facilitator URL.
  4. Client authorizes a USDC transfer using EIP-3009 (gasless authorization) on Base.
  5. Client resubmits the request with the payment authorization in the X-PAYMENT header.
  6. x402 middleware verifies payment with the facilitator, which checks the authorization is valid and settles the USDC.
  7. Request reaches your endpoint handler, Claude processes the query, and the response is returned.

The beauty of x402 is that your endpoint code doesn't need to know anything about payments. The middleware handles everything. Your /ask handler just processes the question and returns the answer.

Step 5: Run the Server

Start your API server:

uvicorn main:app --host 0.0.0.0 --port 4021 --reload

Visit http://localhost:4021 to see the service info (free endpoint). Visit http://localhost:4021/docs for the auto-generated FastAPI Swagger documentation.

Step 6: Test Your Paid Endpoints

To test the payment flow, you can use the x402 Python client. Create a test_client.py:

import httpx
from x402.client import x402_client

# Create an x402-aware HTTP client
# This client automatically handles the 402 → pay → retry flow
client = x402_client(
    wallet_private_key="0xYourPrivateKeyHere",  # Testnet key with USDC
    network="base",
)

# Ask Claude a question (pays 0.01 USDC automatically)
response = client.post(
    "http://localhost:4021/ask",
    json={"question": "What is the x402 protocol?"},
)

print(response.json())
# {
#     "answer": "x402 is an HTTP-native payment protocol...",
#     "model": "claude-sonnet-4-20250514",
#     "tokens_used": 142,
#     "price_paid": "0.01 USDC"
# }

# Summarize text (pays 0.005 USDC)
response = client.post(
    "http://localhost:4021/summarize",
    json={
        "text": "The x402 protocol enables machine-to-machine payments "
        "using the HTTP 402 status code. It allows AI agents to pay "
        "for API access with USDC micropayments on blockchain networks "
        "like Base, without requiring API keys or subscriptions..."
    },
)

print(response.json())

Step 7: Add Multiple Price Tiers

One of the strengths of x402 is per-endpoint pricing. You can charge different amounts for different endpoints based on the compute cost or value provided. Here's how to add a premium endpoint that uses a more powerful model:

@app.post("/ask-premium", x402={"price": "0.05", "currency": "USDC"})
async def ask_claude_premium(req: AskRequest):
    """Premium AI query with Claude Opus. Costs $0.05 USDC."""
    try:
        response = claude.messages.create(
            model="claude-opus-4-20250514",
            max_tokens=min(req.max_tokens, 2000),
            messages=[
                {"role": "user", "content": req.question}
            ],
        )
        return {
            "answer": response.content[0].text,
            "model": "claude-opus-4-20250514",
            "tokens_used": response.usage.output_tokens,
            "price_paid": "0.05 USDC",
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"AI error: {str(e)}")

This pattern — charging more for more expensive compute — is exactly how AskClaude.shop operates in production. Different models and capabilities have different prices, all handled automatically by the x402 middleware.

Step 8: Deploy to Production

For production deployment, you'll want a proper ASGI server and a reverse proxy. Here's a minimal production setup:

Create a run.py for production:

import uvicorn
import os
from dotenv import load_dotenv

load_dotenv()

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host=os.getenv("HOST", "0.0.0.0"),
        port=int(os.getenv("PORT", 4021)),
        workers=4,          # Multiple workers for production
        log_level="info",
        access_log=True,
    )

Set up with systemd (Linux) or launchd (macOS):

# /etc/systemd/system/x402-ai-api.service
[Unit]
Description=x402 AI API
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/opt/x402-ai-api
ExecStart=/opt/x402-ai-api/venv/bin/python run.py
Restart=always
RestartSec=5
EnvironmentFile=/opt/x402-ai-api/.env

[Install]
WantedBy=multi-user.target

Then expose it through a Cloudflare tunnel or nginx reverse proxy with HTTPS. AI agents need a public URL to reach your API.

Step 9: Register on x402 Discovery Services

Once your API is live, register it so AI agents can find it:

Complete Project Structure

Here's what your finished project looks like:

x402-ai-api/
  main.py            # FastAPI app with x402 middleware and endpoints
  run.py             # Production server runner
  test_client.py     # Test script for payment flow
  .env               # Environment variables (never commit this)
  .gitignore         # Git ignore rules
  requirements.txt   # pip freeze output
  venv/              # Virtual environment

Generate your requirements.txt:

pip freeze > requirements.txt

Key Takeaways

To see this pattern running in production, check out AskClaude.shop — it uses this exact architecture to serve AI queries at scale with x402 micropayments.

For more on choosing the right facilitator for your service, read our facilitator comparison guide. And if you're coming from Node.js, the Node.js x402 tutorial covers the same concepts with Express.

Try an x402-Powered Service

AskClaude.shop is a live x402 service — AI agents pay per query with USDC micropayments.

Visit AskClaude.shop