uttapen

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:

  1. Per-account daily quota: 50 requests per day by default, counted across all free models together. Going over returns 429 free_quota_exceeded with Retry-After: 3600 and, in the uttapen block, an alternative_model plus roughly what it costs. Requests the provider never served (an error before the first byte) are credited back to your quota.
  2. The provider's shared capacity: free capacity is shared across all uttapen users. When the provider returns 429, you get 429 free_capacity_exhausted with a Retry-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-After seriously; hammering earlier only keeps the counter full.
  • key_monthly_limit_reached and free_quota_exceeded don'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, set max_retries=0 so the attempts don't multiply.
  • Under sustained load, a queue (Redis, RabbitMQ) draining slightly below rate_limit_rpm is far steadier than reactive retries.

Defaults at a glance

LimitValueCode
Requests per minute, per key60 (1 to 600)rate_limit_exceeded
Concurrent streams, per account10too_many_concurrent_streams
Monthly limit, per keynone unless you set onekey_monthly_limit_reached
Free models, per account50 requests per dayfree_quota_exceeded
Provider free capacitysharedfree_capacity_exhausted
Request body20 MBrequest_too_large (413)