Reference
Rate limits & idempotency
Requests are rate limited per key and per workspace, by requests-per-minute. When you are limited, unoblox tells you exactly how long to wait. An Idempotency-Key identifies a request so a retry of it is refused rather than executed twice — it does not replay the original response.
How rate limiting works
The limiter counts requests (not tokens) in a fixed 60-second window, at two levels, checked together:
| Scope | Set by | Default |
|---|---|---|
| Per key | The rpm you set when creating the key (1–600). | 60 if you don't set one |
| Per workspace | A workspace-wide cap configured for the account. | Unlimited unless configured |
A request is rejected if either the key or the workspace is over its cap for the current window. There is no separate tokens-per-minute limit — throughput is governed by your ₹ balance and any budgets, not a TPM cap. A key created with no explicit rpm gets 60, not unlimited — set one explicitly, up to 600, if you need more. Freemium models add a separate fair-use limit (currently 10 requests per minute per workspace, at any balance; see Freemium access). Give each service or environment its own key with its own rpm so one runaway client cannot starve the others.
When you hit a limit
You get a 429 with type: rate_limit_exceeded and a set of headers telling you the cap and when it resets:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 12
Content-Type: application/json
{ "error": { "message": "rate limit exceeded", "type": "rate_limit_exceeded", "code": 429, "request_id": "7b679c45-424f-4153-b72e-e7217758065f" } }| Header | Meaning |
|---|---|
| Retry-After | Seconds to wait before retrying. |
| X-RateLimit-Limit | The applicable per-minute request cap. |
| X-RateLimit-Remaining | Requests left in the window (0 on a 429). |
| X-RateLimit-Reset | Seconds until the window resets. |
These headers are attached to the 429 response only — successful responses do not carry rate-limit headers.
Backing off
Respect Retry-After and retry with exponential backoff. The OpenAI SDK raises RateLimitError on a 429, exposing the response headers:
import os, time
from openai import OpenAI, RateLimitError
client = OpenAI(
base_url="https://api.unoblox.ai/v1",
api_key=os.environ["UNOBLOX_API_KEY"],
)
def create_with_retry(**kwargs):
for attempt in range(5):
try:
return client.chat.completions.create(**kwargs)
except RateLimitError as e:
# Honor Retry-After; fall back to exponential backoff.
wait = float(e.response.headers.get("retry-after", 2 ** attempt))
time.sleep(wait)
raise RuntimeError("still rate limited after retries")Idempotency
Idempotency-Key becomes this request’s id. Its purpose is the opposite of a typical idempotency key: it lets unoblox detect and refuse a retry, not replay one. A durable claim on the id is taken before any money effect; reusing an id you’ve already sent — even after a timeout, a network blip, or a 429/5xx — gets a 400, not the earlier response:
{ "error": { "message": "This request ID has already been used; the response can't be replayed automatically.", "type": "invalid_request", "code": 400, "error_code": "UB-GW-059" } }curl https://api.unoblox.ai/v1/chat/completions \
-H "Authorization: Bearer $UNOBLOX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4821-attempt-1" \
-d '{
"model": "<model-id>",
"messages": [{ "role": "user", "content": "Draft a shipping update." }]
}'| Behavior | Detail |
|---|---|
| Retry with the SAME key | Refused with 400 UB-GW-059. There is no dedup window or response replay — the claim is permanent. |
| Retry with a NEW key | This is the correct way to retry after a timeout/429/5xx: generate a fresh key for the retry attempt. |
| Length | 1–256 bytes, or 400 UB-GW-060. |
| Not a response cache | This is a billing/replay guard, not a cache — it never returns the earlier completion. Deterministic response caching is separate and requires its own opt-in header (see Pricing). |
| No key sent | unoblox generates a request id for you, so accounting still works — you just lose the ability to detect your own retries. |
Inspecting a key's limits
A key's current RPM cap, spend limit, model scope and window usage are shown in the portal under API Keys, and can be read back programmatically via the key-introspection endpoint (it returns rpm_limit, spend_limit, models_scope and usage). See the API reference.
Need a higher limit?
Raise a key's rpm in the portal, or split traffic across keys. Enterprise workspaces can lift the workspace-wide cap — talk to us.