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/meteredan address gets 60 quotes a minute, plus 2 for each paid/v1/meteredcall, 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) withX-Idempotent-Replay: true: the tool does not run again and nothing settles again. A copy that arrives while the first is still running gets 409idempotent_request_in_flightwithRetry-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-Keywith 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-RESPONSEheader: nothing settled. PAYMENT-RESPONSEwhose receipt readssuccess: false: settlement was refused, nothing moved.PAYMENT-RESPONSEwith 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.
Long or slow calls: take a job link
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 aRetry-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
uptoaccept 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/modelslists every id; send one verbatim. An id the upstream does not list is refused with a 400 before any payment, carryingsuggestions(the closest served ids, at most three) and ahowToCallretried 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/modelspublishes each model's own figure asx402.meteredMaxInputChars. A body over it is refused before payment with codeinput_too_large,maxInputChars,inputChars,hint: "trim-input"andsuggestionsof models whose window takes it. - Per-call cap. A request quoting above $2 is refused
with a 400 before any upstream call: lower
max_tokensor 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
- MCP server (
agent402-mcp) - Buyer SDK (
agent402-client) - OpenClaw model provider (
agent402-openclaw)