Skip to content

Error Handling

Learn how to handle errors gracefully when using the Kitbase SDK.

Overview

The TypeScript, Dart, and PHP SDKs provide typed error classes that allow you to handle specific error conditions precisely. This enables you to provide better user experiences and implement robust retry logic.

Error Types

Events Errors

ErrorHTTP StatusDescription
AuthenticationError401Invalid or missing API key
ValidationError400/422Missing or invalid required fields
TimeoutError-Request exceeded timeout (30s)
ApiError4xx/5xxAPI returned an error response

Feature Flags Errors

ErrorHTTP StatusDescription
AuthenticationError401Invalid or missing API key
FlagNotFoundError404Feature flag not found
TypeMismatchError-Requested type doesn't match flag's value type
ValidationError400/422Missing or invalid required fields
TimeoutError-Request exceeded timeout (30s)
ApiError4xx/5xxAPI returned an error response
InvalidContextError-Evaluation context is invalid
ParseError-Failed to parse flag configuration

TypeScript

Events Error Handling

typescript
import {
  Kitbase,
  KitbaseError,
  AuthenticationError,
  ApiError,
  ValidationError,
  TimeoutError,
} from '@kitbase/analytics';

const kitbase = new Kitbase({ token: '<YOUR_API_KEY>' });

try {
  await kitbase.track({
    channel: 'payments',
    event: 'New Subscription',
  });
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Invalid API key - check your token
    console.error('Authentication failed');
  } else if (error instanceof ValidationError) {
    // Missing required fields
    console.error(`Validation error: ${error.field}`);
  } else if (error instanceof TimeoutError) {
    // Request timed out - retry later
    console.error('Request timed out');
  } else if (error instanceof ApiError) {
    // API returned an error
    console.error(`API error: ${error.statusCode}`);
    console.error(error.response);
  } else if (error instanceof KitbaseError) {
    // Generic SDK error
    console.error(error.message);
  }
}

Retry with Exponential Backoff

typescript
import type { TrackOptions, TrackResponse } from '@kitbase/analytics';
import { TimeoutError, ApiError } from '@kitbase/analytics';

async function trackWithRetry(
  kitbase: Kitbase,
  options: TrackOptions,
  retries = 3
): Promise<TrackResponse> {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      return await kitbase.track(options);
    } catch (error) {
      // Don't retry auth or validation errors
      if (error instanceof AuthenticationError) throw error;
      if (error instanceof ValidationError) throw error;

      // Retry on timeout or server errors
      const shouldRetry =
        error instanceof TimeoutError ||
        (error instanceof ApiError && error.statusCode >= 500);

      if (shouldRetry && attempt < retries) {
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise((resolve) => setTimeout(resolve, delay));
        continue;
      }

      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}

Feature Flags Error Handling

typescript
import {
  FlagsClient,
  FlagsError,
  AuthenticationError,
  FlagNotFoundError,
  TypeMismatchError,
  ValidationError,
  TimeoutError,
  ApiError,
} from '@kitbase/flags';

const flags = new FlagsClient({ token: '<YOUR_API_KEY>' });

try {
  const isEnabled = await flags.getBooleanValue('dark-mode', false, {
    targetingKey: 'user-123',
  });
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Invalid API key - check your token
    console.error('Authentication failed');
  } else if (error instanceof FlagNotFoundError) {
    // Flag doesn't exist
    console.error(`Flag ${error.flagKey} not found`);
  } else if (error instanceof TypeMismatchError) {
    // Requested wrong type
    console.error(`Expected ${error.expectedType}, got ${error.actualType}`);
  } else if (error instanceof ValidationError) {
    // Missing required fields
    console.error(`Validation error: ${error.field}`);
  } else if (error instanceof TimeoutError) {
    // Request timed out
    console.error('Request timed out');
  } else if (error instanceof ApiError) {
    // API returned an error
    console.error(`API error: ${error.statusCode}`);
  } else if (error instanceof FlagsError) {
    // Generic flags error
    console.error(error.message);
  }
}

Dart

Events Error Handling

dart
import 'package:kitbase_events/kitbase_events.dart';

final kitbase = Kitbase(token: '<YOUR_API_KEY>');

try {
  await kitbase.track(
    channel: 'payments',
    event: 'New Subscription',
  );
} on AuthenticationException {
  // Invalid API key - check your token
  print('Authentication failed');
} on ValidationException catch (e) {
  // Missing required fields
  print('Validation error: ${e.field}');
} on TimeoutException {
  // Request timed out - retry later
  print('Request timed out');
} on ApiException catch (e) {
  // API returned an error
  print('API error: ${e.statusCode}');
  print(e.response);
} on KitbaseException catch (e) {
  // Generic SDK error
  print(e.message);
}

Retry with Exponential Backoff

dart
import 'dart:math';
import 'package:kitbase_events/kitbase_events.dart';

Future<TrackResponse> trackWithRetry(
  Kitbase kitbase, {
  required String channel,
  required String event,
  int retries = 3,
}) async {
  for (var attempt = 1; attempt <= retries; attempt++) {
    try {
      return await kitbase.track(channel: channel, event: event);
    } on AuthenticationException {
      rethrow; // Don't retry
    } on ValidationException {
      rethrow; // Don't retry
    } on TimeoutException {
      if (attempt < retries) {
        await Future.delayed(Duration(seconds: pow(2, attempt).toInt()));
        continue;
      }
      rethrow;
    } on ApiException catch (e) {
      if (e.statusCode >= 500 && attempt < retries) {
        await Future.delayed(Duration(seconds: pow(2, attempt).toInt()));
        continue;
      }
      rethrow;
    }
  }
  throw const KitbaseException('Max retries exceeded');
}

