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.

BucketLimitWhy it exists
Per credential60 requests / minuteThe limit that matters to you as an integrator, and the one announced in the headers.
Per IP600 requests / minuteCharged 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
HTTP/1.1 200 OK
X-Request-Id: 9f1c0a3e-7b2d-4f18-9a55-2c6e0d1b4a77
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 41
X-RateLimit-Reset: 1767222060
When it runs out
HTTP/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 429 repeats 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 True

Turns 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.