Chat API
Read this page as MarkdownIn this section: Rate limits
Rate limits
Requests per minute and turns at once, counted per grant, with the headers every response carries so a client can plan around them.
The ceilings
| Ceiling | Value | Counted per |
|---|---|---|
| Requests per minute | 30 | Grant: one OAuth token family, or all of an account's personal keys together |
| Turns at once | 2 | The same grant |
| Turns at once, one account | 4 | The account, across every grant it holds |
| Turns at once, everyone | 6, briefly 12 during a deploy | The whole service, shared across callers |
| Spend in a Pacific calendar day | An allowance sized well above a person's day | The account, across every grant it holds |
Refreshing a token does not reset its count. Every personal API key on one account shares a single count, so a second key is not a second allowance. Authorizing a second client does widen the per-grant ceiling, but only up to the per-account one, so a third grant buys no more turns at once than the second did. The global ceiling is shared, so someone else's traffic can trip it; every one of these three refusals says concurrency limited and asks for a two-second wait.
The daily allowance is the one ceiling counted per account rather than per grant, so authorizing a second client does not widen it, and the person's own web chat spends from the same allowance. It exists to bound what one compromised credential can spend, and normal use does not approach it; a client that does is almost always looping. It resets at midnight Pacific, all at once. The Retry-After on its refusal is an hour, a hint rather than the reset time.
GET /v1/models has a per-minute window of its own, the same size as the completions one and counted on the same grant, but separate from it. Listing models does not spend the budget your turns need. It carries no x-ratelimit-* headers, so this page is the only place that ceiling is written down.
Headers on every response
Every /v1/chat/completions response carries these, on successes, streams and 429s alike:
x-ratelimit-limit-requests: 30
x-ratelimit-remaining-requests: 27
x-ratelimit-reset-requests: 41sThey are spelled the way OpenAI spells them, so an OpenAI SDK reads them with no Miles-specific code. remaining excludes the call being answered. reset is seconds until the window opens again.
An account whose monthly budget governs it also gets two more:
x-miles-budget-percent-used: 42
x-miles-budget-reset-date: 2026-10-14percent-used is an integer 0-100, already floored. reset-date is the day the budget period turns over, Pacific. Both are absent, not zero, on an account with no monthly budget: absent means the budget does not apply, and 0 would mean the opposite. Nothing is emitted before the caller is identified, so a 401 carries none of these. Neither does /v1/models, which has its own window and would otherwise report the wrong one under OpenAI's header names.
Over a ceiling
A 429 in the OpenAI error envelope with a Retry-After header. Four conditions share the status and need different handling. Read the message, not just the code:
type | Message starts | What to do |
|---|---|---|
rate_limit_exceeded | rate limited, retry after Ns | Wait Retry-After seconds; the per-minute window is full. On /v1/models this is that endpoint's own window |
rate_limit_exceeded | concurrency limited, retry after 2s | Reduce parallelism; another turn on this grant, on another grant of the same account, or on the service, is still running |
rate_limit_exceeded | You've reached today's usage limit | Wait Retry-After, which is an hour here. The account's daily spend allowance is full until midnight Pacific. This is not the per-minute window and will not clear in a minute, so a client that retries on the usual 429 backoff will keep failing |
insufficient_quota | You've used this month's budget. It resets on … | This month's budget is spent. Retry-After is seconds until the reset date, not a short backoff -- it can be days, so do not retry on the usual 429 schedule |
Two of those four are rate_limit_exceeded on a window measured in seconds and one is rate_limit_exceeded on a window measured in hours, so a client that branches on the code alone will retry the daily ceiling far too fast. Branch on Retry-After.
The full table of codes is on the errors page.