A 402 is a payment handshake
A paid call has two retry paths. Mixing them is how an agent gets stuck signing payments or burns through its task budget.
The first 402 Payment Required is expected. In the x402 v2 flow, the server sends payment terms in PAYMENT-REQUIRED. The client signs those terms, then repeats the request with PAYMENT-SIGNATURE.
That repeat isn’t an error retry. It’s the second half of the protocol.
Before signing, check the network, asset, amount, and recipient. Agentutility endpoints settle in USDC on Base mainnet, whose network identifier is eip155:8453. If the quoted amount exceeds the agent’s per-call ceiling, fail locally. Don’t let the wallet decide after signing.
What if the paid request returns another 402? Stop. Read the response error. A wrong network, expired authorization, insufficient balance, or rejected signature needs correction. Sending the same invalid payment again won’t fix it.
Set a hard limit of one newly signed payment per logical attempt.
A 5xx has an uncertain outcome
A 500, 502, 503, or 504 means the server couldn’t return a successful result. But work may have started, and payment may have settled.
That changes the retry decision.
A paid GET can usually be replayed after a delay. A paid POST needs documented idempotency support, such as an Idempotency-Key, or a status endpoint that can resolve the first attempt. Without either one, fail and surface the uncertain result to the caller.
Don’t assume an error response was free. Inspect PAYMENT-RESPONSE when present, and retain any transaction hash or request ID. A missing response after a timeout doesn’t prove that settlement failed.
Most 4xx responses shouldn’t be retried. Fix the request first. 429 is the exception agents will encounter most often: honor Retry-After, then check the budget again before signing anything.
Put USDC limits ahead of retries
Agentutility’s registry has 799 endpoints priced from $0.001 through $0.50 per call. One global retry count doesn’t fit that spread.
Use two monetary limits: a maximum price for one authorization and a maximum spend for the full task. Reserve the quoted amount before every paid attempt. If the outcome is unknown, keep that amount reserved until a receipt or status check resolves it.
Say an endpoint costs $0.02 and the task has $0.04 left. The agent gets at most two paid attempts, even if its HTTP policy normally permits four.
Stop before signing. After settlement is too late.
Exponential backoff in code
This JavaScript wrapper expects paidFetch to handle one x402 handshake internally. Each wrapper call may create a new payment, so the budget check happens before every call.
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export async function callPaidEndpoint({
paidFetch,
url,
init = {},
priceMicros,
budgetMicros,
maxAttempts = 4,
}) {
const method = (init.method ?? "GET").toUpperCase();
const headers = new Headers(init.headers);
const canReplay =
method === "GET" ||
method === "HEAD" ||
headers.has("Idempotency-Key");
let reservedMicros = 0;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
if (reservedMicros + priceMicros > budgetMicros) {
throw new Error("Paid retry would exceed the task budget");
}
reservedMicros += priceMicros;
try {
const response = await paidFetch(url, { ...init, headers });
if (response.status === 402) {
throw new Error("Payment wasn't accepted; don't auto-sign again");
}
const retryable5xx =
response.status >= 500 &&
response.status <= 599 &&
response.status !== 501;
if (!retryable5xx || !canReplay || attempt + 1 === maxAttempts) {
return response;
}
} catch (error) {
if (!canReplay || attempt + 1 === maxAttempts) throw error;
}
const baseDelay = Math.min(8_000, 250 * 2 ** attempt);
const jitter = Math.floor(Math.random() * 250);
await sleep(baseDelay + jitter);
}
}
There’s no backoff for the initial 402 because it’s a protocol continuation. The delay starts after a server failure or lost connection, where another paid attempt could help and could cost more USDC.