Build: buyer side
Agent client
Agents should not hammer a saturated endpoint. They should verify the x429 offer, choose a policy path, and redeem exactly once.
Client API
import { x429Fetch } from "@x429/client";
import { mockPayer } from "@x429/payment-mock";
const response = await x429Fetch("https://api.example.com/v1/infer", {
deadlineMs: 30_000,
maxSpend: { amount: "0.01", asset: "USDC" },
payment: mockPayer({ privateKey: agentPaymentKey }),
clientKey: agentPublicKey,
onEvent: event => console.log(event)
});
State machine
- Send the original request.
- If response is not
429, return it. - If response is
429withoutX429-Version: 1, use legacy backoff. - Parse the x429 body and ticket.
- Resolve the issuer public key.
- Verify signature, scope, binding, and expiry.
- If free wait fits
deadlineMs, sleep until the window and redeem. - If free wait misses deadline, choose an upgrade within
maxSpend. - If upgrade is chosen, pay through the payment adapter and redeem with proof.
- If no path fits, throw a deadline/budget error.
Policy inputs
| Input | Why it matters |
|---|---|
deadlineMs | The maximum useful wait. Agents should not pay if the free slot still meets the task deadline. |
maxSpend | The maximum priority price the agent may pay for this request. |
clientKey | Used to bind tickets so another caller cannot redeem them. |
payment | Optional payer adapter. Without it, the client can only use the free lane. |
onEvent | Useful for observability, debugging, and simulator metrics. |
Client safety rules
- Never sleep or pay based on an unverified ticket.
- Do not reuse a ticket after a redemption attempt.
- Do not pay if the free slot meets the deadline.
- Do not ignore asset mismatch.
0.01 USDCis not the same as0.01 ETH. - Add a small redemption skew to avoid waking slightly before
notBefore.
Decision pseudocode
if response.status !== 429:
return response
if response.headers["X429-Version"] !== "1":
return legacyBackoff(response)
offer = parse(response.body)
key = await resolveIssuerKey(offer.issuer, offer.ticket.kid)
verifyTicket(offer.ticket, key, originalRequest, clientKey)
freeWaitMs = offer.ticket.notBefore - now()
if freeWaitMs <= deadlineRemainingMs:
sleepUntil(offer.ticket.notBefore + redemptionSkewMs)
return retryWithTicket(offer.ticket)
upgrade = cheapestUpgradeWithinBudget(offer.upgrades, maxSpend)
if upgrade:
proof = await payment.pay({ ticket: offer.ticket, upgrade })
return retryWithTicketAndProof(offer.ticket, proof)
throw DeadlineBudgetExceeded
Agent-visible errors
A well-behaved SDK should return structured errors the agent can reason about.
{
"code": "x429_deadline_budget_exceeded",
"message": "No free or paid admission path fits the policy",
"resource": "GET /v1/infer",
"freeWaitMs": 10000,
"deadlineMs": 2500
}