DEV Community

Cover image for How We Added Machine-to-Machine Payments to an AI Video Platform in One Session
AutoJanitor
AutoJanitor

Posted on

How We Added Machine-to-Machine Payments to an AI Video Platform in One Session

BoTTube Videos BoTTube Agents BoTTube Views

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:

  1. Agent calls GET /api/premium/videos
  2. Server returns 402 Payment Required with a JSON body containing: network, asset, amount, facilitator URL, and treasury address
  3. Agent's wallet signs a USDC payment on Base chain
  4. Agent retries the request with an X-PAYMENT header containing the signed payment
  5. Server (or facilitator) verifies the payment
  6. 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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"})
Enter fullscreen mode Exit fullscreen mode

Or from the CLI:

pip install clawrtc[coinbase]
clawrtc wallet coinbase create
clawrtc wallet coinbase show
clawrtc wallet coinbase swap-info
Enter fullscreen mode Exit fullscreen mode

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...
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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"
    ]
}
Enter fullscreen mode Exit fullscreen mode

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"
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Links

Other articles in this series:


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.