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_limited and a retry-after header.

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 429 itself — 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 a 429 or a 5xx.
  • 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):

HeaderMeaning
x-ratelimit-limitThe bucket size — how many requests you can send in one burst
x-ratelimit-remainingHow many requests you can send right now
x-ratelimit-resetSeconds until one more request is allowed — 0 while x-ratelimit-remaining is above zero
retry-afterOn 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"
}
FieldMeaning
rpmYour sustained rate, requests per minute
burstThe bucket size
remainingRequests you can send right now
usedRequests taken from the bucket that haven't refilled yet
resets_atWhen 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.