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
| Header | Required | Meaning |
|---|---|---|
Retry-After | yes | Legacy-compatible wait hint. Should match the free ticket window start. |
X429-Version | yes | Signals that the body contains an x429 offer. Current value: 1. |
Content-Type | yes | application/json. |
Link | recommended | Points to the discovery document. |
Cache-Control | yes | no-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
| Condition | Expected behavior |
|---|---|
| Malformed ticket | Reject with a client-visible error. Do not admit. |
| Invalid signature | Reject. Treat as tampering. |
| Wrong resource or binding | Reject. Tickets are not transferable in v1. |
| Before free window | Reject unless valid paid proof allows early admission. |
| Expired ticket | Reject and allow the client to re-enter normal policy. |
| Spent nonce | Reject as replay. |
Compatibility and versioning
- Servers must continue returning a normal HTTP
429status so legacy clients behave safely. X429-Version: 1is 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.
| Field | Rule |
|---|---|
notBefore | Earliest free-lane admission time unless paid proof allows an earlier class. |
notAfter | Last possible redemption time. Expired tickets are rejected. |
Retry-After | Legacy hint that should point to the same free-lane wait. |