Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.tallyforagents.com/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks through an LLM agent that autonomously pays for a paywalled API call using the x402 protocol shape — with Tally providing the wallet + permission layer. By the end you’ll have a working agent that:
  • Receives a natural-language task (“what’s the weather in Tokyo?”)
  • Decides on its own to call a paid tool
  • Handles an HTTP 402 Payment Required response by paying with Tally
  • Verifies the payment landed on-chain before being served the data
  • Returns the answer to the user
Either Claude or GPT works — the example auto-detects which API key you’ve configured. The runnable source lives at github.com/pkohler95/tally-examples in the paying-weather-agent/ directory — clone it to follow along, or read on for the inline walkthrough.

What you’ll build (and why)

Most “agent” demos today either fake the payment side (no real money moves) or use a bespoke integration (only works with one vendor). This example uses two pieces of real infrastructure:
  • Tally for the wallet + permission layer. The agent is granted scoped spending authority (per-tx max, daily cap, optional recipient allowlists) on a wallet, and Tally enforces it server- side. No keys on the agent’s box.
  • x402 protocol shape for how the receiver communicates “you owe me $X.” Coinbase’s x402 standard is the closest thing to a protocol for agentic payments. The same flow works with any service that follows it.
Together they answer “how does an agent autonomously pay for things on the internet?” — with real wallets, real on-chain payments, and real policy enforcement, while you’re still on testnet so nothing catastrophic happens if the agent makes a mistake.

Prerequisites

In the Tally dashboard

This setup is identical to the Quickstart; the short version is:
  1. Sign in at app.tallyforagents.com.
  2. Fund your Main Wallet with Base Sepolia USDC + ETH. ~$1 USDC + ~0.01 ETH is plenty:
  3. Register an agent named weather-agent (Dashboard → Agents → Register agent). weather-agent is the example’s default; if you pick a different name you’ll set TALLY_AGENT_ID in .env.local later.
  4. Grant a permission to that agent on your Main Wallet. Defaults (10/tx,10/tx, 100/day) are more than enough — the mock weather service charges $0.05 per query.
  5. Create an API key (Dashboard → API keys → Create). Copy the plaintext (shown once).

For the LLM side

One of:
  • Anthropic API key
  • OpenAI API key
The example auto-detects which you have. Anthropic wins ties; unset ANTHROPIC_API_KEY for a run to force OpenAI.

For the code

Clone the example repo OR install the deps in your own project:
# Clone and install (recommended):
git clone https://github.com/pkohler95/tally-examples.git
cd tally-examples && pnpm install

# Or add the deps to your own project:
npm install @tallyforagents/sdk viem dotenv
# plus ONE of:
npm install @anthropic-ai/sdk
# OR
npm install openai

The agent + mock server

The runnable example splits across four files:
  • server.ts — reference x402 weather service (HTTP server with on-chain verification). The agent doesn’t need this to run — by default it talks to Tally’s hosted demo endpoint at https://app.tallyforagents.com/api/demo/x402-weather. The local server is here as a reference implementation you can read, modify, or self-host if you want.
  • tools.ts — the get_weather tool definition + payment-and-retry logic
  • agent-claude.ts / agent-openai.ts — provider-specific agent loops (pick whichever matches your API key)
  • index.ts — entry point that auto-detects which model SDK is available and dispatches
Each section below walks through one piece. If you only care about consuming an x402 service (the agent side), you can skip the server section.

What’s actually happening

