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
| Error | HTTP Status | Description |
|---|---|---|
AuthenticationError | 401 | Invalid or missing API key |
ValidationError | 400/422 | Missing or invalid required fields |
TimeoutError | - | Request exceeded timeout (30s) |
ApiError | 4xx/5xx | API returned an error response |
Feature Flags Errors
| Error | HTTP Status | Description |
|---|---|---|
AuthenticationError | 401 | Invalid or missing API key |
FlagNotFoundError | 404 | Feature flag not found |
TypeMismatchError | - | Requested type doesn't match flag's value type |
ValidationError | 400/422 | Missing or invalid required fields |
TimeoutError | - | Request exceeded timeout (30s) |
ApiError | 4xx/5xx | API 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;
}