Rate Limits & Quotas

RPM windows, token quotas, the response headers, and how 429s behave.

What is limited

LimitScopeWindowExceeded returns
Requests per minute (RPM)Per API keyRolling ~60 s429 rate_limit_exceeded
Project RPMPer projectRolling window429 project_rate_limit_exceeded
Token quota (daily/monthly)Per keyCalendar window429 quota_exceeded
Project token quotaPer projectCalendar window429 project_quota_exceeded
Project monthly cost ceilingPer projectMonthly402 project_quota_exceeded
Per-key USD spend capPer keyRolling402 key_spend_cap_reached

RPM is enforced with a two-bucket weighted sliding window — it bounds you to approximately limit requests per any rolling 60 seconds, rather than a cliff-edge minute boundary.

Response headers

Rate-limit headers are sent on both 200 and 429 responses:

HeaderMeaning
x-ratelimit-limit-requestsRPM limit for the key
x-ratelimit-remaining-requestsRequests remaining in the current rolling minute
x-ratelimit-reset-requestsSeconds until the request window resets (integer seconds)
x-ratelimit-limit-tokensToken quota window limit, when configured
x-ratelimit-remaining-tokensTokens remaining in the quota window
x-ratelimit-reset-tokensSeconds until the token window resets
Retry-After429 only — seconds to wait (see below)

Retry-After semantics

SourceValue
Gateway 429 (rate_limit_exceeded, quota_exceeded, …)Seconds until the tracked window resets
Provider 429 (provider_rate_limited)The provider's own Retry-After, forwarded when present
402Not sent — a funds denial is not congestion (why)

OpenAI SDKs honour Retry-After automatically; if you roll your own retry logic, always wait for it before retrying.

Example 429

{
  "error": {
    "type": "rate_limit_error",
    "message": "Rate limit exceeded",
    "code": "rate_limit_exceeded",
    "param": null,
    "request_id": "req_01J8EXAMPLE"
  }
}
HTTP/1.1 429 Too Many Requests
Retry-After: 17
x-ratelimit-limit-requests: 600
x-ratelimit-remaining-requests: 0
x-ratelimit-reset-requests: 17

Handling provider 429s

A provider-side 429 is preserved as 429 provider_rate_limited with the provider's verbatim message, e.g.:

{
  "error": {
    "type": "rate_limit_error",
    "message": "Rate limit reached for gpt-4o in organization org-x on tokens per min (TPM): Limit 30000, Used 29000, Requested 2000.",
    "code": "provider_rate_limited",
    "param": null,
    "request_id": "req_01J8TESTERR02"
  }
}

Treat it exactly like a gateway 429: honour Retry-After and back off.