Skip to content
Core Concepts

Rate Limits

Kirim applies per-organisation, per-endpoint-class token-bucket rate limits. Limits are enforced atomically by a Redis Lua script — there are no race conditions across replicas.

ClassHTTP methodsExamples
writePOST, PUT, PATCH, DELETESend a message, attach a label, replay a webhook delivery
readGET, HEADList conversations, fetch a message, introspect via /me

Each class has its own bucket. Sending 60 messages does not eat into your read budget, and vice versa.

PlanWrite / minRead / min
Default (free)60600
Pro6006000
Enterpriseconfigurableconfigurable

Upgrade your plan in the dashboard to lift limits. Enterprise plans get custom per-key overrides on request — contact support.

Every successful response and every 4xx error carries the same three headers so client code can self-throttle without parsing the body:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1716480000
HeaderMeaning
X-RateLimit-LimitBucket capacity for the class you just hit, requests per minute.
X-RateLimit-RemainingTokens currently available (floored to integer).
X-RateLimit-ResetUnix epoch seconds when the bucket would be full at the current refill rate.

On a 429 there is one extra header:

Retry-After: 12

Retry-After is the number of seconds until at least one token becomes available again. Sleep at least that long before retrying.

async function callWithBackoff(request: RequestFn, maxAttempts = 5) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const res = await request()
if (res.status !== 429) return res
const retryAfter = Number(res.headers.get('retry-after') ?? 1)
// Add jitter so a thundering herd of retries doesn't all wake up
// in the same millisecond and trip the limiter again.
const jitter = Math.random() * 0.5 + 0.75 // 0.75x ~ 1.25x
await sleep(retryAfter * 1000 * jitter)
}
throw new Error('Rate limit retry budget exhausted')
}
  • Don’t fire requests as fast as Remaining allows. The bucket refills continuously, but bursting it to zero and immediately retrying just trades a 429 for another 429.
  • Don’t ignore Retry-After. It’s accurate. Retrying earlier guarantees a second 429 and consumes attempts you’ll need later.
  • Don’t share one API key across high-volume consumers. The bucket is per-organisation, not per-key — so two consumers hammering the same org’s key drain the same bucket. Use one key per consumer for clean isolation in audit logs, but accept that they share the budget.
  • Idempotency — safely retry without duplicating side effects.
  • Errors — full error envelope reference.