Platform

Rate limits

Per-key rate limits, the headers we return, and how to back off.

Every authenticated call to /api/v1 is metered by a per-key rate limiter. Your ceiling is set by your app’s tier (or a per-app override), the limiter counts requests in a fixed 60-second window, and each response tells you exactly where you stand through a set of X-RateLimit-* headers. Exceeding the limit returns HTTP 429 Too Many Requests with a Retry-After header. A separate, coarser per-IP throttle protects the authentication path itself.

How limits are applied

Limits are keyed on the API key, not the app or the owner. Two keys under the same app each get their own independent budget, so you can isolate a noisy integration behind its own key. Because the limiter identity is the key’s hash — never a request header — you cannot raise your ceiling by spoofing X-Forwarded-For or X-Real-IP.

The counter uses a fixed 60-second tumbling window. The first request opens a window; every request in the next 60 seconds counts against the same window; once the window elapses the count resets to zero on the next request. It is not a sliding window, so a burst early in a window is fully replenished at the window boundary rather than gradually.

Buckets are also scoped per route. Your tier limit is applied independently to each endpoint family — for example chat/completions, guilds, usage, personas, memories, and each webhook-delivery operation maintain separate windows. Spending your budget calling one endpoint does not consume another endpoint’s budget. Treat the number in X-RateLimit-Limit as the per-route ceiling and read it from the response rather than assuming an aggregate.

Per-route budgets are a convenience, not a contract. Do not architect around calling many distinct endpoints to multiply throughput — the split may narrow in future. Right-size your traffic to your tier limit and always honor the live headers.

Tiers and limits

An app’s tier determines its per-minute ceiling. If your app carries a positive per-app rate-limit override, that value wins over the tier default. Tiers and overrides are managed by Krallo staff; you cannot change them from the console.

TierRequests per minutePer route
free (default)60Yes
pro600Yes
enterprise6000Yes
Per-app overrideCustom (when set > 0)Yes

An unrecognized or unset tier resolves to the free ceiling. The values above are the platform defaults; the effective limit is always reported in the X-RateLimit-Limit header, which is the single source of truth for your key at the moment of the request. Rate limits are distinct from billing quotas — see Billing & quotas for how metered calls draw down your token balance and free quota, which is enforced separately (and returns 402, not 429).

Rate limit headers

Every response from an authenticated /api/v1 endpoint that reaches the rate limiter carries the three advisory headers below — including the 429 itself. Read them on every response and adapt dynamically rather than hardcoding numbers.

HeaderTypeMeaning
X-RateLimit-Limit integer The maximum number of requests permitted in the current window for this key and route.
X-RateLimit-Remaining integer Requests left in the current window after accounting for this request. Never negative; clamped to 0. A value of 0 means this was your last permitted request until the window resets.
X-RateLimit-Reset integer The Unix time, in seconds since the epoch (UTC), at which the current window ends and the counter resets. This is an absolute timestamp, not a number of seconds to wait.

Example headers on a successful response:

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1752598860

X-RateLimit-Reset is an absolute epoch timestamp. To turn it into a delay, compute reset - now in seconds and clamp at zero. When you receive a 429, prefer the Retry-After header (a plain number of seconds) as the authoritative wait.

Which responses carry the headers

The headers are attached the moment a request passes authentication and any required scope check and reaches the limiter. As a result they appear on:

They are not present on:

Exceeding a limit (429)

When you exceed your per-route ceiling within the window, the request is rejected with HTTP 429 Too Many Requests. The response includes a Retry-After header carrying the number of seconds to wait before retrying.

GET/api/v1/… → 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1752598860

The 429 body identifies the exhausted bucket and restates the retry delay:

{
  "error": "rate_limited",
  "hint": "Too many requests for bucket 'devapi.chat.completions'",
  "retryAfterSeconds": 60
}

The 429 body is not the standard error envelope. Unlike other /api/v1 errors — which return { "error": { "code", "message" }, "meta": { "request_id" } } (see Errors) — a rate-limit rejection returns a flat object where error is the string "rate_limited" and there is no meta.request_id.

Branch on the HTTP status code to detect throttling. Do not assume every error body has a nested error.code field; special-case 429 before parsing.

Retry-After is a fixed, conservative value (currently 60 seconds — the full window length). Because the window is fixed, the precise reset moment is given by X-RateLimit-Reset; you may wait until that timestamp instead of the full Retry-After if you want to resume sooner, but honoring Retry-After is always safe.

The pre-auth per-IP throttle

Before any token is hashed or looked up, the authentication path applies a separate, coarse throttle keyed on the caller’s resolved IP address. Its job is to stop floods of missing, invalid, revoked, or expired tokens from hammering the key store — it is intentionally generous and only trips under abnormal volume, not normal multi-key use.

PropertyValue
ScopePer client IP, across the whole /api/v1 auth path
Default ceiling600 requests per minute
Window60 seconds, fixed
On exceed429 with Retry-After: 60

This throttle runs before the per-key limiter, so its 429 uses the same flat rate_limited body shape but does not carry the X-RateLimit-* headers (those are only set once a valid principal is established). The client IP is derived from X-Forwarded-For / X-Real-IP where present, falling back to the socket address. Because this is a defensive limit on failed and repeated auth attempts, the fix is the same as any 429: back off and retry.

Handling limits in your client

Build throttling into your client rather than reacting only when requests start failing:

Node.js
async function callKrallo(path, init, attempt = 0) {
  const res = await fetch(`https://exe.krallo.com/api/v1${path}`, {
    ...init,
    headers: { Authorization: `Bearer ${process.env.KRALLO_API_KEY}`, ...init?.headers },
  });

  if (res.status !== 429) return res;

  // Prefer Retry-After (seconds); fall back to exponential backoff with jitter.
  const retryAfter = Number(res.headers.get('Retry-After'));
  const backoff = Math.min(60, 2 ** attempt) + Math.random(); // seconds
  const waitSec = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : backoff;

  if (attempt >= 5) throw new Error('Rate limited: exhausted retries');
  await new Promise((r) => setTimeout(r, waitSec * 1000));
  return callKrallo(path, init, attempt + 1);
}

You can also gate outbound requests on the remaining budget before you spend it:

Node.js
const remaining = Number(res.headers.get('X-RateLimit-Remaining'));
const resetAt = Number(res.headers.get('X-RateLimit-Reset')) * 1000; // epoch ms

if (remaining === 0) {
  const waitMs = Math.max(0, resetAt - Date.now());
  await new Promise((r) => setTimeout(r, waitMs));
}

Errors this returns

StatusBody errorWhen
429 rate_limited You exceeded your per-key, per-route ceiling within the 60-second window, or tripped the pre-auth per-IP throttle. Retry after Retry-After seconds.

All other failure modes on these endpoints — authentication, scope, billing, validation — are covered on the Errors page. See also Authentication for key lifecycle and Billing & quotas for how metered usage is accounted separately from rate limiting.