Buying at volume: limits, retries and spend caps for AI agents

An agent that buys a thousand calls in an hour needs what a buyer of one call needs, at a larger scale: a price it can check before it pays, a retry that never pays twice, a ceiling on what it spends, and limits it can plan around. The limits on this page are rendered from the code that enforces them.

Before the burst: read the prices

Every route and its list price is in /api/pricing (free). Each row also names the chains it takes (networks, null for every offered chain) and, on a route that runs as a job, its async block. Read it at the start of each burst rather than from memory: list prices change, and each change is on the changelog.

curl -s https://agent402.tools/api/pricing \
  | jq -c '.endpoints[] | select(.slug=="onchain-sql") | {path, price, networks}'

The 402 is the quote for the request in hand: each accepts entry carries the amount in the asset's smallest unit on that chain, with any settlement premium or price floor of the chain included (/api/pricing lists them under payment.premiumUsdByNetwork and payment.minPriceUsdByNetwork for the chains that have one). On the metered model routes the quote is computed from the request body, and the paid retry is priced again from its own body, so send the paid retry with the body you were quoted for. Compare the quote with your ceiling before you sign; the clients below do that on their own.

Throughput limits

There is no per-wallet rate limit on paid calls. The limits below bound unpaid reads, failed settlements and concurrent heavy runs. Each one answers before any payment is taken, and the last column says how long to wait.

What is counted Limit Answer
Unpaid price checks (a request with no payment) to priced catalog routes 3,000 an hour per client address; 30,000 an hour for the rest of the clock hour once that address settles a payment, and for 10 minutes after each paid call 429, Retry-After until the hour turns
Unpaid quotes on /v1/metered 60 a minute and 1,200 an hour per client address; past that, 2 more for each paid /v1/metered call from that address, used within 10 minutes, up to 600 a minute and 12,000 an hour 429, Retry-After: 60
Refused payment credentials on /v1/metered the same figures, counted once a credential is refused 429, Retry-After: 60
Payments that verified and then failed to settle, per wallet refused at 3 in 15 minutes while they are more than half of the wallet's outcomes there, or at 40 whatever it settled 429 naming when it lifts
Report and media runs that failed to settle or failed after the run, per buyer blocked for 30 minutes at 3 in 15 minutes while they are more than half of the buyer's outcomes there, or at 10 whatever it settled 429 without Retry-After; the block lifts on its own
Unpaid requests with a body over 100 KB to a route that reads its price from the body 60 a minute and 1,200 an hour per client address, the same once that address has paid 429, Retry-After: 60
Concurrent runs of the report and media routes from one wallet the wallet's balance covers every run in flight plus the new one 429, Retry-After: 30
Jobs per client address 4 running at once 429, Retry-After: 30

An IPv6 /64 is one address. A request carrying a payment, a credits key or a proof-of-work solution is never counted as an unpaid price check. The report and media guard keys a buyer by its signed payer, its verified Tempo sender, its credits key or, for any other payment, its address. Once an address has paid (a final 200 that settled on any rail, or a credits debit), its unpaid price checks read against 10 times the figure for the rest of that clock hour and for 10 minutes after each paid call. A /v1/metered quote reads the whole body, so past its plain figure each paid /v1/metered call earns 2 more quotes (its own and one retry), used within 10 minutes. A payment credential that is refused still counts. Retry-After: 60 on a /v1/metered 429 is the minute window; an address past the hourly figure waits until its oldest quote in the last hour ages out, so a retry loop backs off further after repeated 429s.

How to plan a burst around them:

  • A stock x402 client reads the 402 and then sends the paid retry, so each call is one unpaid read plus one paid request, and only the unpaid read is counted. On /v1/metered an address gets 60 quotes a minute, plus 2 for each paid /v1/metered call, so a steady buyer is paced by its own paid calls, up to 600 a minute.
  • Keep the paying wallet funded above everything in flight. Each payment is verified on its own; a wallet that runs dry partway through a burst turns its later calls into failed settlements, which is what the per-wallet limit counts.
  • On a 429 or a 503, wait Retry-After (or the block named above) and send the call again.

Retries that never pay twice

