← Back to Blog
AI AgentsAutonomous PaymentsTypeScriptUSDCx402Tutorial

Building an AI Agent That Pays for APIs Autonomously

By BluePages Team·April 2, 2026·5 min read

Most AI agents today stop dead when they hit a paywall. The agent discovers an API it needs, sends a request, gets a 402 Payment Required response, and gives up. The human has to step in, sign up, enter a credit card, and hand the agent an API key.

This tutorial shows how to build an agent that handles the entire flow autonomously: discover a skill, detect that payment is required, make a USDC payment on Base, and retry with proof.

Architecture Overview

The agent has four components:

  1. Discovery layer -- searches BluePages for skills matching a capability
  2. HTTP client -- makes requests to skill endpoints
  3. Payment handler -- detects 402 responses and executes on-chain payments
  4. Retry logic -- resubmits requests with payment proof
User Request
    |
    v
[Discovery] --search--> BluePages API
    |
    v
[HTTP Client] --invoke--> Skill Endpoint
    |
    v (402?)
[Payment Handler] --transfer--> USDC on Base
    |
    v
[Retry with Proof] --invoke--> Skill Endpoint
    |
    v
Result returned to user

Prerequisites

npm install viem @anthropic-ai/bluepages-sdk

You will need:

  • A Base network wallet with USDC balance (even $1 is enough for thousands of micro-API calls)
  • A BluePages API key (free at bluepages.ai)
  • Node.js 18+

Step 1: Set Up the Wallet

The agent needs a wallet to sign transactions. For a production agent, use a secure key management solution. For this tutorial, we will use a private key from an environment variable:

import { createWalletClient, createPublicClient, http } from "viem";
import { base } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`);

const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
});

const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});

const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base

Step 2: Build the Discovery Layer

The discovery layer searches BluePages for skills that match a given capability:

interface Skill {
  id: string;
  name: string;
  description: string;
  endpoint: string;
  pricing?: {
    amount: string;
    currency: string;
    network: string;
  };
}

async function discoverSkill(query: string): Promise<Skill[]> {
  const response = await fetch(
    `https://bluepages.ai/api/skills?q=${encodeURIComponent(query)}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.BLUEPAGES_API_KEY}`,
      },
    }
  );

  if (!response.ok) {
    throw new Error(`Discovery failed: ${response.status}`);
  }

  const data = await response.json();
  return data.skills;
}

Step 3: Build the Payment Handler

The payment handler parses 402 responses and executes USDC transfers:

import { parseAbi } from "viem";

const erc20Abi = parseAbi([
  "function transfer(address to, uint256 amount) returns (bool)",
]);

interface PaymentRequirement {
  amount: bigint;
  currency: string;
  network: string;
  recipient: `0x${string}`;
  nonce: string;
}

function parsePaymentRequirement(response: Response, body: any): PaymentRequirement {
  return {
    amount: BigInt(body.payment.amount),
    currency: body.payment.currency,
    network: body.payment.network,
    recipient: body.payment.recipient as `0x${string}`,
    nonce: body.payment.nonce,
  };
}

async function executePayment(req: PaymentRequirement): Promise<string> {
  // Validate that we support this payment type
  if (req.currency !== "USDC" || req.network !== "base") {
    throw new Error(`Unsupported payment: ${req.currency} on ${req.network}`);
  }

  // Execute the USDC transfer
  const txHash = await walletClient.writeContract({
    address: USDC_ADDRESS,
    abi: erc20Abi,
    functionName: "transfer",
    args: [req.recipient, req.amount],
  });

  // Wait for confirmation
  const receipt = await publicClient.waitForTransactionReceipt({
    hash: txHash,
    confirmations: 1,
  });

  if (receipt.status !== "success") {
    throw new Error("Payment transaction failed");
  }

  return txHash;
}

Step 4: Build the Smart HTTP Client

This is the core of the agent: an HTTP client that handles 402 responses automatically.

interface InvokeOptions {
  maxPayment?: bigint; // Maximum amount willing to pay (in USDC micro-units)
}

