Blog

429 Has Several Causes: Rate Limits, Spend Caps, and Shared Pools

A 429 is a category, not a diagnosis. The response body and headers tell you whether to wait, re-route, or stop retrying and fix the budget.

429 Has Several Causes: Rate Limits, Spend Caps, and Shared Pools

A 429 is a category, not a diagnosis. Teams treat it as "we sent requests too quickly" and add a sleep, which works for exactly one of its causes. The same status code also appears when a shared pool is exhausted by traffic to a different model, when a long-context request crosses its own separate budget, and when a hard spend limit stops traffic because the money side says stop. The response body and the rate-limit headers are what separate these cases; the retry strategy should follow from that reading, not from habit.

The practical split is simple. If the response carries a retry hint, the cause is likely a temporary rate condition and waiting is appropriate. If it does not, and the failures persist across attempts, the cause is more likely a budget or quota condition where retrying changes nothing. Getting that first branch right saves both latency and money.

Everything below follows the official rate limits guide, checked on 2026-11-01. The guide's tables contain example values that are not your limits; none of them are reproduced here.

Where limits actually apply

The level matters more than the number. Rate limits are defined at the organization level and at the project level, not at the user level. That single sentence explains a common surprise: issuing separate API keys to five teammates does not create five budgets, because the keys belong to the same project and organization. If you need isolation between workloads or customers, the boundary to split on is the project, which is also the boundary the multi-tenant guide recommends for quotas.

Limits also vary by model. A budget that feels generous on a small model can be tight on a large one, and the console is the authoritative place to see the current values for your tier.

Long-context requests are a third dimension. For long-context models, there is a separate rate limit for long-context requests. That means a document-heavy pipeline and an interactive chat workload should not be planned against one shared number; they draw on different buckets even when they use the same key.

Cause one: an ordinary rate limit

This is the case the retry playbook was written for. The response includes rate-limit headers describing the limit, the remaining amount, and when the window resets. The correct response is to back off until the reset, then resume.

Two refinements matter. First, back off by the reset time rather than by a fixed sleep; a fixed sleep either wastes time or retries too early. Second, when you resume, prefer small requests. Re-sending the largest request in the queue immediately after a reset is a good way to consume the fresh budget in one call and be throttled again.

Retry-After, when present, is the minimum number of seconds to wait before retrying a temporary rate-limit error. Treat it as a floor, not a target: waiting exactly that long and retrying everything at once recreates the burst that caused the problem.

Cause two: a shared limit across models

Some model families have shared rate limits. The guide's definition is precise: any models listed under a shared limit on the organization's limit page share a rate limit between them, so calls to any model in that group count toward the same pool.

This is the answer to "I did not hit my limit on this model, so why am I being throttled?" You did not hit that model's own number, because there is no separate number; a sibling model consumed the pool. The fix is architectural rather than tactical: know which of your models are grouped, and treat the group as one capacity budget when you plan traffic or route work between models.

Cause three: the long-context budget

Long-context requests have their own limit, as noted above. Operationally, this means a summarization job over large documents can be throttled while ordinary chat traffic is nowhere near its ceiling, and vice versa.

Budget them separately. If a nightly document pipeline and a daytime assistant share a project, track their consumption against separate expectations, so a document spike is not misread as a general capacity problem. The triage map is the right starting point when the symptom is ambiguous, because it forces the question of which budget was actually exhausted.

Cause four: the hard spend limit

This is the one most often misdiagnosed, because it looks like a rate problem and is not. The guide describes three distinct money-side controls, and only one of them stops traffic.

A spend alert sends a notification and API traffic continues. A hard spend limit is different: affected API requests return a 429 error. Separately, OpenAI sets an approved monthly usage limit for each organization, which is a different control from the spend limits you configure.

So a 429 can mean "you are going too fast" or "you told us to stop at this amount", and the two demand opposite responses. The guide is explicit that retry hints do not apply to quota, billing, or other errors that require user action; retrying cannot resolve them. When a 429 persists without a retry hint, checking the spend configuration is a better next step than increasing the backoff.

This is also why "add more money" is not a universal answer. A hard limit is a policy you set; raising it is a decision with a budget owner, not an engineering retry parameter. And a rate limit is not solved by budget at all.

Usage tiers, and why growth planning is a limit decision

The paid usage tiers are Build, Launch, and Grow. An organization's tier upgrades automatically as its total credit purchases reach each threshold, and higher tiers generally provide higher rate limits across models.

Two planning consequences follow. The first is that capacity is a function of account history, not only of current architecture, so a new project inherits the organization's tier rather than starting its own. The second is timing: a launch or a seasonal peak is the worst moment to discover that the tier was never upgraded, because the upgrade follows purchases rather than a support request. If a campaign is expected to multiply traffic, check the tier and the per-model limits before the campaign, not during it.

The console shows the current limits for each model at your tier, which is the only number that matters for your account. Documentation examples are illustrations.

Reading the headers without hardcoding them

The response headers are the diagnostic layer. The ones worth wiring into your client are x-ratelimit-limit-requests, x-ratelimit-limit-tokens, x-ratelimit-remaining-requests, x-ratelimit-remaining-tokens, x-ratelimit-reset-requests, and Retry-After when present.

Use them in two ways. For retries, respect Retry-After and the reset time instead of a constant. For admission control, use the remaining counts as a client-side limiter: when remaining capacity approaches zero, queue new work locally rather than sending it to be rejected. A request that waits in your own queue costs nothing; a request that is rejected still consumed a round trip and, in some paths, still costs you the attempt.

Do not hardcode the header values or derive thresholds from documentation tables. They vary by model and tier, and the header on the response is the live truth for your account. Log the header values with each failure, following the field discipline in the logging guide, so an incident review can reconstruct which budget ran out.

Two quota surfaces that are easy to miss

Beyond chat traffic, two ingestion paths have their own accounting.

Vector store ingestion is rate limited per vector store ID. For each vector store, the files endpoint and the file_batches endpoint share a limit, and the guide recommends file_batches for larger ingests. A team that spreads ingestion across many small calls to one store is competing with itself; batching is both faster and gentler on the shared limit.

The second surface is batch queue accounting. Batch queue limits are calculated from the total input tokens queued for a model, and tokens from pending jobs count against that queue limit. Once a job completes, its tokens are no longer counted against that model's limit. That is the mechanism behind the operational benefit described in the Batch API guide: moving bulk work into batch does not just change the price, it stops that work from competing with synchronous traffic for the same model budget.

Triage order

  1. Read the response body and headers first; a retry hint points toward a temporary rate condition.
  2. If there is no retry hint and failures persist, check the hard spend limit and the organization's monthly usage limit before retrying again.
  3. Check whether the model belongs to a shared-limit group, and whether a sibling model consumed the pool.
  4. Separate long-context traffic into its own budget and its own expectations.
  5. Add client-side admission control based on remaining capacity, so work queues locally instead of being rejected.
  6. Review the usage tier before a known traffic increase, not after the first failures.
  7. Route large vector-store ingests through the batch endpoint for that store.
  8. Move batch-shaped daytime work into the Batch API so it stops consuming synchronous headroom, and keep cost work in the order described in the cost reduction sequence.

A 429 is information. It says one of several budgets is exhausted, and the headers usually say which. The expensive mistake is answering all of them with the same sleep, because one of those budgets will not refill no matter how long the client waits.

All behaviors and field names above follow the official guide as checked on 2026-11-01; confirm them against the current documentation before relying on them.