Node SDK: Error Handling

Exported errors

import { KeitoApiError, KeitoAuthError, KeitoRateLimitError } from '@keito-ai/sdk';

try {
  await keito.expenses.get(expenseId);
} catch (error) {
  if (error instanceof KeitoAuthError) {
    console.error('Check the key and Company ID.');
  } else if (error instanceof KeitoRateLimitError) {
    console.error('Retry after seconds:', error.retryAfter);
  } else if (error instanceof KeitoApiError) {
    console.error(error.status, error.error, error.error_description);
  } else {
    throw error;
  }
}

Other exported HTTP errors are KeitoForbiddenError, KeitoNotFoundError and KeitoConflictError. KeitoTimeoutError and KeitoConnectionError represent transport failures. They share the KeitoError base class.

HTTP errors expose status, error, error_description, response headers, and the parsed response body when available. Do not assume a validation-specific field or code property: inspect the API response and description.

Safe retries

Configure retries with maxRetries; there is no retryDelay constructor option. For example, new Keito({ maxRetries: 0 }) disables automatic retries while credentials are read from the environment.

Validation and authorization failures need a corrected request or permission change. A 409 or 412 usually needs a fresh read and a new decision; retrying unchanged input is not a general recovery strategy. Respect Retry-After on rate limits.

For supported JSON mutations, pass a stable idempotency key as the final options argument:

const expense = await keito.expenses.create({
  project_id: projectId,
  expense_category_id: categoryId,
  spent_date: '2026-10-30',
  total_cost: 24.50,
  source: 'api',
}, { idempotencyKey: 'receipt-import-unique-001' });

Use a different key for every new operation, and reuse a key only for the identical request after an uncertain response. The server checks current authority before replay. Keep the returned expense ID before starting a receipt upload. Multipart uploads are not automatically replayed; inspect the expense and retry the upload deliberately rather than create another expense.

Timer failures

Keep the ID returned by startTimer. Stop that timer explicitly when work finishes, even when the work itself failed. Whether failed work should be retained, edited or deleted is your application’s decision; the SDK never silently deletes a time entry or creates a compensating expense.