Rate limits
Calls to /api/v1 are rate-limited per account with a token bucket: you can send a
burst of requests straight away, then keep going at your sustained rate.
How the bucket works
- The bucket holds up to burst requests — 20 by default — and starts full.
- Every request takes one. The bucket refills at your sustained rate: your requests per minute (RPM), the highest across your active plans unless your account has its own setting. At 60 RPM it gains one request a second.
- When it is empty the call is refused with
429 rate_limitedand aretry-afterheader.
So with 60 RPM you can fire 20 requests at once, then about one a second after that — and after a quiet spell the full burst is available again. Each plan's RPM is listed on Pricing; yours is on your dashboard.
What counts
Only requests that actually run use up the bucket:
- A refused request — including the
429itself — takes nothing, so retrying never digs you deeper. - Requests turned down before they run (
400,402,403) get their request back, and so do requests that fail upstream with a429or a5xx. - Chat and service calls share one bucket. BYOK calls count too.
Response headers
Every /api/v1 response carries the bucket's state (a 401 doesn't — the bucket belongs
to an account, and that call never got one):
| Header | Meaning |
|---|---|
x-ratelimit-limit | The bucket size — how many requests you can send in one burst |
x-ratelimit-remaining | How many requests you can send right now |
x-ratelimit-reset | Seconds until one more request is allowed — 0 while x-ratelimit-remaining is above zero |
retry-after | On every 429: seconds until one more request is allowed. Wait at least this long before retrying |
Both x-ratelimit-reset and retry-after count down to the next request, not to a
full bucket.
HTTP/1.1 429 Too Many Requests
retry-after: 2
x-ratelimit-limit: 20
x-ratelimit-remaining: 0
x-ratelimit-reset: 2
{ "error": { "code": "rate_limited", "message": "Rate limit exceeded", "details": {} } }
Per-IP limit
Separately, each client IP address has its own bucket: 300 requests per minute, burst 100. It
only matters when many accounts or scripts share one address. Going over it answers the same
429 rate_limited with retry-after.
Reading your limit: GET /api/v1/key-info
key-info reports the bucket as rate_limit, so an app can pace
itself before it hits a 429:
"rate_limit": {
"rpm": 60,
"burst": 20,
"remaining": 17,
"used": 3,
"resets_at": "2026-10-29T10:15:03.000Z"
}
| Field | Meaning |
|---|---|
rpm | Your sustained rate, requests per minute |
burst | The bucket size |
remaining | Requests you can send right now |
used | Requests taken from the bucket that haven't refilled yet |
resets_at | When at least one more request is allowed — the same moment x-ratelimit-reset counts down to |
Reading key-info doesn't use up a request.
Pacing a client
async function call(body) {
for (;;) {
const r = await fetch("https://khabot.com/api/v1/chat/completions", {
method: "POST",
headers: { Authorization: "Bearer " + KEY, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (r.status !== 429) return r;
const wait = Number(r.headers.get("retry-after")) || 1;
await new Promise((ok) => setTimeout(ok, wait * 1000));
}
}
Agents that fan out many calls can watch x-ratelimit-remaining and slow down before it reaches
zero.