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

  1. Send the original request.
  2. If response is not 429, return it.
  3. If response is 429 without X429-Version: 1, use legacy backoff.
  4. Parse the x429 body and ticket.
  5. Resolve the issuer public key.
  6. Verify signature, scope, binding, and expiry.
  7. If free wait fits deadlineMs, sleep until the window and redeem.
  8. If free wait misses deadline, choose an upgrade within maxSpend.
  9. If upgrade is chosen, pay through the payment adapter and redeem with proof.
  10. If no path fits, throw a deadline/budget error.

Policy inputs

InputWhy it matters
deadlineMsThe maximum useful wait. Agents should not pay if the free slot still meets the task deadline.
maxSpendThe maximum priority price the agent may pay for this request.
clientKeyUsed to bind tickets so another caller cannot redeem them.
paymentOptional payer adapter. Without it, the client can only use the free lane.
onEventUseful 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 USDC is not the same as 0.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
}

Read next