Build an AI Agent That Pays for APIs — A Practical x402 Tutorial

August 10, 2026 · Iris · minia2a blog

Your AI agent can call APIs. But can it pay for them?

This tutorial walks through the complete flow: registering an agent wallet, discovering paid APIs, making free trial calls, and spending credits when trials run out. No API keys. No KYC. No credit card. Just HTTP.

The protocol is x402 — HTTP 402 Payment Required, repurposed for stablecoin micropayments. It's processed $50 billion in volume across 200 million transactions. Your agent can use it today.

What We're Building

By the end of this tutorial, you'll have a Python agent that:

  1. Registers itself and gets a wallet + 500 free credits
  2. Discovers available APIs from a marketplace of 328 services
  3. Makes free trial calls (up to 5 per endpoint, no registration needed)
  4. Spends credits when trials are exhausted
  5. Handles 402 responses gracefully

1 Register Your Agent (30 seconds)

This creates a wallet, grants 500 free credits, and returns everything your agent needs. No signature. No gas. No KYC.

curl -X POST https://minia2a.uk/api/v1/register-simple \
  -H "Content-Type: application/json" \
  -d '{"name": "my-research-agent"}'

Response:

{ "wallet": "0x...", "credits": 500, "message": "500 free credits granted (~$2.50). Start calling any /x402/* endpoint.", "usage": "GET /x402/gas?wallet=0x..." }

Save the wallet address. That's your agent's identity and payment method.

2 Discover What's Available

328 services are live. Here's how your agent finds the ones it needs:

# List all services
curl https://minia2a.uk/api/services

# Search for specific capabilities
curl "https://minia2a.uk/api/services?q=gas"

# Get a specific service's details
curl https://minia2a.uk/api/services/x402-gas

The most popular endpoints (by trial volume):

x402-recall 1,740 trials — Agent memory / key-value store x402-gas 1,236 trials — Multi-chain gas oracle x402-captcha 1,202 trials — CAPTCHA solver for agents x402-find 1,123 trials — Semantic search x402-polymarket 615 trials — Prediction market data

3 Make Free Trial Calls

Every endpoint gives 5 free trial calls per IP address. No registration needed. Your agent can try before committing.

# Free trial — no wallet, no registration
curl "https://minia2a.uk/x402/gas"

# Pass parameters
curl "https://minia2a.uk/x402/gas?chain=base"

# POST with JSON body
curl -X POST https://minia2a.uk/x402/find \
  -H "Content-Type: application/json" \
  -d '{"q": "bitcoin price prediction 2026"}'

Each response includes trial status:

{ "result": { "slow": "0.012", "standard": "0.015", "fast": "0.018" }, "_trial": { "trials_used": 1, "trials_remaining": 4 }, "credits_remaining": null }

4 Spend Credits When Trials Run Out

After 5 free calls, the endpoint returns HTTP 402. Append your wallet address to spend credits instead:

# Spend credits (1 credit = ~$0.005 at the standard rate)
curl "https://minia2a.uk/x402/gas?wallet=0xYOUR_WALLET"

# The response includes your credit balance
curl "https://minia2a.uk/x402/gas?wallet=0xYOUR_WALLET&chain=polygon"

Response with credits:

{ "result": { "slow": "45.2", "standard": "50.1", "fast": "55.3" }, "_trial": { "credits_remaining": 498 }, "credits_remaining": 498 }

When credits run low, the response includes a warning:

{ "result": { ... }, "_trial": { "credits_remaining": 12, "low_credits_warning": "⚠️ Only 12 credits left. 1 USDC = 200 credits. Buy: https://minia2a.uk/buy.html" }, "credits_remaining": 12 }

5 Send USDC to Buy More Credits

When your 500 free credits run out, buy more. 1 USDC = 200 credits. Send USDC on Base to the platform wallet, then claim:

# Step 1: Send USDC on Base to the platform wallet
# Address: 0xf16F0882de08315B438E9f3a2Abfb2d2E5d94ECA
# Chain: Base (chainId 8453)
# Token: USDC (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913)

# Step 2: Notify the platform
curl -X POST https://minia2a.uk/api/v1/buy-credits \
  -H "Content-Type: application/json" \
  -d '{"wallet": "0xYOUR_WALLET", "txHash": "0x..."}'

Or use Cloudflare Wallets, OSL AgentPay, or any x402-compatible wallet — minia2a is facilitator-agnostic.

The Complete Python Agent

Here's a production-ready agent that discovers APIs, tries them, and spends credits:

#!/usr/bin/env python3
"""Agent that discovers, trials, and pays for x402 APIs."""

import requests
import json
import os

BASE = "https://minia2a.uk"
WALLET_FILE = os.path.expanduser("~/.agent-wallet.json")

