> ## 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.

# x402

> tally.x402 — call any HTTP service that follows the x402 payment protocol, and Tally handles the payment for you.

The `tally.x402` resource lets your agent call any HTTP service that uses the [x402 protocol][x402]. When the service returns `402 Payment Required`, the SDK parses the payment terms, pays via Tally, and retries with the on-chain proof in the `X-Payment` header — your agent code sees one `fetch()` call.

[x402]: https://www.x402.org

Use this for any agent that needs to pay for paywalled APIs without you hand-rolling the 402 dance every time. See the [`paying-x402-agent` guide](/guides/paying-x402-agent) for an end-to-end walk-through.

## Method

### `tally.x402.fetch(url, input)`

Fetch a URL. If the service returns 402, pay via Tally and retry automatically. Returns the final HTTP response plus the receipt for the payment (if one was needed).

```ts theme={null}
import { Tally } from "@tallyforagents/sdk";

const tally = new Tally({ apiKey: process.env.TALLY_API_KEY! });
const agent = await tally.agents.upsert({ id: "hermes" });

const { response, payment } = await tally.x402.fetch(
  "https://api.example.com/weather?city=Tokyo",
  {
    agent_id: "hermes",
    wallet: agent.wallets[0].address,
  },
);

if (response.ok) {
  const data = await response.json();
  if (payment) {
    console.log(`paid ${payment.amount_usdc} USDC, tx ${payment.tx_hash}`);
  }
}
```

The fetch returns whenever the underlying response comes back — for 200 on the first call, that's immediately; for 402 → pay → retry, that's a few seconds (the x402 server usually waits for on-chain confirmation before returning).

## Types

### `X402FetchInput`

```ts theme={null}
type X402FetchInput = {
  /** Agent's externalId. Must have an active permission grant on `wallet`
   *  with enough headroom to cover the x402 charge. */
  agent_id: string;

  /** Sender wallet address. Typically `agent.wallets[0].address`. */
  wallet: string;

  /** HTTP method for the request. Defaults to "GET". */
  method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";

  /** Extra request headers. `x-payment` is set automatically on the retry. */
  headers?: Record<string, string>;

  /** Request body. Passed through to fetch on both the initial call and the retry. */
  body?: BodyInit | null;

  /** Client-side cap on what the SDK will spend on this call. If the 402
   *  asks for more than this, the SDK throws (type:
   *  `x402_amount_exceeds_cap`) before paying. The agent's policy caps
   *  remain authoritative server-side; this is a per-call extra gate. */
  max_amount_usdc?: string;

  /** Memo stored on the Tally Payment row. Defaults to `x402:${host}${pathname}`. */
  memo?: string;

  /** Idempotency key for the underlying `payments.create`. If you may retry
   *  the same logical x402 call, set this to a stable string so the retry
   *  returns the original payment instead of double-spending. */
  idempotency_key?: string;

  /** Per-call timeout in ms for the underlying fetches. */
  timeout_ms?: number;
};
```

### `X402FetchResult`

```ts theme={null}
type X402FetchResult = {
  /** The final HTTP response. If the first call returned 200, this is that
   *  response. Otherwise it's the post-payment retry response. */
  response: Response;

  /** Receipt for the payment the SDK made on your behalf. `null` when the
   *  first call returned 200 (no payment was needed). */
  payment: X402PaymentReceipt | null;
};

type X402PaymentReceipt = {
  id: string;           // Tally Payment id (pay_...)
  tx_hash: string;      // On-chain transaction hash
  amount_usdc: string;  // Decimal USDC, e.g. "0.05"
  to: string;           // Recipient from the 402 terms
  network: string;      // "base-sepolia" (test) or "base" (live)
  memo: string | null;
};
```

## Errors

`tally.x402.fetch()` throws a [`TallyError`](/sdk/errors) for SDK-side problems. Errors that originate from the underlying `payments.create` call (policy denial, validation, rate limiting) come through as the standard typed exceptions. x402-specific failures use these `.type` values:

| `.type`                        | When                                                                                      |
| ------------------------------ | ----------------------------------------------------------------------------------------- |
| `x402_protocol`                | The 402 response was missing `accepts[]`, malformed JSON, or used an unsupported network. |
| `x402_amount_exceeds_cap`      | The 402 quoted more than the caller's `max_amount_usdc`.                                  |
| `x402_payment_missing_tx_hash` | Tally accepted the payment but returned no tx hash (should not happen — file a bug).      |
| `validation_failed`            | The caller passed an invalid `max_amount_usdc` decimal string.                            |

**Non-200 retry responses do not throw.** Some x402 services return 4xx/5xx legitimately (rate limit, bad request, etc.) — the SDK passes the response through and you decide what to do. Check `response.ok` / `response.status` as you would with any fetch.

## Patterns

### Generic tool for any LLM agent

Drop this into your LLM tool list and the agent can pay for any x402-paywalled endpoint with a single tool call:

```ts theme={null}
const TOOL_DEFINITION = {
  name: "pay_x402_service",
  description:
    "Call an HTTP service that may charge for the request via the x402 protocol. " +
    "Pays automatically if the service returns 402. Returns the response body as text.",
  parameters: {
    type: "object",
    properties: {
      url: { type: "string", description: "The full URL to call." },
      method: { type: "string", enum: ["GET", "POST"], description: "HTTP method." },
    },
    required: ["url"],
  },
};

async function callPayX402Service(args: { url: string; method?: "GET" | "POST" }) {
  const { response, payment } = await tally.x402.fetch(args.url, {
    agent_id: "hermes",
    wallet: agent.wallets[0].address,
    method: args.method ?? "GET",
    max_amount_usdc: "1.00", // refuse anything over $1 per call
  });
  const body = await response.text();
  return {
    status: response.status,
    body,
    paid: payment
      ? { amount_usdc: payment.amount_usdc, tx_hash: payment.tx_hash }
      : null,
  };
}
```

This is the building block for connecting an agent like Claude Desktop, Cursor, or your own LLM app to Tally — the LLM picks the URL, the SDK handles the payment.

### Idempotent retries

If your tool wrapper might retry the same logical x402 call (network blip, agent loop, etc.), pass a stable `idempotency_key` so the second attempt collapses into the first payment instead of double-spending:

```ts theme={null}
await tally.x402.fetch(url, {
  agent_id: "hermes",
  wallet: agent.wallets[0].address,
  idempotency_key: `tool-call-${toolCallId}`,
});
```

The key is scoped to (account, mode), so reusing it across runs is fine as long as you actually want the same payment.

### Capping per-call spend

The agent's [policy](/permissions) sets the per-tx and daily-cap ceiling Tally enforces. Within those, you can add a tighter per-call cap as a defensive measure against a buggy or malicious 402 quote:

```ts theme={null}
await tally.x402.fetch(url, {
  agent_id: "hermes",
  wallet: agent.wallets[0].address,
  max_amount_usdc: "0.10", // SDK throws before paying if 402 asks for more
});
```