On x402 a response of 400 or above is not settled, so a retry after one costs nothing extra. For a call that may have succeeded (a timeout, a dropped connection), use the replay rules:

  • Send an Idempotency-Key. The same key with the same credential (the same signed payment), route, query and body returns the first 200 answer (up to 1 MB, kept 10 minutes) with X-Idempotent-Replay: true: the tool does not run again and nothing settles again. A copy that arrives while the first is still running gets 409 idempotent_request_in_flight with Retry-After: 2; on x402 it is not charged.
  • On an EVM payment the signed payment is the key. Send the same request again with the same payment header within 10 minutes and the first answer comes back marked X-Replayed-Answer: 1, with no second settlement (identity-bound routes, routes with a lasting effect and streamed calls excepted).
  • On Solana, send the key yourself. A Solana payment has no implicit key, so pair an Idempotency-Key with the exact payment header that went out the first time.
  • A fresh signature is a new payment. Sign again only after an answer of 400 or above.

Each answer says whether it settled:

  • No PAYMENT-RESPONSE header: nothing settled.
  • PAYMENT-RESPONSE whose receipt reads success: false: settlement was refused, nothing moved.
  • PAYMENT-RESPONSE with a status under 400: charged and served.

One exception, on the EVM chains: a transfer that lands after a timed-out settle, a disconnect during settle, or a settle the facilitator answered as pending whose receipt was not read in time, is booked as a refund owed and repaid after on-chain proof.

Every route whose /api/pricing row has an async block runs as a job: the report products, video, the premium image tier, the router's execute tiers and the rest the rows mark. Send the paid call with Prefer: respond-async and it answers 202 at once with a Location job link. Poll GET /api/jobs/{id} (free), waiting Retry-After between polls, until status is completed or failed; state, stage, progress and etaSeconds say where it is. The payment settles only when the result is ready, and on x402 a failed run is not charged. A row whose block carries preferWait also takes Prefer: wait=<maxSeconds> to wait on the connection, and a paid call there with neither header is answered 400 before any payment.

With 4 jobs per client address at once, a burst of slow calls is a queue on your side: collect a finished job before you start the next. agent402-client and agent402-mcp switch to jobs on their own.

Spend caps in the clients

Every cap below is checked before a payment is signed, so a quote above it is refused with nothing sent.

agent402-client. The caps are constructor options, held in the client instance's memory; give each worker its own instance with its own share.

import { Agent402 } from "agent402-client";
import { wrapFetchWithPayment } from "@x402/fetch";
import { x402Client } from "@x402/core/client";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

const client = new x402Client();
client.setSpendControls?.(false); // the caps below replace the library default
registerExactEvmScheme(client, { signer: privateKeyToAccount(process.env.AGENT_KEY) });
const payFetch = wrapFetchWithPayment(fetch, client);

const a = new Agent402({
  fetch: payFetch,
  x402Client: client,   // the caps are checked on the payment that is signed
  maxPerCallUsd: 0.05,  // refuse any single quote above this
  dailyLimitUsd: 25,    // rolling 24 hours, this instance
  maxPerHostUsd: 10,    // rolling 24 hours, per seller host
});
const out = await a.call("extract", { url: "https://example.com/article" });

A refused call throws SpendingLimitError naming the cap, the quote and the list price. A payment that was sent and answered with an error counts as held until its authorization can no longer settle.

agent402-mcp. The per-call cap is on by default (off removes it); the session budget is opt-in.

export AGENT_KEY=0x...                # or SOLANA_AGENT_KEY for a Solana wallet
export AGENT402_MAX_PER_CALL=0.10     # USD per call
export AGENT402_BUDGET=20             # USD for the whole session
export AGENT402_NETWORKS=solana       # pay only on these chains, in this order
npx -y [email protected]

agent402-openclaw. maxPerCallUsd in the plugin config, or AGENT402_MAX_PER_CALL_USD, defaults to the metered per-call cap below; off removes it only beside allowUncappedPayments: true.

All three refuse an accept that asks for a signing validity longer than a day, and refuse to send a payload signed past it.

Choosing a rail for volume

Rail When it settles When the call fails
x402 exact on an EVM chain after the tool answers, only for a response under 400 not charged; the signed payment is its own replay key
x402 exact on Solana after the tool answers, only for a response under 400 not charged; send an Idempotency-Key for replays
x402 exact on Stellar after the tool answers, only for a response under 400 not charged
x402 exact on Algorand after the tool answers, only for a response under 400 not charged
x402 exact, Gateway batched (where offered; paid from a Circle Gateway deposit) settled off chain by Circle Gateway after the tool answers, only for a response under 400 not charged
MPP evm charge the same x402 settlement underneath not charged
MPP Tempo pull (a signed transaction the relay broadcasts) broadcast after the tool answers, only for a response under 400 not charged
MPP Tempo push (a transfer you already sent) final before the call runs booked as a refund owed to the paying wallet, repaid after on-chain proof

