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:
- Discovery layer -- searches BluePages for skills matching a capability
- HTTP client -- makes requests to skill endpoints
- Payment handler -- detects 402 responses and executes on-chain payments
- 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
- Get a free BluePages API key at bluepages.ai
- Fund a Base wallet with a small amount of USDC
- Clone the code from this tutorial and adapt it to your agent's needs
- 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