Feature Flags Error Handling

dart
import 'package:kitbase/flags.dart';

final flags = KitbaseFlags(token: '<YOUR_API_KEY>');

try {
  final isEnabled = await flags.getBooleanValue(
    'dark-mode',
    false,
    context: EvaluationContext(targetingKey: 'user-123'),
  );
} on FlagsAuthenticationException {
  // Invalid API key - check your token
  print('Authentication failed');
} on FlagNotFoundException catch (e) {
  // Flag doesn't exist
  print('Flag ${e.flagKey} not found');
} on TypeMismatchException catch (e) {
  // Requested wrong type
  print('Expected ${e.expectedType}, got ${e.actualType}');
} on FlagsValidationException catch (e) {
  // Missing required fields
  print('Validation error: ${e.field}');
} on FlagsTimeoutException {
  // Request timed out
  print('Request timed out');
} on FlagsApiException catch (e) {
  // API returned an error
  print('API error: ${e.statusCode}');
} on KitbaseFlagsException catch (e) {
  // Generic flags error
  print(e.message);
}

Error Properties

TypeScript

AuthenticationError

typescript
interface AuthenticationError extends KitbaseError {
  message: string; // "Invalid or missing API key"
}

ValidationError

typescript
interface ValidationError extends KitbaseError {
  message: string; // Error description
  field: string;   // The field that failed validation
}

TimeoutError

typescript
interface TimeoutError extends KitbaseError {
  message: string; // "Request timed out"
}

ApiError

typescript
interface ApiError extends KitbaseError {
  message: string;      // Error description from API
  statusCode: number;   // HTTP status code
  response: unknown;    // Raw response body
}

FlagNotFoundError (Flags only)

typescript
interface FlagNotFoundError extends FlagsError {
  message: string;  // Error description
  flagKey: string;  // The flag key that wasn't found
}

TypeMismatchError (Flags only)

typescript
interface TypeMismatchError extends FlagsError {
  message: string;      // Error description
  flagKey: string;      // The flag key
  expectedType: string; // The type you requested
  actualType: string;   // The actual flag type
}

Dart

AuthenticationException

dart
class AuthenticationException extends KitbaseException {
  final String message; // "Invalid or missing API key"
}

ValidationException

dart
class ValidationException extends KitbaseException {
  final String message; // Error description
  final String? field;  // The field that failed validation
}

TimeoutException

dart
class TimeoutException extends KitbaseException {
  final String message; // "Request timed out"
}

ApiException

dart
class ApiException extends KitbaseException {
  final String message;    // Error description from API
  final int statusCode;    // HTTP status code
  final dynamic response;  // Raw response body
}

FlagNotFoundException (Flags only)

dart
class FlagNotFoundException extends KitbaseFlagsException {
  final String message; // Error description
  final String flagKey; // The flag key that wasn't found
}

TypeMismatchException (Flags only)

dart
class TypeMismatchException extends KitbaseFlagsException {
  final String message;      // Error description
  final String flagKey;      // The flag key
  final String expectedType; // The type you requested
  final String actualType;   // The actual flag type
}

Best Practices

1. Don't Retry Authentication Errors

Authentication errors indicate a configuration problem. Log these prominently and don't retry:

typescript
if (error instanceof AuthenticationError) {
  logger.error('Kitbase authentication failed - check API key');
  notifyOps('Kitbase API key is invalid');
  throw error; // Don't retry
}

2. Implement Exponential Backoff

For transient errors (timeouts, server errors), use exponential backoff:

typescript
const delay = Math.min(Math.pow(2, attempt) * 1000, 30000); // Max 30s
await new Promise(resolve => setTimeout(resolve, delay));

3. Log Errors for Debugging

Include context when logging errors:

typescript
} catch (error) {
  logger.error('Failed to track event', {
    channel: options.channel,
    event: options.event,
    error: error instanceof KitbaseError ? {
      type: error.constructor.name,
      message: error.message,
    } : error,
  });
}

4. Graceful Degradation

Don't let tracking failures break your application:

typescript
async function safeTrack(options: TrackOptions) {
  try {
    await kitbase.track(options);
  } catch (error) {
    // Log but don't throw - tracking is non-critical
    console.error('Event tracking failed:', error);
  }
}

5. Validate Before Sending

Catch validation errors early:

typescript
function validateTrackOptions(options: TrackOptions): void {
  if (!options.channel?.trim()) {
    throw new Error('Channel is required');
  }
  if (!options.event?.trim()) {
    throw new Error('Event is required');
  }
}

6. Use Default Values for Feature Flags

Feature flags should always gracefully fall back to defaults:

typescript
async function getFeatureFlag<T>(
  flagKey: string,
  defaultValue: T,
  context: EvaluationContext
): Promise<T> {
  try {
    return await flags.getBooleanValue(flagKey, defaultValue, context);
  } catch (error) {
    // Log but don't throw - return default value
    console.error(`Flag evaluation failed for ${flagKey}:`, error);
    return defaultValue;
  }
}

7. Handle TypeMismatchError for Flags

When the flag type doesn't match what you requested:

typescript
try {
  const value = await flags.getBooleanValue('my-flag', false, context);
} catch (error) {
  if (error instanceof TypeMismatchError) {
    // Flag exists but is wrong type - check your flag configuration
    console.error(`Flag ${error.flagKey} is ${error.actualType}, not ${error.expectedType}`);
    // Fall back to default
    return false;
  }
  throw error;
}

Released under the MIT License.