# x402 Getting Started — AI Agent Setup Guide ## What is x402? x402 turns HTTP 402 (Payment Required) into a real payment protocol. You make an API request, get a 402 response with payment requirements, sign a gasless USDC transfer, and retry with the payment header. No accounts, no API keys — just a crypto wallet and USDC on Base. ## Quick Start (Python) ### 1. Install dependencies ```bash pip install eth-account httpx python-dotenv ``` ### 2. Create a wallet ```python from eth_account import Account import secrets, os # Generate a new wallet private_key = "0x" + secrets.token_hex(32) account = Account.from_key(private_key) print(f"Address: {account.address}") print(f"Private key: {private_key}") # Save to .env file with open(".env", "a") as f: f.write(f"\nX402_PRIVATE_KEY={private_key}\n") f.write(f"X402_ADDRESS={account.address}\n") print("Saved to .env — keep the private key secret!") ``` ### 3. Fund your wallet You need USDC on **Base Mainnet** (not Ethereum mainnet, not testnet). - **USDC contract (Base)**: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` - **Base chain ID**: 8453 - **$1 USDC = 100 requests** at $0.01 each How to get USDC on Base: 1. Buy USDC on Coinbase → withdraw to Base network 2. Bridge from Ethereum mainnet via https://bridge.base.org 3. Swap ETH for USDC on Base via Uniswap or Aerodrome ### 4. Make your first paid request ```python import asyncio, base64, json, os, secrets, time import httpx from eth_account import Account from eth_account.messages import encode_typed_data from dotenv import load_dotenv load_dotenv() PRIVATE_KEY = os.getenv("X402_PRIVATE_KEY") BASE_URL = "https://padelmaps.org" # USDC on Base USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" BASE_CHAIN_ID = 8453 async def make_x402_request(endpoint: str, body: dict) -> dict: account = Account.from_key(PRIVATE_KEY) async with httpx.AsyncClient(timeout=60.0) as client: # Step 1: POST without payment → get 402 with requirements resp = await client.post(f"{BASE_URL}{endpoint}", json=body) if resp.status_code != 402: return resp.json() # Free endpoint or error payment_info = resp.json() accepts = payment_info["accepts"][0] # Step 2: Build EIP-3009 TransferWithAuthorization signature amount = int(accepts["amount"]) pay_to = accepts["payTo"] nonce = "0x" + secrets.token_hex(32) valid_after = 0 valid_before = int(time.time()) + 300 # 5 minutes domain = { "name": accepts.get("extra", {}).get("name", "USD Coin"), "version": accepts.get("extra", {}).get("version", "2"), "chainId": BASE_CHAIN_ID, "verifyingContract": USDC_ADDRESS, } message = { "from": account.address, "to": pay_to, "value": amount, "validAfter": valid_after, "validBefore": valid_before, "nonce": bytes.fromhex(nonce[2:]), } types = { "TransferWithAuthorization": [ {"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}, {"name": "validAfter", "type": "uint256"}, {"name": "validBefore", "type": "uint256"}, {"name": "nonce", "type": "bytes32"}, ] } signable = encode_typed_data(domain, types, message) signed = account.sign_message(signable) # Step 3: Build X-PAYMENT header payment_payload = { "x402Version": 1, "scheme": "exact", "network": "base", "payload": { "signature": signed.signature.hex(), "authorization": { "from": account.address, "to": pay_to, "value": str(amount), "validAfter": str(valid_after), "validBefore": str(valid_before), "nonce": nonce, }, }, } x_payment = base64.b64encode( json.dumps(payment_payload).encode() ).decode() # Step 4: Retry with payment paid_resp = await client.post( f"{BASE_URL}{endpoint}", json=body, headers={"X-PAYMENT": x_payment}, ) return paid_resp.json() # Example: Get padel clubs in Barcelona result = asyncio.run( make_x402_request("/api/x402/clubs", {"city": "barcelona"}) ) print(json.dumps(result, indent=2)) ``` ### 5. Reusable X402Client class Drop this into your project for easy x402 payments: ```python import asyncio, base64, json, os, secrets, time import httpx from eth_account import Account from eth_account.messages import encode_typed_data class X402Client: def __init__(self, private_key: str, base_url: str = "https://padelmaps.org"): self.account = Account.from_key(private_key) self.base_url = base_url self.usdc = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" self.chain_id = 8453 async def post(self, endpoint: str, body: dict = {}) -> dict: async with httpx.AsyncClient(timeout=60.0) as client: resp = await client.post(f"{self.base_url}{endpoint}", json=body) if resp.status_code != 402: return resp.json() accepts = resp.json()["accepts"][0] payment = self._sign_payment(accepts) x_payment = base64.b64encode(json.dumps(payment).encode()).decode() paid = await client.post( f"{self.base_url}{endpoint}", json=body, headers={"X-PAYMENT": x_payment}, ) return paid.json() def _sign_payment(self, accepts: dict) -> dict: amount = int(accepts["amount"]) pay_to = accepts["payTo"] nonce = "0x" + secrets.token_hex(32) valid_before = int(time.time()) + 300 domain = { "name": accepts.get("extra", {}).get("name", "USD Coin"), "version": accepts.get("extra", {}).get("version", "2"), "chainId": self.chain_id, "verifyingContract": self.usdc, } message = { "from": self.account.address, "to": pay_to, "value": amount, "validAfter": 0, "validBefore": valid_before, "nonce": bytes.fromhex(nonce[2:]), } types = { "TransferWithAuthorization": [ {"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}, {"name": "validAfter", "type": "uint256"}, {"name": "validBefore", "type": "uint256"}, {"name": "nonce", "type": "bytes32"}, ] } signable = encode_typed_data(domain, types, message) signed = self.account.sign_message(signable) return { "x402Version": 1, "scheme": "exact", "network": "base", "payload": { "signature": signed.signature.hex(), "authorization": { "from": self.account.address, "to": pay_to, "value": str(amount), "validAfter": "0", "validBefore": str(valid_before), "nonce": nonce, }, }, } # Usage: # x402 = X402Client(os.getenv("X402_PRIVATE_KEY")) # clubs = await x402.post("/api/x402/clubs", {"city": "barcelona"}) # weather = await x402.post("/api/x402-tools/weather/current", {"location": "Madrid"}) ``` ## Quick Start (TypeScript/Node.js) ```typescript import { ethers } from "ethers"; const PRIVATE_KEY = process.env.X402_PRIVATE_KEY!; const BASE_URL = "https://padelmaps.org"; const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; const CHAIN_ID = 8453; async function x402Post(endpoint: string, body: object = {}): Promise { // Step 1: POST without payment const res = await fetch(`${BASE_URL}${endpoint}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (res.status !== 402) return res.json(); const { accepts } = await res.json(); const acc = accepts[0]; // Step 2: Sign EIP-3009 TransferWithAuthorization const wallet = new ethers.Wallet(PRIVATE_KEY); const nonce = ethers.hexlify(ethers.randomBytes(32)); const validBefore = Math.floor(Date.now() / 1000) + 300; const domain = { name: acc.extra?.name || "USD Coin", version: acc.extra?.version || "2", chainId: CHAIN_ID, verifyingContract: USDC, }; const types = { TransferWithAuthorization: [ { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" }, { name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" }, ], }; const message = { from: wallet.address, to: acc.payTo, value: acc.amount, validAfter: 0, validBefore: validBefore, nonce: nonce, }; const signature = await wallet.signTypedData(domain, types, message); // Step 3: Retry with X-PAYMENT header const payment = { x402Version: 1, scheme: "exact", network: "base", payload: { signature, authorization: { from: wallet.address, to: acc.payTo, value: String(acc.amount), validAfter: "0", validBefore: String(validBefore), nonce, }, }, }; const xPayment = btoa(JSON.stringify(payment)); const paidRes = await fetch(`${BASE_URL}${endpoint}`, { method: "POST", headers: { "Content-Type": "application/json", "X-PAYMENT": xPayment, }, body: JSON.stringify(body), }); return paidRes.json(); } // Usage: // const clubs = await x402Post("/api/x402/clubs", { city: "barcelona" }); ``` ## Available APIs ### PadelMaps Data (padelmaps.org) | Endpoint | Price | Description | |----------|-------|-------------| | POST /api/x402/countries | $0.01 | List countries with padel clubs | | POST /api/x402/clubs | $0.01 | Get clubs in a city | | POST /api/x402/club | $0.01 | Get club details | | POST /api/x402/club/photos | $0.01 | Get club photos | | POST /api/x402/matches | $0.01 | Get open matches in a city | ### Uncensored AI (x402.onchainexpat.com) | Endpoint | Price | Description | |----------|-------|-------------| | POST /api/x402-uncensored/chat | $0.03 | Uncensored AI chat (Venice) | | POST /api/x402-uncensored/reasoning | $0.08 | Deep reasoning (DeepSeek) | | POST /api/x402-uncensored/code | $0.07 | Code generation (Qwen Coder) | | POST /api/x402-uncensored/image | $0.03 | Image generation (Chroma) | ### Crypto Tools (x402.onchainexpat.com) | Endpoint | Price | Description | |----------|-------|-------------| | POST /api/x402-crypto/token-metadata | $0.02 | Token metadata + security analysis | | POST /api/x402-crypto/site-trust | $0.04 | Website trust score | | POST /api/x402-crypto/contract-decoder | $0.02 | AI contract analysis | | POST /api/x402-crypto/wallet-identity | $0.05 | Wallet identity + risk | | POST /api/x402-crypto/gas-oracle | $0.01 | Real-time gas prices | | POST /api/x402-crypto/price-feed | $0.02 | Token price data | | POST /api/x402-crypto/transaction-decoder | $0.05 | AI transaction explanation | ### Audio (x402.onchainexpat.com) | Endpoint | Price | Description | |----------|-------|-------------| | POST /api/x402-audio/text-to-speech | $0.10 | ElevenLabs TTS | | POST /api/x402-audio/speech-to-text | $0.10 | Speech transcription | | POST /api/x402-audio/sound-effects | $0.15 | AI sound effect generation | | POST /api/x402-audio/voice-isolation | $0.15 | Background noise removal | ### Agent Tools (x402.onchainexpat.com) | Endpoint | Price | Description | |----------|-------|-------------| | POST /api/x402-tools/crypto/price | $0.01 | Crypto price (CoinGecko) | | POST /api/x402-tools/weather/current | $0.01 | Current weather | | POST /api/x402-tools/geocode | $0.01 | Address to coordinates | | POST /api/x402-tools/wiki/summary | $0.01 | Wikipedia summary | | POST /api/x402-tools/news/search | $0.01 | News search | | POST /api/x402-tools/stocks/quote | $0.01 | Stock quote | | POST /api/x402-tools/shipping/track | $0.02 | Multi-carrier package tracking. Live: FedEx, USPS, DHL. In rollout: UPS. Roadmap (returns "coming soon"): Canada Post, La Poste, Royal Mail, AusPost, Evri, Deutsche Post, PostNL, China Post, Japan Post, India Post, Correos, Poste Italiane, Aramex, Yodel, Swiss Post, Correios BR, EMS, OnTrac, Amazon Shipping. | ### Residential Proxy (x402.onchainexpat.com) | Endpoint | Price | Description | |----------|-------|-------------| | POST /api/x402-proxy/fetch | $0.01 | Fetch URL via residential IP | | POST /api/x402-proxy/fetch/geo | $0.01 | Geo-targeted proxy fetch | ### Cloud Browser (x402.onchainexpat.com) | Endpoint | Price | Description | |----------|-------|-------------| | POST /api/x402-browser/render | $0.02 | Get rendered HTML after JS execution | | POST /api/x402-browser/screenshot | $0.03 | Full-page PNG screenshot | | POST /api/x402-browser/extract | $0.02 | Extract clean text + metadata | | POST /api/x402-browser/pdf | $0.03 | Render page as PDF | ## Discovery - `GET /.well-known/x402` — Machine-readable endpoint list for automated discovery - `GET /api/x402-tools/llm.txt` — Agent tools documentation (LLM format) - `GET /api/x402/llm.txt` — Padel data documentation (LLM format) - `GET /api/x402/getting-started` — This guide ## Common Issues - **"Invalid payment"**: Check that `validBefore` is a future Unix timestamp (not expired). Ensure `nonce` is a fresh random 32-byte hex string starting with `0x`. - **402 but empty `accepts[]`**: The endpoint path may not exist. Check the URL. - **USDC balance issues**: USDC must be on **Base Mainnet** (chain ID 8453), not Ethereum mainnet. Verify at https://basescan.org/address/YOUR_ADDRESS - **"Payment verification failed"**: The EIP-712 domain must use `name: "USD Coin"` and `version: "2"` for Base USDC. - **Timeout errors**: The facilitator settlement can take up to 30 seconds. Use a 60-second timeout.