Rate limits
There are two different ceilings, and they are easy to confuse:
- Rate — how often you may ask. That is this page.
- Spend — how much you may run up. That is Spend & limits,
and it answers
402, not429.
The ceiling is on your account, not your key
Minting more API keys does not give you more rate. One account has one budget and every key you own draws on it. Keys are for separating environments and for being able to revoke one without disturbing the others — they are not a way to buy headroom.
| Scope | Default | What it is for |
|---|---|---|
| Per account | 600 / minute | The one that governs you |
| Per key | 600 / minute | Bounds one leaked key inside your account |
| Unrecognised key, per IP | 60 / minute | Nothing to do with you; it bounds guessing. A disabled, expired or revoked key counts here, never against your account |
| Keyless x402 carrying a payment, per IP | 30 / minute | Only when keyless payment is on; a key raises it |
| Keyless x402 without a payment, per IP | 300 / minute | Requests that can only be answered with a 402 price quote |
An IPv6 client is counted per /64, not per address.
Ten a second, sustained, is far above any interactive client. If you are hitting it you are almost certainly running a batch, and the fix is concurrency (see below) rather than a higher ceiling.
Separately from rate, a single API key may have 48 requests in flight at once (keyless traffic: 48 per IP), and the whole deployment 512. This one is per key, not per account. A long streaming answer occupies its slot for its whole life, so a batch of slow requests can reach the concurrency ceiling while nowhere near the rate one.
What a 429 tells you
A rate-window refusal — account, key, unrecognised key or keyless — carries all of these:
1HTTP/1.1 429 Too Many Requests2retry-after: 373x-ratelimit-limit: 6004x-ratelimit-remaining: 05x-ratelimit-reset: 17576004006x-aile-refused-by: rate_limit_account
retry-after— seconds. Honour it. The OpenAI and Anthropic SDKs already do this automatically.x-ratelimit-reset— epoch seconds, when the window turns over.x-aile-refused-by— which window you hit, because ten different things answer429and they want different responses from you:
x-aile-refused-by | What happened | What to do |
|---|---|---|
rate_limit_account | Your account's requests-per-minute | Slow down, or spread the work over time |
rate_limit_key | One key asking too often | Usually a runaway loop on that key |
rate_limit_unknown_key | The key is not one of ours | Check the key — this is the guessing bound, not your limit |
rate_limit_anon | Keyless x402 requests carrying a payment from your IP | Send a key |
rate_limit_anon_quote | Keyless requests with no payment from your IP | Stop re-asking for the price; sign the 402 you have |
The other five carry no x-ratelimit-* or x-aile-refused-by header:
- Concurrency — 48 in flight on this key.
retry-after: 5, and the message says so. Lower your parallelism; retrying immediately makes it worse. - The abuse guard's rate cap —
retry-after, andrefusedBy: "l3.rate"in the body. See Errors. - Keyless x402 on the non-chat endpoints, per paying wallet (where the
deployment enables it) — checked after your payment verifies and before
anything is dialled, so you are not charged. Per hour: 3 paid requests that
ended uncollected by your own doing (you hung up, or the payment was refused
at settlement), 5 that ended with no output, and 5 delivered without a
confirmed settlement. And at most 2 paid requests in flight at once.
retry-aftersays when the window turns over (1for the in-flight cap). - A video's keyless download link — 30 downloads a minute per video. The
message says when to retry; there is no
retry-after. - A stake key's free window —
x-aile-stake: exhausted, withx-aile-stake-wallsaying which wall:window(your own) orpool(the shared one). Plusx-aile-stake-resets(epoch milliseconds) andretry-afterwhen the window or pool has a refill time. The two come together or not at all: a shared-pool refusal with no refill time carries neither, so back off as below. Set a fallback key on the stake key to keep serving, billed normally.
On the chat endpoints (/v1/chat/completions, /v1/messages, /v1/responses)
and any endpoint your tier's window covers, a stake key's other refusals name their reason in the same header, with no
reset: off (403, your tier has no free window), size (400, the request is
bigger than the window can ever cover; make it smaller) and unavailable
(503, our side). If the stake key names a fallback key that cannot pay
(disabled, expired or over its credit limit), the status is that key's own —
402 or 403 — with the same x-aile-stake and x-aile-stake-wall values
and x-aile-stake-resets when there is a refill time, but never retry-after:
waiting does not fix the fallback. A fallback key whose own pins (provider or
no self-hosted, sharer, machine, price ceiling, verified-only) rule out the sharer counts as
none here: you get the refusal you would get with no fallback set, status and
headers included, and the message names the pin, as does
x-aile-stake-fallback-pin: provider, lender, machine, price or
verified.
A served reply says applied when the window paid for it, or exhausted when
it was billed to the fallback key, whatever the reason the window did not pay
(off, size and unavailable included); exhausted adds
x-aile-stake-resets when there is a refill time, and neither carries
retry-after. A request that declares a tool the provider runs on its own servers and bills on reported usage (such as x_search on grok-cli) never draws the window: the fallback key pays and the reply says exhausted, and with no usable fallback key it is a 400 with x-aile-stake: size, or the fallback key's own status when one is set but cannot pay, and in every case the message names the tool to remove. No x-aile-stake means the window was not consulted (for
example an x402-paid serve) or the stream took over 25 s to start: such a
stream is committed to 200 with x-aile-edge-hold and only the x-aile-*
headers that could no longer change, never x-aile-stake (see Headers), and a refusal after that arrives as an
SSE error frame. A non-streaming reply that carries
x-aile-edge-hold was held the same way, and a refusal after that is a 200
whose JSON body is the error. Do not read a missing header as applied. A
served request that ends in an error status carries neither value. A stream
sends the header when it starts, so a 200 stream that ends up
unbilled — for example one that errors or is cut off before any text, or ends
empty — keeps applied or exhausted, although nothing was charged and the
window was not drawn.
Chat always draws the window; other /v1 endpoints draw it only where the
operator opened them to your tier (the stake page lists them). Everywhere else its
fallback key pays, or the request is refused — 403 when no fallback key is set,
or it is disabled, expired or its own pins rule the request out, and 402 when it
is over its credit limit — and no x-aile-stake* header is sent.
A 429 from a sharer's upstream is a different thing and is not counted
against you. On chat those are retried against another sharer before you ever
see them, except for a self-hosted (local/) model, which makes one attempt
and returns the provider's 429 status and body without its headers, so it
carries no retry-after. The non-chat endpoints (embeddings, images, audio and
the rest) also make one attempt, and answer a provider's 429 as 503 with
x-aile-status: 502 and no provider text; you were not charged. Back off as
below either way.
Uwaga
x-ratelimit-* and x-aile-refused-by appear only on a refusal. A
successful response carries no x-ratelimit-* headers at all: the provider's
own describe the sharer's account, not yours, and are dropped.
Stake-funded discount
Where the operator runs it, stakers who opt in share their unused window as a
discount for everyone else. A balance-paid request is discounted in full or not
at all, first come, and x-aile-discount says which: applied (you paid
x-aile-discount-percent percent below the listed price), exhausted (no staker's
window could fund this request, so you paid full price — not a refusal),
pinned (the request chose who serves, which a discount never covers: it named a
sharer, machine or account, it or the key allows only some sharers, a block list, a
price floor or moving failing sharers to the back
set aside the sharer that would have been tried first, or
speed first changed who is tried first), ineligible (self-hosted capacity, partner credit, your own stake window
paid, a wallet whose key may have been exported, a request that declares a tool the provider runs and bills itself, such as x_search, or a sharer you related yourself to, say by withdrawing to its wallet, was skipped) or off (not offered on this endpoint). Prices shown
stay undiscounted, and a price ceiling is checked against them. A key's credit
limit counts only what you pay; if the discount does not apply and the full price
no longer fits it, the request is refused 402; where a discount was expected,
x-aile-discount says why it did not apply (usually exhausted). Requests paid by x402
or MPP, with or without a key, are never discounted and carry no
x-aile-discount header.
A relay restarting
While a relay instance restarts, a new /v1 request may get 503 with
retry-after: 2, or fail to connect for a few seconds; retry either. Requests
already running get a few seconds to finish; a long stream can still be cut.
A key has to look like a key
Caller keys are sk-aile- followed by 48 hex characters. Anything else is
refused with 401 and x-aile-refused-by: malformed_key before we look
anything up — so a typo fails fast and cheaply rather than counting against you.
Backing off properly
Respect retry-after, and add jitter if you run many workers — a fleet that all
retries at exactly the same moment turns one refusal into a thundering herd and
gets refused again together.
1import time, random2from openai import OpenAI34client = OpenAI(base_url="https://api.aile.sh/v1", api_key=KEY)56for attempt in range(5):7 try:8 return client.chat.completions.create(model=MODEL, messages=msgs)9 except Exception as e:10 wait = getattr(e, "response", None) and e.response.headers.get("retry-after")11 time.sleep(float(wait or 2 ** attempt) + random.uniform(0, 0.5))
If you need more
The defaults are per deployment — a self-hosted aile can raise them in its own configuration. Staking raises no limit here. It buys a free window on the stake key only; see Staking. On a hosted deployment, ask: the number is a judgement about capacity, not a paywall.