On the EVM chains and Tempo pull, a settlement whose outcome is not known when the call is answered is read on chain again; one that lands after a failed answer is booked as a refund owed and repaid after on-chain proof.

  • The report products and other long routes take x402 exact on EVM chains only: their run outlives a Solana payment's recent blockhash, an Algorand transaction's rounds and a Tempo credential. Tempo is also not offered on the identity-bound routes.
  • A Solana payment embeds a recent blockhash that lives about a minute, so sign each payment right before you send it rather than signing a batch ahead.
  • A Solana payment refused for a missing USDC account or too little USDC answers with retry: "fund-wallet" and a Retry-After; fund the wallet before the next call.
  • MPP methods appear in the 402 while this instance offers them; /.well-known/x402 lists them.
  • On the metered routes, a 402 carrying an upto accept lets a wallet with a one-time Permit2 approval for USDC sign the quote as a ceiling and settle actual usage under it.

Metered chat at volume

  • Model ids. GET /v1/models lists every id; send one verbatim. An id the upstream does not list is refused with a 400 before any payment, carrying suggestions (the closest served ids, at most three) and a howToCall retried with the first. A version written with a hyphen (v4-1) is served as the dotted id when that exact id is listed.
  • Input size. Every model takes 200,000 input characters, and a model with a larger context window takes three characters per window token, up to 1,000,000. /v1/models publishes each model's own figure as x402.meteredMaxInputChars. A body over it is refused before payment with code input_too_large, maxInputChars, inputChars, hint: "trim-input" and suggestions of models whose window takes it.
  • Per-call cap. A request quoting above $2 is refused with a 400 before any upstream call: lower max_tokens or the input. On x402 it is not charged.
curl -s https://agent402.tools/v1/models \
  | jq -c '.data[] | select(.x402.meteredMaxInputChars) | {id, maxInputChars: .x402.meteredMaxInputChars}'

Errors to handle

Status What it means What to do
400 the input was refused before the tool ran; the body carries expected, required, example and howToCall (method, URL and a ready body) fix the request from howToCall; not charged on x402
402 the quote, or a refused payment; the first-contact 402 body carries the offer, howToCall and, on a job route, async pay, or fund the wallet the refusal names
409 the same keyed call is still running wait Retry-After, then resend the same key with the same payment header to get its answer
422 the tool cannot serve this input (an image too large to read, a body that is not an image) read error and the howToCall note; not charged on x402
429 one of the limits above wait Retry-After
503 busy or briefly paused; image-ocr reading another image over 16 megapixels answers hint: "large-image-busy" wait Retry-After, or send a smaller image, which is read at once
502, 504 with attribution: "upstream" a source the tool reads failed (a host timed out or refused the connection) not your input: retry later or pick another source; not charged on x402

A loop that honors all of it, shown with plain fetch (pass your payment fetch in its place):

const BASE = process.env.AGENT402_URL || "https://agent402.tools";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function call(path, body, { pay = fetch, tries = 5 } = {}) {
  // The key replays a stored answer only with the same signed payment; a payment
  // client signs afresh on each retry, so each retry here is its own paid call.
  // This loop retries only answers that are not charged on x402.
  const key = crypto.randomUUID();
  for (let i = 0; i < tries; i++) {
    const res = await pay(BASE + path, {
      method: "POST",
      headers: { "Content-Type": "application/json", "Idempotency-Key": key },
      body: JSON.stringify(body),
    });
    if (res.ok) return res.json();
    if (![409, 429, 502, 503, 504].includes(res.status)) throw new Error(JSON.stringify(await res.json()));
    const wait = Number(res.headers.get("retry-after")) || 2 ** i;
    await sleep(wait * 1000);
  }
  throw new Error("gave up after " + tries + " tries");
}

console.log(await call("/api/hash", { text: "hello world" }));

The recovery contract in brief is at /agents.md, and the whole service at /llms.txt. A worked chain of paid stages, from research to subtitles, is in content pipelines.

Packages for this guide

Back to guides