The agent has access to one tool, get_weather. The LLM’s system prompt is implicit — Claude / GPT both recognize that paywalled APIs are a real thing — but the tool’s description spells it out:
const TOOL_DEFINITION = {
  name: "get_weather",
  description:
    "Get the current weather for a city. This tool is paywalled: " +
    "the first call returns 402 Payment Required with an amount; the " +
    "tool automatically pays via the user's Tally wallet and retries.",
  parameters: {
    type: "object",
    properties: {
      city: { type: "string", description: "The city name" },
    },
    required: ["city"],
  },
};
When the LLM calls it, the implementation does the x402 dance:
// 1. Hit the server. Expect 402.
const first = await fetch(`${serviceUrl}/weather?city=${city}`);
if (first.status === 402) {
  const terms = (await first.json()).accepts[0];

  // 2. Pay via Tally. The agent's permission caps + recipient
  //    allowlists are enforced server-side; if the LLM somehow
  //    hallucinated a too-large amount, Tally would reject it.
  const payment = await tally.payments.create({
    agent_id: agentId,
    wallet: walletAddress,
    to: terms.payTo,
    amount_usdc: formatAtomicUSDC(BigInt(terms.maxAmountRequired)),
    idempotency_key: `weather-${city}-${Date.now()}`,
  });

  // 3. Retry with the tx hash. The server verifies on-chain.
  const retry = await fetch(`${serviceUrl}/weather?city=${city}`, {
    headers: { "x-payment": payment.tx_hash },
  });
  return retry.json();
}

The server side (mock x402)

The included server.ts is a ~150-line Node HTTP server that follows the x402 response shape:
// On any /weather request without X-Payment, return 402 with
// payment terms in the body.
{
  x402Version: 1,
  error: "X-Payment header required",
  accepts: [{
    scheme: "exact",
    network: "base-sepolia",
    maxAmountRequired: "50000",      // 0.05 USDC atomic units
    resource: "/weather?city=Tokyo",
    description: "Premium weather data",
    payTo: "0x...",
    asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", // USDC
  }]
}
When the retry comes in with X-Payment: <tx-hash>, the server uses viem to verify on-chain:
const receipt = await client.waitForTransactionReceipt({
  hash: txHash,
  timeout: 30_000,
});

// Walk the receipt logs for a USDC Transfer event with the right
// recipient and at-least amount.
for (const log of receipt.logs) {
  if (log.address.toLowerCase() === USDC_BASE_SEPOLIA.toLowerCase()) {
    const decoded = decodeEventLog({ abi: TRANSFER_ABI, ...log });
    if (decoded.args.to.toLowerCase() === expectedTo &&
        decoded.args.value >= expectedAmount) {
      return { ok: true };
    }
  }
}
This is real verification. The mock server doesn’t trust the agent’s claim that “I paid you” — it checks Base Sepolia for the matching on-chain transfer. Same model as production x402 services.

What this teaches

Three concepts compound in the example:
  1. The LLM doesn’t see money. Tools take amounts as decimal strings; the LLM sees “this costs 0.05 USDC” but never touches keys or moves funds directly. Your code is the only thing that can call Tally — and Tally’s policy is the only thing that can move funds.
  2. The x402 protocol is just HTTP. No special SDK on the receiver side, no chain RPC from the agent’s process, no Web3 wallet to integrate. The agent makes a normal fetch() and reacts to the 402 response.
  3. Multi-layered enforcement. Even if the LLM hallucinated a payment 100x bigger than expected, Tally would reject it (per-tx cap exceeded). Even if the agent’s auth key were compromised, the most damage possible is bounded by the cap.

Pointing at a real x402 service

The example’s WEATHER_SERVICE_URL defaults to http://localhost:4242 (the mock server). When you want to talk to a real x402 service:
  1. Find one that accepts Base Sepolia (most are mainnet-only today, but the ecosystem is growing).
  2. Set WEATHER_SERVICE_URL to its URL.
  3. Update the agent’s permission to allow that service’s wallet as a recipient (or leave the allowlist empty to allow any).
The code on the agent side is unchanged.

Limitations of the demo

The mock server intentionally cuts corners that a real x402 implementation needs:
  • Bare tx hash in X-Payment (real x402 uses a base64-encoded full payment payload)
  • No replay protection — the same tx hash works repeatedly
  • No request-window check — the tx could be hours old
  • Single payment scheme — real x402 supports multiple
The agent-side code is closer to spec-compliant; the simplifications are all server-side. The example README has the full list.

Where to go next