Documentation
Rate limits
How many requests a credential gets, how they are announced and what to do with a 429.
There are two buckets, and they do not measure the same thing.
| Bucket | Limit | Why it exists |
|---|---|---|
| Per credential | 60 requests / minute | The limit that matters to you as an integrator, and the one announced in the headers. |
| Per IP | 600 requests / minute | Charged before authenticating. Without it, hammering with invalid bearers would be free: verifying the signature and hitting the database already costs, and there is no credential to charge yet. |
The token endpoint has its own per-client_id bucket, also 60 per minute, so a valid credential cannot turn it into an infinite JWT source. One more reason to cache the token instead of requesting one per call.
Note
The per-credential bucket is charged as soon as we know who you are, before checking the scope. A request with the wrong scope costs the server exactly the same work as a correct one, so it consumes quota just the same.
The headers
Every authenticated response carries the bucket state, including error ones: you do not need to succeed to learn how much is left.
HTTP/1.1 200 OK
X-Request-Id: 9f1c0a3e-7b2d-4f18-9a55-2c6e0d1b4a77
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 41
X-RateLimit-Reset: 1767222060HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1767222060
{
"status": "error",
"message": "Demasiados requests",
"message_code": "rate_limit_exceeded",
"data": [],
"meta": { "request_id": "…" }
}Handling a 429
- Honor
Retry-After. It is the exact wait until the window frees up; retrying earlier just burns another unit. - Add jitter. Without it, all your workers retry at the same instant and the
429repeats as a block. - If you are tracking
X-RateLimit-Remaining, throttle before hitting zero: waiting is cheaper than bouncing.
import random
import time
def respect_rate_limit(response) -> bool:
"""Duerme lo que pide el 429 y avisa si conviene reintentar."""
if response.status_code != 429:
return False
wait = float(response.headers.get("Retry-After", "1"))
time.sleep(wait + random.uniform(0, 0.5))
return TrueTurns are a separate matter
The per-minute limit counts requests, not model usage. A turn can run for minutes and burn thousands of tokens while being a single request.
Model usage is governed by the owning user's license: each turn draws from the niucredits pool. When it runs out, the send is rejected even if you have request quota to spare. Usage is available under Analytics.
Note
And alongside it there is the one API turn per chat limit (see Idempotency), which is about consistency, not volume.