Rate limits — per-minute, streams and quotas
Per-key requests per minute, the X-RateLimit and Retry-After headers, the concurrent stream cap, free-model daily quotas and a correct backoff example.
Updated: September 7, 2026
Four independent limits apply to your requests. You set the first three yourself in the dashboard, or leave them at their defaults; the fourth one only concerns free models. All four return 429 with a distinct code, so your code can tell them apart.
Requests per minute (per key)
Every key has a rate_limit_rpm, 60 by default, adjustable between 1 and 600 in Dashboard → Keys. The window slides — it isn't a calendar minute. All billable routes (chat/completions, completions, embeddings) share the counter; GET /v1/models and GET /v1/uttapen/* are not counted.
These headers are on every response:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1788734238
X-RateLimit-Reset is the Unix time in seconds at which the window clears. Once you hit the ceiling:
HTTP/1.1 429 Too Many Requests
Retry-After: 19
X-RateLimit-Remaining: 0
{"error":{"message":"This key has reached its limit of 60 requests per minute.","type":"rate_limit_exceeded","code":"rate_limit_exceeded","param":null}}
If you need more, raise the number on the key itself — no need to contact us. For heavy parallel workloads (batch document processing, say), create several named keys so that both the counter and the usage report are split per workload.
Concurrent streams (per account)
Each account can keep at most 10 streams open at the same time, no matter how many keys are involved. The eleventh gets 429 too_many_concurrent_streams with Retry-After: 5. The counter frees up the instant a stream ends or is cancelled. Non-streaming requests don't count here — they are bounded by RPM alone.
Monthly key limit
If monthly_limit_toman is set on a key, the settled cost of that key's requests in the current Jalali month plus any open holds is compared against the limit. Going over returns 429 key_monthly_limit_reached, with an uttapen block carrying monthly_limit_toman and spent_toman. The check happens inside the same transaction that places the hold, so two parallel requests can't slip past the limit together. The counter resets on the first day of the Jalali month.
Free models
Models with the :free suffix work without any balance, but they carry two caps:
- Per-account daily quota: 50 requests per day by default, counted across all free models together. Going over returns
429 free_quota_exceededwithRetry-After: 3600and, in theuttapenblock, analternative_modelplus roughly what it costs. Requests the provider never served (an error before the first byte) are credited back to your quota. - The provider's shared capacity: free capacity is shared across all uttapen users. When the provider returns
429, you get429 free_capacity_exhaustedwith aRetry-After. This one is out of our hands; at busy hours it's normal.
Free models are for testing and prototyping. For anything a real user is waiting on, a cheap paid model such as openai/gpt-5-nano or google/gemini-2.5-flash-lite is far more dependable.
Provider-side limits
Sometimes the provider itself throttles a model. You get 429 upstream_rate_limited. If that happens three times in a row for the same model, the gateway marks it "cold" for a few dozen seconds and returns 429 with a Retry-After without going upstream at all, so you don't waste time waiting. A comparable model from another provider (google/gemini-2.5-flash in place of openai/gpt-5-mini, for instance) is usually available at that very moment; the models field does exactly this as an automatic fallback (migration).
Backoff done right
import random, time
import openai
def create_with_retry(**kwargs):
for attempt in range(4):
try:
return client.chat.completions.create(**kwargs)
except openai.RateLimitError as e:
code = e.code
if code in ("key_monthly_limit_reached", "free_quota_exceeded"):
raise # waiting will not help
retry_after = e.response.headers.get("retry-after")
wait = float(retry_after) if retry_after else min(2 ** attempt, 20)
time.sleep(wait + random.random())
except openai.InternalServerError:
time.sleep(min(2 ** attempt, 20) + random.random())
raise RuntimeError("uttapen: too many retries")
A few notes:
- Take
Retry-Afterseriously; hammering earlier only keeps the counter full. key_monthly_limit_reachedandfree_quota_exceededdon't resolve themselves with time — keep them out of the retry loop.- The official SDK already retries twice on its own (
max_retries). If you have your own retry layer, setmax_retries=0so the attempts don't multiply. - Under sustained load, a queue (Redis, RabbitMQ) draining slightly below
rate_limit_rpmis far steadier than reactive retries.
Defaults at a glance
| Limit | Value | Code |
|---|---|---|
| Requests per minute, per key | 60 (1 to 600) | rate_limit_exceeded |
| Concurrent streams, per account | 10 | too_many_concurrent_streams |
| Monthly limit, per key | none unless you set one | key_monthly_limit_reached |
| Free models, per account | 50 requests per day | free_quota_exceeded |
| Provider free capacity | shared | free_capacity_exhausted |
| Request body | 20 MB | request_too_large (413) |