Rate Limits

Learn about KalimaLab rate limits, response headers, and how to build resilient applications.

Limits by plan

Rate limits are applied per API key. All limits reset at midnight UTC.

PlanPriceRequests/dayRequests/monthConcurrent
Free$050015,0005
Starter$9/mo10,000300,00020
ProMost Popular$29/mo100,0003,000,000100
Business$99/mo1,000,00030,000,000Unlimited

Need higher limits? Contact us at enterprise@kalimalab.com for custom plans.

Rate limit response headers

Every API response includes rate limit headers so you can track your usage in real time:

HeaderDescriptionExample
X-RateLimit-LimitYour daily request limit.10000
X-RateLimit-RemainingRequests remaining today.9847
X-RateLimit-ResetUnix timestamp (UTC) when the limit resets.1710374400
X-RateLimit-PolicyRate limit policy (always "day").day
Retry-AfterSeconds to wait before retrying (only present on 429).3600
typescript
const response = await fetch('https://api.kalimalab.com/v1/words/random', {  headers: { Authorization: `Bearer ${process.env.KALIMALAB_API_KEY}` },})const remaining = response.headers.get('X-RateLimit-Remaining')const resetAt = response.headers.get('X-RateLimit-Reset')console.log(`${remaining} requests remaining`)console.log(`Resets at ${new Date(Number(resetAt) * 1000).toISOString()}`)

Handling 429 Too Many Requests

When you exceed your daily limit, the API returns HTTP 429 with this response body:

json
{  "data": null,  "error": {    "code": "ERR_RATE_LIMIT_EXCEEDED",    "message": "Daily request limit exceeded. Your limit resets at 2025-03-10T00:00:00Z.",    "limit": 500,    "remaining": 0,    "resetAt": "2025-03-10T00:00:00Z"  },  "meta": { "requestId": "req_01j9..." }}

Handling 429 gracefully with the SDK

typescript
import { KalimaLab, RateLimitError } from '@kalimalab/sdk'const client = new KalimaLab({ apiKey: process.env.KALIMALAB_API_KEY! })async function fetchWord() {  try {    return await client.words.random()  } catch (err) {    if (err instanceof RateLimitError) {      const resetMs = err.resetAt.getTime() - Date.now()      console.warn(`Rate limit hit. Resets in ${Math.ceil(resetMs / 1000 / 60)} minutes.`)      console.warn(`Daily limit: ${err.limit} requests`)      // Return cached data, queue for later, or surface to user      return null    }    throw err  }}

Handling 429 with native fetch

typescript
async function fetchWithRateLimitHandling(url: string) {  const res = await fetch(url, {    headers: { Authorization: `Bearer ${process.env.KALIMALAB_API_KEY}` },  })  if (res.status === 429) {    const retryAfter = res.headers.get('Retry-After')    const waitSeconds = retryAfter ? Number(retryAfter) : 3600    console.warn(`Rate limited. Try again in ${waitSeconds}s.`)    return null  }  return res.json()}

Best practices for staying within limits

Cache responses

The Word of the Day endpoint returns the same word all day — cache it at the application level. Many word lookups are also stable and can be cached in Redis or local storage.

Use batch endpoints

The /v1/validate endpoint accepts up to 100 words per request. Batch validation requests instead of making one request per word.

Debounce autocomplete

When powering a search field, add a 150–300ms debounce to /v1/words/autocomplete calls to avoid firing on every keystroke.

SDK auto-retry

The SDK automatically retries 5xx errors and network failures with exponential backoff. It does not automatically retry 429 — this is intentional so you can implement your own queue strategy.

Monitor your usage

Check X-RateLimit-Remaining in responses and log a warning when you drop below 10% of your daily limit.

Need more requests?

Upgrade your plan at any time from the dashboard. Changes take effect immediately — no restart required.