async function invokeSkill(
  endpoint: string,
  payload: any,
  options: InvokeOptions = {}
): Promise<any> {
  const { maxPayment = BigInt(10000) } = options; // Default max: $0.01

  // First attempt
  const response = await fetch(endpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });

  // If not 402, return the response directly
  if (response.status !== 402) {
    if (!response.ok) {
      throw new Error(`Skill invocation failed: ${response.status}`);
    }
    return response.json();
  }

  // Parse payment requirement
  const body = await response.json();
  const paymentReq = parsePaymentRequirement(response, body);

  // Check spending limit
  if (paymentReq.amount > maxPayment) {
    throw new Error(
      `Payment of ${paymentReq.amount} exceeds max allowed ${maxPayment}`
    );
  }

  console.log(`Payment required: ${paymentReq.amount} ${paymentReq.currency}`);
  console.log(`Executing payment to ${paymentReq.recipient}...`);

  // Execute payment
  const txHash = await executePayment(paymentReq);
  console.log(`Payment confirmed: ${txHash}`);

  // Retry with proof
  const retryResponse = await fetch(endpoint, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Payment-Proof": txHash,
      "X-Payment-Nonce": paymentReq.nonce,
    },
    body: JSON.stringify(payload),
  });

  if (!retryResponse.ok) {
    throw new Error(`Skill invocation failed after payment: ${retryResponse.status}`);
  }

  return retryResponse.json();
}

Step 5: Wire It All Together

Now combine discovery and invocation into a single agent function:

async function agentExecute(task: string): Promise<any> {
  console.log(`Agent task: ${task}`);

  // Step 1: Discover relevant skills
  const skills = await discoverSkill(task);

  if (skills.length === 0) {
    throw new Error(`No skills found for: ${task}`);
  }

  // Step 2: Pick the best skill (simplest: use the first result)
  const skill = skills[0];
  console.log(`Selected skill: ${skill.name} (${skill.endpoint})`);

  // Step 3: Invoke with automatic payment handling
  const result = await invokeSkill(
    skill.endpoint,
    { query: task },
    { maxPayment: BigInt(5000) } // Max $0.005 per call
  );

  return result;
}

// Usage
const result = await agentExecute("analyze the sentiment of this product review");
console.log(result);

Safety: Spending Limits

An agent with a wallet is an agent that can lose money. Build in guardrails:

class SpendingTracker {
  private spent: bigint = BigInt(0);
  private readonly sessionLimit: bigint;
  private readonly perCallLimit: bigint;

  constructor(sessionLimitUsdc: number, perCallLimitUsdc: number) {
    this.sessionLimit = BigInt(Math.floor(sessionLimitUsdc * 1_000_000));
    this.perCallLimit = BigInt(Math.floor(perCallLimitUsdc * 1_000_000));
  }

  canSpend(amount: bigint): boolean {
    if (amount > this.perCallLimit) return false;
    if (this.spent + amount > this.sessionLimit) return false;
    return true;
  }

  recordSpend(amount: bigint): void {
    this.spent += amount;
  }

  get totalSpent(): string {
    return `$${(Number(this.spent) / 1_000_000).toFixed(6)}`;
  }
}

// Allow $1/session, $0.01/call
const tracker = new SpendingTracker(1.0, 0.01);

Integrate the tracker into invokeSkill so the agent cannot exceed its budget:

if (!tracker.canSpend(paymentReq.amount)) {
  throw new Error(`Spending limit exceeded. Total spent: ${tracker.totalSpent}`);
}

const txHash = await executePayment(paymentReq);
tracker.recordSpend(paymentReq.amount);

Running the Agent

export AGENT_PRIVATE_KEY="0x..."
export BLUEPAGES_API_KEY="bp_..."

npx tsx agent.ts

Example output:

Agent task: analyze the sentiment of this product review
Selected skill: SentimentPro (https://api.sentimentpro.ai/analyze)
Payment required: 500 USDC
Executing payment to 0x1234...5678...
Payment confirmed: 0xabcd...ef01
Result: { sentiment: "positive", confidence: 0.94, ... }

The Future of Agent Commerce

What we built here is a minimal viable example, but the pattern is powerful. An agent that can discover services, evaluate pricing, and pay autonomously can:

  • Comparison shop: Query multiple skills for the same capability and pick the cheapest
  • Budget optimize: Use free tiers when available, pay only when necessary
  • Scale without human bottlenecks: No signup forms, no credit card entry, no approval workflows

The missing pieces are still significant -- reputation systems (which skills deliver quality results?), refund mechanisms (what happens when a paid call fails?), and subscription models (pay once for N calls). But the foundation is here.

Get Started

  1. Get a free BluePages API key at bluepages.ai
  2. Fund a Base wallet with a small amount of USDC
  3. Clone the code from this tutorial and adapt it to your agent's needs
  4. If you are building a paid API, list it on BluePages with x402 pricing to make it discoverable and payable by autonomous agents

Share this article