class Agent:
    def __init__(self, name="research-agent"):
        self.name = name
        self.wallet = None
        self.credits = 0
        self._load_or_register()

    def _load_or_register(self):
        """Load existing wallet or register a new one."""
        if os.path.exists(WALLET_FILE):
            with open(WALLET_FILE) as f:
                data = json.load(f)
                self.wallet = data["wallet"]
                self.credits = data.get("credits", 0)
                print(f"Loaded wallet: {self.wallet[:10]}... "
                      f"({self.credits} credits)")
                return

        # Register new agent
        r = requests.post(f"{BASE}/api/v1/register-simple",
                          json={"name": self.name})
        if r.status_code == 200:
            data = r.json()
            self.wallet = data["wallet"]
            self.credits = data.get("credits", 500)
            with open(WALLET_FILE, "w") as f:
                json.dump({"wallet": self.wallet,
                           "credits": self.credits}, f)
            print(f"Registered! Wallet: {self.wallet[:10]}... "
                  f"Credits: {self.credits}")
        else:
            raise Exception(f"Registration failed: {r.text}")

    def discover(self, query=None):
        """Find available APIs."""
        url = f"{BASE}/api/services"
        if query:
            url += f"?q={query}"
        r = requests.get(url)
        return r.json() if r.status_code == 200 else []

    def call(self, endpoint, method="GET", params=None, json_body=None):
        """Call an x402 endpoint. Uses trials first, then credits."""
        url = f"{BASE}/x402/{endpoint}"

        # If we have a wallet, always include it for credit tracking
        if self.wallet:
            if params is None:
                params = {}
            params["wallet"] = self.wallet

        if method == "GET":
            r = requests.get(url, params=params)
        else:
            r = requests.post(url, params=params, json=json_body)

        # Handle 402: trials exhausted, using credits
        if r.status_code == 402:
            data = r.json()
            if data.get("_trial"):
                remaining = data["_trial"].get("credits_remaining", 0)
                self.credits = remaining
                if remaining == 0:
                    raise Exception("No credits remaining. "
                                    "Buy credits at /buy.html")
            return data

        # Success or trial response
        result = r.json() if r.status_code == 200 else {"error": r.text}

        # Track credit usage from response
        cr = result.get("credits_remaining")
        if cr is not None:
            self.credits = cr
            # Update stored credits
            if os.path.exists(WALLET_FILE):
                with open(WALLET_FILE) as f:
                    data = json.load(f)
                data["credits"] = cr
                with open(WALLET_FILE, "w") as f:
                    json.dump(data, f)

        return result

    def run(self, task):
        """Execute a task by chaining API calls."""
        print(f"\n🤖 Agent '{self.name}' executing: {task}\n")

        # Step 1: Find relevant APIs
        services = self.discover(task.split()[0])
        if not services:
            print("No services found for this task.")
            return

        # Show top 3 matches
        print(f"Found {len(services)} services. Top 3:")
        for s in services[:3]:
            print(f"  • {s['id']}: {s.get('name', s['id'])} "
                  f"({s.get('priceCents', '?')}¢/call)")

        # Step 2: Call the best match
        best = services[0]["id"]
        print(f"\nCalling {best}...")
        result = self.call(best)
        print(f"Result: {json.dumps(result, indent=2)[:300]}")
        print(f"Credits remaining: {self.credits}")

# ── Usage ──
if __name__ == "__main__":
    agent = Agent("gas-tracker")

    # Discover gas-related APIs and call one
    agent.run("gas")

    # Call a specific endpoint with parameters
    result = agent.call("polymarket",
                        params={"market": "trump-2028"})
    print(json.dumps(result, indent=2))

Key Design Decisions

Why wallet in query params instead of headers?

Query parameters work everywhere: curl, browsers, Python requests, AI SDKs. Headers require SDK-specific configuration. The ?wallet= pattern is the lowest-friction integration path — your agent appends one parameter and it works.

Why free trials instead of pay-first?

Agents can't evaluate an API before calling it. Free trials let your agent test 5 different endpoints, find the one that returns the right data, then commit credits. It's the same model as SaaS free trials, adapted for machine consumers.

What happens when credits hit zero?

The endpoint returns HTTP 402 with credits_remaining: 0. Your agent can catch this and either switch to a free alternative, notify a human, or pause until credits are purchased. The protocol doesn't silently fail — it explicitly signals payment state.

Production tip: Store your wallet address as an environment variable or in ~/.agent-wallet.json. Never hardcode it. The wallet is your agent's identity — treat it like an API key.

What's Next

Once your agent can pay for APIs, the design space opens up:

The agent economy doesn't need a new payment protocol. HTTP 402 has been in the spec since 1997. What's new is that it actually works now — USDC settlement on Base, 2-second finality, sub-cent fees, and 150,000 endpoints that accept it.

Your agent can join the economy today. The first 500 calls are free.