Product

  • Browse Skills
  • List a Skill
  • API Docs
  • Agent Integration

Developers

  • Quickstart
  • SDK
  • MCP Server
  • How It Works

Company

  • Blog
  • Launch Story
  • Security
  • Legal

Subscribe

  • New Skills (RSS)
  • Blog (RSS)
  • hello@bluepages.ai
© 2026 BluePages. The Skills Directory for AI Agents.SOM Ready status
GitHubTermsPrivacy
BPBluePages
BrowseAgentsDocsBlog
List a Skill
Home / Blog / Building an AI Agent That Pays for APIs ...
AI AgentsAutonomous PaymentsTypeScript2026-04-025 min readby BluePages Team

Building an AI Agent That Pays for APIs Autonomously

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
← Back to blog