Trang này chỉ có tiếng Anh.

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, not 429.

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.

ScopeDefaultWhat it is for
Per account600 / minuteThe one that governs you
Per key600 / minuteBounds one leaked key inside your account
Unrecognised key, per IP60 / minuteNothing 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 IP30 / minuteOnly when keyless payment is on; a key raises it
Keyless x402 without a payment, per IP300 / minuteRequests 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:

HTTPhttp
1HTTP/1.1 429 Too Many Requests
2retry-after: 37
3x-ratelimit-limit: 600
4x-ratelimit-remaining: 0
5x-ratelimit-reset: 1757600400
6x-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 answer 429 and they want different responses from you:
x-aile-refused-byWhat happenedWhat to do
rate_limit_accountYour account's requests-per-minuteSlow down, or spread the work over time
rate_limit_keyOne key asking too oftenUsually a runaway loop on that key
rate_limit_unknown_keyThe key is not one of oursCheck the key — this is the guessing bound, not your limit
rate_limit_anonKeyless x402 requests carrying a payment from your IPSend a key
rate_limit_anon_quoteKeyless requests with no payment from your IPStop 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, and refusedBy: "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-after says when the window turns over (1 for 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, with x-aile-stake-wall saying which wall: window (your own) or pool (the shared one). Plus x-aile-stake-resets (epoch milliseconds) and retry-after when 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.

Ghi chú

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.

Pythonpython
1import time, random
2from openai import OpenAI
3
4client = OpenAI(base_url="https://api.aile.sh/v1", api_key=KEY)
5
6for 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.