A 429 means you went over a limit. Less obvious: there are several limits, and any one of them can trip.
The three kinds of limit
Requests per minute. How often you may call the API. Usually hit by people who parallelise many small tasks.
Input tokens per minute. How much text you send. Anyone pushing large context runs into it: agents, repository work.
Output tokens per minute. How much the model generates. It trips on long answers or on many generations running at once.
The request limit is rarely the binding one. On agentic workloads you reach the input-token limit long before it.
How to retry correctly
An immediate retry only makes things worse: you add load exactly when there is already too much. What you want is a growing pause with a random component.
import random, time
def call_with_retry(fn, attempts=5, base=1.0):
for i in range(attempts):
try:
return fn()
except RateLimitError:
if i == attempts - 1:
raise
delay = base * (2 ** i) + random.uniform(0, 0.5)
time.sleep(delay)
The random part keeps ten workers from retrying at the same instant. Without it you get waves of failures instead of steady throughput.
What the response headers tell you
The API reports how long remains until the limit resets. That beats guessing: if the server asks for twenty seconds, wait twenty, not the thirty-two your formula came up with.
How to avoid the limit
Do not send a whole file when you need one method. The input limit is spent on context, and on agentic runs context piles up.
Use named keys for accounting. Separate keys show background and live usage side by side, but they do not create separate quotas. A client-side queue and rate limiter control the load.
Queue work and bound the request rate. Even sequential requests can exceed a provider quota. Use a bounded queue, a client rate limiter, and the provider's retry guidance when it returns any.
Cache repeated context. If every request drags along the same large instruction, cache it on the provider side.
Separate keys: accounting, not quotas
Name keys by workload (production, background work, experiments) so usage is accounted for separately. Keys do not isolate quotas, so each workload is kept in check by a client queue and rate limiter.
codexkey shows usage statistics per key. When you create a key, pick Premium or Cheap, and set an expiry or a Premium fallback for Cheap if you need one. The dashboard key API does not let you set per-key RPM, TPM, concurrency or budget.
What to remember
Requests, tokens and concurrency can all affect a workload. Keep retries bounded, add jitter, and respect retry headers when they exist. A client queue and rate limiter separate background traffic from live traffic. Named keys give separate usage accounting, not independent quotas.