Error Handling

Every KalimaLab error has a machine-readable code, a human-readable message, and an HTTP status code.

Error response format

On error, the data field is null and the error, the data field is null and the

error-response.json
{  "data": null,  "error": {    "code": "ERR_VALIDATION",    "message": "Parameter 'letters' must be an integer between 1 and 20.",    "field": "letters"  },  "meta": {    "requestId": "req_01j9abc...",    "responseTimeMs": 2  }}
FieldTypeDescription
error.codestringMachine-readable error identifier. Use this for programmatic error handling.
error.messagestringHuman-readable description. Safe to display to developers, not to end users.
error.fieldstring?Present on ERR_VALIDATION — the name of the invalid parameter.
error.limitnumber?Present on ERR_RATE_LIMIT_EXCEEDED — your daily limit.
error.resetAtstring?Present on ERR_RATE_LIMIT_EXCEEDED — ISO 8601 timestamp of when the limit resets.

All error codes

CodeHTTPDescriptionAction
ERR_AUTH_MISSING401No API key provided.Add an Authorization: Bearer ... or x-api-key header.
ERR_AUTH_INVALID401API key not found.The key does not match any active key. Check for typos.
ERR_AUTH_REVOKED401API key revoked.Create a new key from the dashboard.
ERR_RATE_LIMIT_EXCEEDED429Daily rate limit exceeded.Check the X-RateLimit-Reset header for when the limit resets.
ERR_VALIDATION400Invalid request parameters.The error message identifies the invalid field.
ERR_NOT_FOUND404Resource not found.The requested word, root, or resource ID does not exist.
ERR_PLAN_LIMIT403Feature not available on current plan.Upgrade to access this endpoint or parameter.
ERR_INTERNAL500Internal server error.An unexpected error occurred. Retry with exponential backoff.

Error handling in the SDK

The SDK exports typed error classes for each error code. Use instanceof checks for precise handling:

error-handling.ts
import {  KalimaLab,  RateLimitError,  AuthError,  NotFoundError,  ValidationError,  KalimaLabError,} from '@kalimalab/sdk'const client = new KalimaLab({ apiKey: process.env.KALIMALAB_API_KEY! })async function getWord(id: string) {  try {    return await client.words.get(id)  } catch (err) {    if (err instanceof RateLimitError) {      // Specific: rate limit info available      console.error(`Rate limit hit. Limit: ${err.limit}, resets at: ${err.resetAt}`)      return null    }    if (err instanceof AuthError) {      // Auth problem — check key      console.error(`Auth error [${err.code}]: ${err.message}`)      throw err    }    if (err instanceof NotFoundError) {      // Word doesn't exist      return null    }    if (err instanceof ValidationError) {      // Bad parameters      console.error(`Validation error on field: ${err.field}`)      throw err    }    if (err instanceof KalimaLabError) {      // Any other KalimaLab API error      console.error(`API error ${err.status}: ${err.code}`)    }    throw err  }}

Error handling with native fetch

typescript
async function fetchWord(id: string) {  const res = await fetch(`https://api.kalimalab.com/v1/words/${id}`, {    headers: { Authorization: `Bearer ${process.env.KALIMALAB_API_KEY}` },  })  const body = await res.json()  if (!res.ok) {    const { code, message } = body.error    switch (code) {      case 'ERR_NOT_FOUND':        return null      case 'ERR_RATE_LIMIT_EXCEEDED':        throw new Error(`Rate limited until ${body.error.resetAt}`)      case 'ERR_AUTH_MISSING':      case 'ERR_AUTH_INVALID':      case 'ERR_AUTH_REVOKED':        throw new Error(`Authentication failed: ${message}`)      default:        throw new Error(`API error ${res.status}: ${code}`)    }  }  return body.data}

Retry strategy

Not every error is worth retrying. Here is the recommended retry matrix:

Error / StatusRetry?Strategy
400 ERR_VALIDATIONNoFix request parameters.
401 ERR_AUTH_*NoFix your API key.
403 ERR_PLAN_LIMITNoUpgrade your plan.
404 ERR_NOT_FOUNDNoResource does not exist.
429 ERR_RATE_LIMITYesWait until the X-RateLimit-Reset timestamp.
500 ERR_INTERNALYesExponential backoff: 1s, 2s, 4s, then fail.
Network timeoutYesRetry up to 3 times with backoff.

SDK automatic retry

The SDK automatically retries 500 errors and network timeouts up to maxRetries times (default: 3) with exponential backoff. It does not retry 4xx errors.

Implementing exponential backoff

When calling the API directly (without the SDK), implement exponential backoff for retryable errors. This avoids flooding the API and increases the chance of success:

retry.ts
async function fetchWithRetry(  url: string,  options: RequestInit,  maxRetries = 3,): Promise<Response> {  let attempt = 0  while (attempt <= maxRetries) {    const res = await fetch(url, options)    // Success or a non-retryable client error — return immediately    if (res.ok || (res.status >= 400 && res.status < 500 && res.status !== 429)) {      return res    }    // 429: honour the Retry-After / X-RateLimit-Reset header if present    if (res.status === 429) {      const resetHeader = res.headers.get('X-RateLimit-Reset')      const waitMs = resetHeader        ? Math.max(0, Number(resetHeader) * 1000 - Date.now())        : 2 ** attempt * 1000      console.warn(`Rate limited. Waiting ${waitMs}ms before retry ${attempt + 1}.`)      await new Promise((r) => setTimeout(r, waitMs))    } else {      // 5xx: exponential backoff — 1s, 2s, 4s, 8s …      const waitMs = 2 ** attempt * 1000      console.warn(`Server error ${res.status}. Waiting ${waitMs}ms before retry ${attempt + 1}.`)      await new Promise((r) => setTimeout(r, waitMs))    }    attempt++  }  throw new Error(`Request failed after ${maxRetries} retries`)}// Usageconst res = await fetchWithRetry(  'https://api.kalimalab.com/v1/words?letters=3',  { headers: { Authorization: `Bearer ${process.env.KALIMALAB_API_KEY}` } },)const { data } = await res.json()