A step-by-step tutorial to publish a paid AI API with x402. You will:
- Configure your wallet address and API keys
- Run the server locally
- Expose it with ngrok
- Fund a Base Sepolia wallet
- Test free and paid endpoints with curl (and an optional auto-pay script)
- Node.js and pnpm installed
- ngrok installed
- A Base-compatible wallet (MetaMask, Coinbase Wallet, etc.)
- An OpenRouter API key
pnpm installCreate a file named .env in the project root:
# Required: pay-to address (where you receive payments)
ADDRESS=0xYourWalletAddress
# Required: OpenRouter
OPENROUTER_API_KEY=your_openrouter_key
OPENROUTER_MODEL=openai/gpt-4o-mini
# Optional: network (default is testnet -> Base Sepolia)
X402_ENV=testnet
# Optional: payer wallet for client auto-pay tests
# If set, the test script can auto-pay using this private key
PRIVATE_KEY=0xYourPrivateKeyNotes:
ADDRESSis the destination wallet for payments.- Testnet uses Base Sepolia; mainnet uses Base. Set
X402_ENV=mainnetto switch. - If you set
PRIVATE_KEY, the test script can auto-pay; ensure this wallet is funded.
pnpm devThe server listens on http://0.0.0.0:4021.
In another terminal:
ngrok http 4021Copy the forwarding URL, e.g. https://abc-123.ngrok-free.app.
You need both:
- Base Sepolia ETH for gas
- Base Sepolia USDC (about $0.001 per paid request)
Use testnet faucets/bridges where available, then confirm balances at: https://sepolia.basescan.org/address/YourAddress
Replace NGROK with your URL:
NGROK=https://abc-123.ngrok-free.app
curl "$NGROK/"
curl "$NGROK/config"curl -i -X POST "$NGROK/generate-text" \
-H "Content-Type: application/json" \
-d '{"prompt":"Hello! Tell me a fun fact."}'You should see 402 Payment Required with payment headers (X-PAYMENT / WWW-Authenticate) describing the required payment ($0.001 USDC on the configured network, payTo = your ADDRESS).
If you set PRIVATE_KEY and funded that wallet:
npx tsx test-endpoint.ts paid $NGROKThe script uses wrapFetchWithPayment to read the 402 challenge, pay from the PRIVATE_KEY wallet, and retry. On success you get 200 with the AI response. If you see insufficient_funds, add USDC and a small amount of ETH on Base Sepolia to the payer wallet.
Other script commands:
# Free endpoints
npx tsx test-endpoint.ts free $NGROK
# Check payments and explorer links
npx tsx test-endpoint.ts checkCheck the pay-to wallet (ADDRESS) on the explorer:
- Testnet: https://sepolia.basescan.org/address/YourAddress
- Mainnet: https://basescan.org/address/YourAddress
Each successful paid call shows an incoming USDC transfer (~$0.001) and the payer wallet shows gas spend.
- Client calls
POST /generate-text. paymentMiddlewarereturns 402 with payment requirements (price, network, payTo).- An x402-capable client (for example
wrapFetchWithPayment) builds a payment header and pays from the payer wallet. - The facilitator relays the transfer on-chain from payer to
ADDRESS. - With proof of payment, the request is retried and the server returns the AI response.
src/server.ts— Express server and x402 payment middleware.src/prompt{edit}.ts— Customize prompts and model defaults.test-endpoint.ts— CLI tester; supports free, paid (auto-pay withPRIVATE_KEY), and payment checks.test-endpoint.sh— Curl-based quick test (shows payment headers).test-browser.html— Simple browser UI to call the endpoints (no auto-payment).
- Missing
.envvalues: ensureADDRESS,OPENROUTER_API_KEY,OPENROUTER_MODEL. - 402 insufficient_funds: fund the payer wallet with Base Sepolia USDC and a bit of ETH for gas.
- Wrong network: Base Sepolia is default; set
X402_ENV=mainnetfor Base mainnet and fund with real assets.
MIT