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.
The tally.x402 resource lets your agent call any HTTP service that uses the x402 protocol. 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.
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 for an end-to-end walk-through.
Method
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).
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
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
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 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
Drop this into your LLM tool list and the agent can pay for any x402-paywalled endpoint with a single tool call:
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:
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 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:
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
});