SDK Reference · Errors

Error Handling

Every SDK call can fail. This page covers how the Node.js and Python SDKs surface errors, which exception maps to which HTTP status, and the idiomatic patterns for retrying safely.

The exception model

The Node SDK throws a single BearLumenApiError for every failed request. Branch on its .code property (a stable string) to handle each case. The error also carries .statusCode, .correlationId, and, on a 429, .retryAfter (seconds).

Always keep the correlation ID

Every error from a completed request carries a human-readable reference (error.correlationId). It pinpoints the exact request in our logs, so log it on every failure and quote it in support tickets.

Status and exception reference

Each HTTP status the API returns, what it means, and the exception you catch in each SDK. Errors return the standard envelope with a code, message, and correlationId.

StatusMeaningRetryNode (error.code)
400Invalid request. A parameter or body field is missing or malformed.Noinvalid_request
401Authentication failed. The API key is missing or invalid.Noauthentication_error
403Authenticated, but the API key lacks the scope this endpoint requires.Noforbidden
404The requested resource (end user, dimension, session) doesn't exist.Nonot_found
422The request was well-formed but cannot be processed, such as a date range over 12 months.Noinvalid_request
429Rate limit or plan quota exceeded. Wait for the window to reset, then retry.Yesrate_limit_exceeded
500Something went wrong on our end. Safe to retry with backoff.Yesserver_error
503Temporarily unavailable (for example a brief database issue). Retry with backoff.Yesserver_error

Network failures

A connection that never reaches the API has no HTTP status. In Node these surface as a BearLumenApiError with code network_error (and no correlation ID). In Python they currently surface as the underlying httpx exceptions, so wrap calls in except httpx.HTTPError if you need to catch connectivity issues explicitly.

Handling errors

Catch BearLumenApiError and switch on error.code:

TypeScript
import { BearLumen, BearLumenApiError } from '@bearlumen/node-sdk';

try {
  // Trailing 30 days, resolved client-side. Absolute dates still work:
  // bear.costs.byModel({ startDate: '2026-01-01', endDate: '2026-01-31' })
  const costs = await bear.costs.byModel({ lastDays: 30 });
} catch (error) {
  if (error instanceof BearLumenApiError) {
    switch (error.code) {
      case 'authentication_error':
        console.error('Invalid API key');
        break;
      case 'forbidden':
        console.error('API key is missing a required scope');
        break;
      case 'rate_limit_exceeded':
        console.error('Rate limited, retry after', error.retryAfter, 'seconds');
        break;
      case 'not_found':
        console.error('Resource not found');
        break;
      default:
        console.error('Error', error.code, error.message);
    }
    // Include this in support tickets — it pinpoints the exact request.
    console.error('Correlation ID:', error.correlationId);
  }
}

Retrying safely

Retry the retryable statuses (429 and 5xx) with exponential backoff, honoring error.retryAfter when it is present. Never retry a 4xx: the same request will fail the same way.

TypeScript
import { BearLumenApiError } from '@bearlumen/node-sdk';

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const retryable =
        error instanceof BearLumenApiError &&
        (error.code === 'rate_limit_exceeded' || error.code === 'server_error');
      if (retryable && attempt < maxRetries) {
        const delay = error.retryAfter
          ? error.retryAfter * 1000
          : Math.pow(2, attempt) * 1000;
        await new Promise((resolve) => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}

const result = await withRetry(() => bear.costs.byModel({ lastDays: 30 }));

Best practices

  • Retry only what is retryable. Back off and retry on 429 and 5xx. Never retry 4xx (400, 401, 403, 404, 422). Honor the Retry-After hint on a 429 before falling back to exponential backoff.
  • Flush before your process exits. track() batches events in the background. Call bear.shutdown() (Node) or bear.flush() (Python) before exit, or queued events are lost.
  • Log the correlation ID on every failure. error.correlationId (Node) / error.request_id (Python) identifies the exact request in our logs. Include it in support tickets and your own error reports.
  • Distinguish 401 from 403. A 401 means the key is missing or invalid; a 403 means the key is valid but lacks the scope this endpoint needs. They need different fixes, so handle them separately.

Next steps