Wire spec

Protocol

A minimal x429 exchange has three parts: a compatible 429 response, a signed ticket, and a redemption request.

Throttle response

HTTP/1.1 429 Too Many Requests
Retry-After: 10
X429-Version: 1
Content-Type: application/json
Link: </.well-known/x429.json>; rel="service-desc"
Cache-Control: no-store

{
  "x429": 1,
  "issuer": "https://api.example.com",
  "resource": "GET /v1/infer",
  "ticket": {
    "id": "tkt_abc123",
    "kid": "issuer-2026-07",
    "issuer": "https://api.example.com",
    "resource": "GET /v1/infer",
    "scope": "GET /v1/infer",
    "class": "free",
    "bind": { "type": "client-key", "value": "ed25519:..." },
    "notBefore": "2026-07-07T14:03:47Z",
    "notAfter": "2026-07-07T14:04:17Z",
    "nonce": "n_...",
    "sig": "ed25519:..."
  },
  "upgrades": [{
    "class": "immediate",
    "notBeforeMs": 500,
    "price": { "amount": "0.004", "asset": "USDC", "rail": "mock" }
  }],
  "redeem": { "method": "same-request", "header": "X429-Ticket" }
}

Required headers

HeaderRequiredMeaning
Retry-AfteryesLegacy-compatible wait hint. Should match the free ticket window start.
X429-VersionyesSignals that the body contains an x429 offer. Current value: 1.
Content-Typeyesapplication/json.
LinkrecommendedPoints to the discovery document.
Cache-Controlyesno-store. Tickets should not be cached.

Ticket signature

The current reference signs canonical JSON using Ed25519. The sig field is excluded from the signed payload. Clients must verify before acting on a ticket or upgrade.

unsigned = ticket without "sig"
signature = ed25519.sign(canonicalJson(unsigned), issuerPrivateKey)
ticket.sig = "ed25519:" + base64url(signature)

Discovery

GET /.well-known/x429.json
{
  "x429": 1,
  "issuer": "https://api.example.com",
  "docs": "https://x429.dev/docs/",
  "keys": [{ "kid": "issuer-2026-07", "alg": "Ed25519", "publicKey": "ed25519:..." }],
  "paymentModes": ["mock"],
  "headers": { "ticket": "X429-Ticket", "clientKey": "X429-Client-Key" }
}

Redemption

GET /v1/infer HTTP/1.1
Host: api.example.com
X429-Client-Key: ed25519:...
X429-Ticket: <base64url-json-ticket>

For mock paid priority, add:

X429-Mock-Payment: <signed-mock-receipt>

Error behavior

ConditionExpected behavior
Malformed ticketReject with a client-visible error. Do not admit.
Invalid signatureReject. Treat as tampering.
Wrong resource or bindingReject. Tickets are not transferable in v1.
Before free windowReject unless valid paid proof allows early admission.
Expired ticketReject and allow the client to re-enter normal policy.
Spent nonceReject as replay.

Compatibility and versioning

  • Servers must continue returning a normal HTTP 429 status so legacy clients behave safely.
  • X429-Version: 1 is the opt-in signal for the richer JSON body.
  • Clients must ignore unknown top-level fields and unknown upgrade classes.
  • Servers should add new optional fields before changing existing field meaning.
  • A future incompatible protocol shape should use a new version value, not silent field changes.

Clock and time rules

Tickets use absolute ISO timestamps because agents and servers may not share process state. Clients should tolerate small clock differences with a redemption skew, but servers remain the source of truth.

FieldRule
notBeforeEarliest free-lane admission time unless paid proof allows an earlier class.
notAfterLast possible redemption time. Expired tickets are rejected.
Retry-AfterLegacy hint that should point to the same free-lane wait.

Read next