Error Handling Best Practices
Robust error handling is critical for production applications. This guide covers comprehensive strategies for handling StateSet API errors gracefully, implementing retry logic, and maintaining application reliability.Understanding StateSet Error Responses
Error Response Structure
All StateSet API errors follow a consistent structure:Copy
Ask AI
{
"error": {
"type": "invalid_request_error",
"code": "INVALID_PARAMETER",
"message": "The 'email' parameter is required",
"param": "email",
"request_id": "req_abc123",
"documentation_url": "https://docs.stateset.com/api-reference/errors#invalid-parameter"
},
"timestamp": "2024-01-15T10:30:00Z"
}
Error Categories
Client Errors (4xx)
Errors in your request
- Authentication failures
- Invalid parameters
- Rate limiting
- Resource not found
Server Errors (5xx)
Temporary service issues
- Internal server errors
- Service unavailable
- Gateway timeouts
- Maintenance mode
Common Error Codes
Authentication Errors
Cause: Invalid or missing API key
Copy
Ask AI
// ❌ Common mistake
const client = new StateSetClient({
apiKey: 'wrong_key_format'
});
// ✅ Proper handling
const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY
});
if (!process.env.STATESET_API_KEY) {
throw new Error('STATESET_API_KEY environment variable is required');
}
try {
const customers = await client.customers.list();
} catch (error) {
if (error.code === 'UNAUTHORIZED') {
logger.error('Authentication failed - check API key', {
requestId: error.request_id,
documentation: error.documentation_url
});
// Notify operations team
await notifyAuthenticationFailure(error);
}
throw error;
}
Validation Errors
Copy
Ask AI
// ✅ Comprehensive validation error handling
async function createCustomerSafely(customerData) {
try {
const customer = await client.customers.create(customerData);
logger.info('Customer created successfully', { customerId: customer.id });
return customer;
} catch (error) {
if (error.code === 'INVALID_PARAMETER') {
const validationErrors = error.details?.validation_errors || [];
logger.warn('Customer creation failed - validation errors', {
errors: validationErrors,
requestId: error.request_id,
inputData: sanitizeForLogging(customerData)
});
// Return user-friendly error messages
return {
success: false,
errors: validationErrors.map(err => ({
field: err.param,
message: err.message,
code: err.code
}))
};
}
if (error.code === 'DUPLICATE_RESOURCE') {
logger.info('Customer already exists', {
email: customerData.email,
existingId: error.details?.existing_resource_id
});
// Return existing customer
return await client.customers.get(error.details.existing_resource_id);
}
throw error; // Re-throw unexpected errors
}
}
// Helper function to sanitize sensitive data
function sanitizeForLogging(data) {
const sanitized = { ...data };
if (sanitized.email) sanitized.email = maskEmail(sanitized.email);
if (sanitized.phone) sanitized.phone = maskPhone(sanitized.phone);
return sanitized;
}
Rate Limiting
Copy
Ask AI
// ✅ Intelligent rate limit handling with exponential backoff
class RateLimitHandler {
constructor(options = {}) {
this.maxRetries = options.maxRetries || 3;
this.baseDelay = options.baseDelay || 1000;
this.maxDelay = options.maxDelay || 30000;
this.jitterFactor = options.jitterFactor || 0.1;
}
async executeWithRetry(operation, context = {}) {
let lastError;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (error.code !== 'RATE_LIMIT_EXCEEDED') {
throw error; // Not a rate limit error
}
if (attempt === this.maxRetries) {
logger.error('Max retries exceeded for rate limited request', {
attempts: attempt + 1,
context,
requestId: error.request_id
});
throw error;
}
const delay = this.calculateDelay(attempt, error);
logger.warn('Rate limited - retrying request', {
attempt: attempt + 1,
delayMs: delay,
retryAfter: error.headers?.['retry-after'],
context,
requestId: error.request_id
});
await this.sleep(delay);
}
}
throw lastError;
}
calculateDelay(attempt, error) {
// Use server-provided retry-after if available
const retryAfter = error.headers?.['retry-after'];
if (retryAfter) {
return Math.min(parseInt(retryAfter) * 1000, this.maxDelay);
}
// Exponential backoff with jitter
const exponentialDelay = this.baseDelay * Math.pow(2, attempt);
const jitter = exponentialDelay * this.jitterFactor * Math.random();
return Math.min(exponentialDelay + jitter, this.maxDelay);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage example
const rateLimitHandler = new RateLimitHandler({
maxRetries: 5,
baseDelay: 1000,
maxDelay: 60000
});
async function createCustomerWithRetry(customerData) {
return await rateLimitHandler.executeWithRetry(
() => client.customers.create(customerData),
{ operation: 'customer.create', email: customerData.email }
);
}
Advanced Error Handling Patterns
Circuit Breaker Pattern
Copy
Ask AI
// ✅ Circuit breaker to prevent cascading failures
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.timeout = options.timeout || 60000;
this.monitoringPeriod = options.monitoringPeriod || 10000;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.failureCount = 0;
this.lastFailureTime = null;
this.nextAttempt = null;
}
async execute(operation, fallback = null) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
logger.warn('Circuit breaker is OPEN - using fallback', {
nextAttempt: new Date(this.nextAttempt).toISOString(),
failureCount: this.failureCount
});
if (fallback) return await fallback();
throw new Error('Circuit breaker is OPEN - service unavailable');
}
this.state = 'HALF_OPEN';
logger.info('Circuit breaker entering HALF_OPEN state');
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
logger.info('Circuit breaker reset to CLOSED state');
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
logger.error('Circuit breaker opened due to failures', {
failureCount: this.failureCount,
nextAttempt: new Date(this.nextAttempt).toISOString()
});
}
}
}
// Usage with StateSet API
const circuitBreaker = new CircuitBreaker({
failureThreshold: 3,
timeout: 30000
});
async function getCustomerWithFallback(customerId) {
return await circuitBreaker.execute(
() => client.customers.get(customerId),
() => getCachedCustomer(customerId) // Fallback to cache
);
}
Batch Operations Error Handling
Copy
Ask AI
// ✅ Robust batch processing with partial failure handling
class BatchProcessor {
constructor(options = {}) {
this.batchSize = options.batchSize || 100;
this.maxConcurrency = options.maxConcurrency || 5;
this.retryFailures = options.retryFailures !== false;
}
async processBatch(items, processor) {
const batches = this.createBatches(items);
const results = [];
const failures = [];
for (const batch of batches) {
try {
const batchResults = await this.processBatchConcurrently(batch, processor);
results.push(...batchResults.successes);
failures.push(...batchResults.failures);
} catch (error) {
logger.error('Batch processing failed completely', {
batchSize: batch.length,
error: error.message
});
// Add all items in failed batch to failures
failures.push(...batch.map(item => ({
item,
error: error.message,
retryable: this.isRetryableError(error)
})));
}
}
// Retry failures if enabled
if (this.retryFailures && failures.length > 0) {
const retryableFailures = failures.filter(f => f.retryable);
if (retryableFailures.length > 0) {
logger.info('Retrying failed batch items', {
retryCount: retryableFailures.length,
totalFailures: failures.length
});
const retryResults = await this.retryFailures(retryableFailures, processor);
results.push(...retryResults.successes);
// Update failures list with remaining failures
const remainingFailures = failures.filter(f => !f.retryable);
remainingFailures.push(...retryResults.failures);
}
}
return {
successes: results,
failures,
summary: {
total: items.length,
succeeded: results.length,
failed: failures.length,
successRate: (results.length / items.length) * 100
}
};
}
createBatches(items) {
const batches = [];
for (let i = 0; i < items.length; i += this.batchSize) {
batches.push(items.slice(i, i + this.batchSize));
}
return batches;
}
async processBatchConcurrently(batch, processor) {
const semaphore = new Semaphore(this.maxConcurrency);
const successes = [];
const failures = [];
const promises = batch.map(async (item, index) => {
await semaphore.acquire();
try {
const result = await processor(item, index);
successes.push({ item, result });
} catch (error) {
failures.push({
item,
error: error.message,
code: error.code,
retryable: this.isRetryableError(error)
});
} finally {
semaphore.release();
}
});
await Promise.all(promises);
return { successes, failures };
}
isRetryableError(error) {
const retryableCodes = [
'RATE_LIMIT_EXCEEDED',
'INTERNAL_SERVER_ERROR',
'SERVICE_UNAVAILABLE',
'GATEWAY_TIMEOUT'
];
return retryableCodes.includes(error.code);
}
}
// Simple semaphore implementation
class Semaphore {
constructor(limit) {
this.limit = limit;
this.current = 0;
this.queue = [];
}
async acquire() {
return new Promise((resolve) => {
if (this.current < this.limit) {
this.current++;
resolve();
} else {
this.queue.push(resolve);
}
});
}
release() {
this.current--;
if (this.queue.length > 0) {
const next = this.queue.shift();
this.current++;
next();
}
}
}
// Usage example
const batchProcessor = new BatchProcessor({
batchSize: 50,
maxConcurrency: 3
});
async function createCustomersBatch(customerDataList) {
return await batchProcessor.processBatch(
customerDataList,
async (customerData) => {
return await client.customers.create(customerData);
}
);
}
Production Monitoring & Alerting
Error Tracking
Copy
Ask AI
// ✅ Comprehensive error tracking and metrics
class ErrorTracker {
constructor() {
this.errorCounts = new Map();
this.errorRates = new Map();
this.alertThresholds = {
errorRate: 0.05, // 5% error rate
consecutiveErrors: 10,
criticalErrors: ['UNAUTHORIZED', 'FORBIDDEN']
};
}
trackError(error, context = {}) {
const errorKey = `${error.code}_${context.operation || 'unknown'}`;
// Update error counts
this.errorCounts.set(errorKey, (this.errorCounts.get(errorKey) || 0) + 1);
// Log error with context
logger.error('StateSet API error tracked', {
code: error.code,
message: error.message,
operation: context.operation,
requestId: error.request_id,
timestamp: new Date().toISOString(),
...context
});
// Check for critical errors
if (this.alertThresholds.criticalErrors.includes(error.code)) {
this.sendCriticalAlert(error, context);
}
// Check error rate thresholds
this.checkErrorRateThreshold(errorKey, context);
}
trackSuccess(context = {}) {
const operationKey = context.operation || 'unknown';
this.updateSuccessRate(operationKey);
}
sendCriticalAlert(error, context) {
// Integration with alerting system (PagerDuty, Slack, etc.)
const alert = {
severity: 'critical',
title: `StateSet API Critical Error: ${error.code}`,
description: error.message,
context,
timestamp: new Date().toISOString(),
requestId: error.request_id
};
// Send alert to monitoring system
this.sendAlert(alert);
}
async sendAlert(alert) {
try {
// Example: Send to Slack webhook
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `🚨 ${alert.title}`,
attachments: [{
color: alert.severity === 'critical' ? 'danger' : 'warning',
fields: [
{ title: 'Description', value: alert.description, short: false },
{ title: 'Request ID', value: alert.requestId, short: true },
{ title: 'Context', value: JSON.stringify(alert.context), short: true }
]
}]
})
});
} catch (alertError) {
logger.error('Failed to send alert', {
originalAlert: alert,
alertError: alertError.message
});
}
}
}
// Global error tracker instance
const errorTracker = new ErrorTracker();
// Enhanced API client with error tracking
class TrackedStateSetClient {
constructor(options) {
this.client = new StateSetClient(options);
this.errorTracker = errorTracker;
}
async makeRequest(operation, requestFn) {
try {
const result = await requestFn();
this.errorTracker.trackSuccess({ operation });
return result;
} catch (error) {
this.errorTracker.trackError(error, { operation });
throw error;
}
}
// Wrap all client methods
get customers() {
return {
create: (data) => this.makeRequest('customers.create', () =>
this.client.customers.create(data)
),
get: (id) => this.makeRequest('customers.get', () =>
this.client.customers.get(id)
),
list: (params) => this.makeRequest('customers.list', () =>
this.client.customers.list(params)
)
// ... other methods
};
}
}
Testing Error Scenarios
Copy
Ask AI
// ✅ Comprehensive error scenario testing
describe('StateSet API Error Handling', () => {
let client;
beforeEach(() => {
client = new TrackedStateSetClient({
apiKey: process.env.STATESET_TEST_API_KEY
});
});
describe('Authentication Errors', () => {
test('handles invalid API key gracefully', async () => {
const invalidClient = new StateSetClient({ apiKey: 'invalid_key' });
await expect(invalidClient.customers.list()).rejects.toMatchObject({
code: 'UNAUTHORIZED',
message: expect.stringContaining('Invalid API key')
});
});
test('handles missing API key', async () => {
expect(() => new StateSetClient({})).toThrow('API key is required');
});
});
describe('Rate Limiting', () => {
test('handles rate limit with retry', async () => {
const rateLimitHandler = new RateLimitHandler({ maxRetries: 2 });
// Mock rate limited responses
const mockOperation = jest.fn()
.mockRejectedValueOnce({ code: 'RATE_LIMIT_EXCEEDED', headers: { 'retry-after': '1' } })
.mockResolvedValueOnce({ id: 'cust_123' });
const result = await rateLimitHandler.executeWithRetry(mockOperation);
expect(mockOperation).toHaveBeenCalledTimes(2);
expect(result).toEqual({ id: 'cust_123' });
});
});
describe('Circuit Breaker', () => {
test('opens circuit after threshold failures', async () => {
const circuitBreaker = new CircuitBreaker({ failureThreshold: 2, timeout: 1000 });
const failingOperation = jest.fn().mockRejectedValue(new Error('Service error'));
const fallback = jest.fn().mockResolvedValue({ cached: true });
// Trigger circuit breaker
await expect(circuitBreaker.execute(failingOperation)).rejects.toThrow();
await expect(circuitBreaker.execute(failingOperation)).rejects.toThrow();
// Circuit should now be open
const result = await circuitBreaker.execute(failingOperation, fallback);
expect(result).toEqual({ cached: true });
expect(fallback).toHaveBeenCalled();
});
});
});
Error Handling Checklist
Production Readiness Checklist
Before deploying to production, ensure you have:✅ Proper error categorization - Handle different error types appropriately
✅ Retry logic - Implement exponential backoff for retryable errors
✅ Circuit breakers - Prevent cascading failures in distributed systems
✅ Fallback mechanisms - Graceful degradation when APIs are unavailable
✅ Comprehensive logging - Log errors with sufficient context for debugging
✅ Monitoring & alerting - Set up alerts for critical error conditions
✅ Error testing - Test all error scenarios in your test suite
✅ Documentation - Document error handling strategies for your team
✅ Retry logic - Implement exponential backoff for retryable errors
✅ Circuit breakers - Prevent cascading failures in distributed systems
✅ Fallback mechanisms - Graceful degradation when APIs are unavailable
✅ Comprehensive logging - Log errors with sufficient context for debugging
✅ Monitoring & alerting - Set up alerts for critical error conditions
✅ Error testing - Test all error scenarios in your test suite
✅ Documentation - Document error handling strategies for your team
Best Practices Summary
- Always use environment variables for API keys and secrets
- Implement proper logging instead of console.log statements
- Handle rate limiting with intelligent retry mechanisms
- Use circuit breakers for external API calls
- Track error metrics and set up appropriate alerts
- Test error scenarios comprehensively
- Provide meaningful fallbacks when possible
- Never log sensitive information in error messages
Need Help?If you encounter persistent errors or need assistance with error handling strategies, our support team is here to help. Contact us at support@stateset.com with your request ID for faster resolution.