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.
| Plan | Price | Requests/day | Requests/month | Concurrent |
|---|---|---|---|---|
| Free | $0 | 500 | 15,000 | 5 |
| Starter | $9/mo | 10,000 | 300,000 | 20 |
| ProMost Popular | $29/mo | 100,000 | 3,000,000 | 100 |
| Business | $99/mo | 1,000,000 | 30,000,000 | Unlimited |
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:
| Header | Description | Example |
|---|---|---|
| X-RateLimit-Limit | Your daily request limit. | 10000 |
| X-RateLimit-Remaining | Requests remaining today. | 9847 |
| X-RateLimit-Reset | Unix timestamp (UTC) when the limit resets. | 1710374400 |
| X-RateLimit-Policy | Rate limit policy (always "day"). | day |
| Retry-After | Seconds to wait before retrying (only present on 429). | 3600 |
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:
{ "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
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
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?