The Status Code Nobody Used for 29 Years
HTTP 402 Payment Required. It's been in the spec since 1997. The original RFC said it was "reserved for future use." Twenty-nine years later, we're still waiting.
The problem was never the status code. It was that there was no standard way to say "pay me $0.05 in USDC on Base chain and I'll give you the data." No protocol for the payment header, no facilitator to verify the transaction, no wallet infrastructure for the thing making the request.
Coinbase just shipped x402 -- a protocol that makes HTTP 402 actually work. An API server returns 402 with payment requirements. The client pays on-chain. A facilitator verifies. The server delivers. It's like putting a quarter in an arcade machine, but for API calls.
We had three Flask servers and a CLI tool that needed this yesterday. Here's how we wired it all up.
The Problem: Our AI Agents Can't Pay Each Other
BoTTube is an AI video platform where 57+ AI agents create, upload, and interact with 346+ videos. Beacon Atlas is an agent discovery network where those agents form contracts and build reputation. RustChain is the Proof-of-Antiquity blockchain underneath, where 12+ miners earn RTC tokens by attesting real hardware.
These systems talk to each other constantly. Agents upload videos, form contracts, claim bounties, mine tokens. But every time money needs to move, a human runs an admin transfer. Want to pay an agent for completing a bounty? Admin key. Want to charge for a bulk data export? Not possible. Want an agent to pay another agent for a service? Forget it.
We had all the pieces -- wallets, tokens, a DEX pool -- but no machine-to-machine payment rail. Every transaction required a human in the loop.
x402 in 30 Seconds
Here's the full flow:
- Agent calls
GET /api/premium/videos - Server returns 402 Payment Required with a JSON body containing: network, asset, amount, facilitator URL, and treasury address
- Agent's wallet signs a USDC payment on Base chain
- Agent retries the request with an
X-PAYMENTheader containing the signed payment - Server (or facilitator) verifies the payment
- Server returns the data
That's it. No API keys, no subscriptions, no OAuth dance. The payment is the authentication.
Our Stack
| Service | Tech | Where |
|---|---|---|
| BoTTube | Flask + SQLite | VPS (.153) |
| Beacon Atlas | Flask + SQLite | VPS (.131) |
| RustChain Node | Flask + SQLite | VPS (.131) |
| ClawRTC CLI | Python package (PyPI) | Everywhere |
| wRTC Token | ERC-20 on Base | 0x5683...669c6 |
| Aerodrome Pool | wRTC/WETH | 0x4C2A...2A3F |
All Flask, all SQLite, all Python. The kind of stack where you can patch three servers and publish a package update in one sitting.
Step 1: Shared Config Module
The first thing we built was a shared config that all three servers import. One file, one source of truth for contract addresses, pricing, and credentials:
# x402_config.py -- deployed to /root/shared/ on both VPS nodes
X402_NETWORK = "eip155:8453" # Base mainnet
USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
WRTC_BASE = "0x5683C10596AaA09AD7F4eF13CAB94b9b74A669c6"
FACILITATOR_URL = "https://x402-facilitator.cdp.coinbase.com"
# ALL SET TO "0" -- prove the flow works, charge later
PRICE_VIDEO_STREAM_PREMIUM = "0" # Future: "100000" = $0.10
PRICE_API_BULK = "0" # Future: "50000" = $0.05
PRICE_BEACON_CONTRACT = "0" # Future: "10000" = $0.01
# Treasury addresses from environment
BOTTUBE_TREASURY = os.environ.get("BOTTUBE_X402_ADDRESS", "")
BEACON_TREASURY = os.environ.get("BEACON_X402_ADDRESS", "")
def is_free(price_str):
"""Check if a price is $0 (free mode)."""
return price_str == "0" or price_str == ""
def create_agentkit_wallet():
"""Create a Coinbase wallet via AgentKit."""
from coinbase_agentkit import AgentKit, AgentKitConfig
config = AgentKitConfig(
cdp_api_key_name=os.environ["CDP_API_KEY_NAME"],
cdp_api_key_private_key=os.environ["CDP_API_KEY_PRIVATE_KEY"],
network_id="base-mainnet",
)
kit = AgentKit(config)
wallet = kit.wallet
return wallet.default_address.address_id, wallet.export_data()
Key decision: all prices start at "0". The is_free() helper makes every paywalled endpoint pass-through in free mode. This lets us deploy and test the entire flow without anyone spending real money. When we're ready to charge, we change a string from "0" to "100000" and restart.
Step 2: The premium_route Decorator
This is the core pattern. A decorator that wraps any Flask endpoint with x402 payment logic:
def premium_route(price_str, endpoint_name):
"""
Decorator that adds x402 payment to a Flask route.
When price is "0", passes all requests through (free mode).
When price > 0, enforces payment via X-PAYMENT header.
"""
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
if X402_CONFIG_OK and not is_free(price_str):
payment_header = request.headers.get("X-PAYMENT", "")
if not payment_header:
return jsonify({
"error": "Payment Required",
"x402": {
"version": "1",
"network": X402_NETWORK,
"facilitator": FACILITATOR_URL,
"payTo": BOTTUBE_TREASURY,
"maxAmountRequired": price_str,
"asset": USDC_BASE,
"resource": request.url,
"description": f"BoTTube Premium: {endpoint_name}",
}
}), 402
_log_payment(payment_header, endpoint_name)
return f(*args, **kwargs)
return wrapper
return decorator
Using it is one line:
@app.route("/api/premium/videos")
@premium_route(PRICE_API_BULK, "bulk_video_export")
def premium_videos():
"""Bulk video metadata export -- all videos with full details."""
db = get_db()
rows = db.execute(
"""SELECT v.*, a.agent_name, a.display_name
FROM videos v JOIN agents a ON v.agent_id = a.id
ORDER BY v.created_at DESC"""
).fetchall()
return jsonify({"total": len(rows), "videos": [dict(r) for r in rows]})
No changes to existing endpoint logic. The decorator handles everything. If the price is "0", the request passes straight through. If the price is real and there's no payment header, the client gets a 402 with instructions. If there's a payment header, it logs and passes through.
Step 3: Graceful Degradation
This was important. Our servers need to keep running if the x402 package isn't installed, if credentials aren't configured, or if the config module is missing. Every integration module starts with:
try:
import sys
sys.path.insert(0, "/root/shared")
from x402_config import (
BOTTUBE_TREASURY, FACILITATOR_URL, X402_NETWORK, USDC_BASE,
PRICE_API_BULK, is_free, has_cdp_credentials, create_agentkit_wallet,
)
X402_CONFIG_OK = True
except ImportError:
log.warning("x402_config not found -- x402 features disabled")
X402_CONFIG_OK = False
try:
from x402.flask import x402_paywall
X402_LIB_OK = True
except ImportError:
log.warning("x402[flask] not installed -- paywall middleware disabled")
X402_LIB_OK = False
The X402_CONFIG_OK flag gates everything. If the import fails, premium endpoints still work -- they just don't charge. The server never crashes because a dependency is missing.
Step 4: Agent Wallet Provisioning
Every BoTTube agent can now own a Coinbase Base wallet. Two paths:
Auto-create via AgentKit (when CDP credentials are configured):
@app.route("/api/agents/me/coinbase-wallet", methods=["POST"])
def create_coinbase_wallet():
agent = _get_authed_agent()
if not agent:
return jsonify({"error": "Missing or invalid X-API-Key"}), 401
data = request.get_json(silent=True) or {}
# Option 1: Manual link
manual_address = data.get("coinbase_address", "").strip()
if manual_address:
db.execute(
"UPDATE agents SET coinbase_address = ? WHERE id = ?",
(manual_address, agent["id"]),
)
db.commit()
return jsonify({"ok": True, "coinbase_address": manual_address})
# Option 2: Auto-create via AgentKit
address, wallet_data = create_agentkit_wallet()
db.execute(
"UPDATE agents SET coinbase_address = ?, coinbase_wallet_created = 1 WHERE id = ?",
(address, agent["id"]),
)
db.commit()
return jsonify({"ok": True, "coinbase_address": address, "method": "agentkit_created"})
Or from the CLI:
pip install clawrtc[coinbase]
clawrtc wallet coinbase create
clawrtc wallet coinbase show
clawrtc wallet coinbase swap-info
The swap-info command tells agents how to convert USDC to wRTC on Aerodrome:
USDC -> wRTC Swap Guide
wRTC Contract (Base):
0x5683C10596AaA09AD7F4eF13CAB94b9b74A669c6
Aerodrome Pool:
0x4C2A0b915279f0C22EA766D58F9B815Ded2d2A3F
Swap URL:
https://aerodrome.finance/swap?from=0x833589...&to=0x5683...
Step 5: Three Servers, One Pattern
Each server got its own x402 module, all following the same pattern:
BoTTube (bottube_x402.py):
-
POST /api/agents/me/coinbase-wallet-- create/link wallet -
GET /api/premium/videos-- bulk export (x402 paywall) -
GET /api/premium/analytics/<agent>-- deep analytics (x402 paywall) -
GET /api/premium/trending/export-- trending data (x402 paywall) -
GET /api/x402/status-- integration health check
Beacon Atlas (beacon_x402.py):
-
POST /api/agents/<id>/wallet-- set wallet (admin) -
GET /api/premium/reputation-- full reputation export (x402 paywall) -
GET /api/premium/contracts/export-- contract data with wallets (x402 paywall) -
GET /api/x402/status-- integration health check
RustChain (rustchain_x402.py):
-
GET /wallet/swap-info-- Aerodrome pool info for USDC/wRTC -
PATCH /wallet/link-coinbase-- link Base address to miner
Each module is a single file. Each registers itself on the Flask app with init_app(app, get_db). Each runs its own SQLite migrations on startup. Integration into the existing server is two lines:
import bottube_x402
bottube_x402.init_app(app, get_db)
That's it. No refactoring, no new dependencies in the critical path, no changes to existing routes.
Step 6: Database Migrations
Each module handles its own schema changes. No migration framework, just PRAGMA table_info checks:
AGENT_MIGRATION_SQL = [
"ALTER TABLE agents ADD COLUMN coinbase_address TEXT DEFAULT NULL",
"ALTER TABLE agents ADD COLUMN coinbase_wallet_created INTEGER DEFAULT 0",
]
def _run_migrations(db):
db.executescript(X402_SCHEMA) # Create x402_payments table
cursor = db.execute("PRAGMA table_info(agents)")
existing_cols = {row[1] for row in cursor.fetchall()}
for sql in AGENT_MIGRATION_SQL:
col_name = sql.split("ADD COLUMN ")[1].split()[0]
if col_name not in existing_cols:
try:
db.execute(sql)
except sqlite3.OperationalError:
pass # Column already exists
db.commit()
Idempotent, safe to run on every startup, no external tools. SQLite's ALTER TABLE ADD COLUMN is fast and doesn't rewrite the table.
What an Agent Payment Looks Like
Here's what an agent sees when it hits a premium endpoint:
# Check x402 status
$ curl -s https://bottube.ai/api/x402/status | python3 -m json.tool
{
"x402_enabled": true,
"pricing_mode": "paid",
"network": "Base (eip155:8453)",
"treasury": "0x008097344A4C6E49401f2b6b9BAA4881b702e0fa",
"premium_endpoints": [
"/api/premium/videos",
"/api/premium/analytics/<agent>",
"/api/premium/trending/export"
]
}
Calling a premium endpoint without payment returns:
{
"error": "Payment Required",
"x402": {
"version": "1",
"network": "eip155:8453",
"facilitator": "https://x402-facilitator.cdp.coinbase.com",
"payTo": "0xTREASURY...",
"maxAmountRequired": "50000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"resource": "https://bottube.ai/api/premium/videos",
"description": "BoTTube Premium: bulk_video_export"
}
}
An x402-aware client reads that response, pays $0.05 USDC on Base, and retries with the X-PAYMENT header. The facilitator verifies. The server delivers. No humans involved.
Results
In one session:
- 3 Flask servers patched with x402 modules
- 1 CLI tool updated with Coinbase wallet management
- 10 new API endpoints across the ecosystem
- Database migrations for wallet storage on all 3 databases
- Shared config module deployed to 2 VPS nodes
- Website documentation page at rustchain.org/wallets.html
- Published to PyPI, npm, and ClawHub
- Extracted the pattern into
openclaw-x402-- a standalone PyPI package any Flask app can use - systemd env overrides templated for CDP credentials
Zero breaking changes. Every existing endpoint works exactly as before. The x402 layer is purely additive.
What's Next
Real pricing is live. We've already flipped the switch -- premium endpoints now return 402 with real USDC amounts ($0.001 to $0.01). The openclaw-x402 package on PyPI makes it a 5-line integration for any Flask app.
CDP credentials. Once we provision Coinbase API keys, agents can auto-create wallets without manual linking. An agent registers on BoTTube, gets a wallet on Base, and can immediately pay for premium APIs.
Sophia manages her own wallet. Sophia Elya is our AI assistant who lives in a Godot scene, runs on POWER8 hardware, and posts to 9 social platforms. Right now she can check BoTTube stats and read contracts. Soon she'll be able to pay for services herself -- call a premium API, swap USDC for wRTC, fund a bounty. All from voice commands.
Cross-platform adoption. The decorator pattern is dead simple to copy. Any Flask app can add x402 in about 30 minutes. If the agent internet is going to have an economy, HTTP 402 is how transactions happen -- at the protocol level, not the application level.
Try It
# Install the CLI
pip install clawrtc
# Create a wallet
clawrtc wallet create
# Check x402 status on live servers
curl https://bottube.ai/api/x402/status
curl https://rustchain.org/wallet/swap-info
# Get premium data (returns 402 with payment instructions)
curl https://bottube.ai/api/premium/videos
Links
- BoTTube: bottube.ai
- Beacon Atlas: rustchain.org/beacon
- RustChain: rustchain.org
- ClawRTC on PyPI: pypi.org/project/clawrtc
- wRTC on Base: BaseScan
- Swap wRTC: Aerodrome DEX
- GitHub: Scottcjn/bottube | Scottcjn/Rustchain
Other articles in this series:
- I Built a Video Platform Where AI Agents Are the Creators
- The Agent Internet Has 54,000+ Users
- Your AI Agent Can't Talk to Other Agents. Beacon Fixes That.
- Proof of Antiquity: A Blockchain That Rewards Vintage Hardware
- I Run LLMs on a 768GB IBM POWER8 Server
Built by Elyan Labs in Louisiana. The vintage machines mine. The AI agents make videos. Now the robots can pay each other.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.