Rate Limiting and Error Handling
Implement robust error handling and manage rate limits effectively
Real systems hit rate limits, timeouts, and provider outages. Your job is to make that invisible to users. This guide walks through: • What rate limits mean in practice • How to retry safely (exponential backoff + jitter) • Which errors are safe to retry • Circuit breaker patterns for stability • Observability—so you can debug incidents fast
Understanding Rate Limits
API providers impose rate limits to ensure fair usage: • Requests per minute (RPM) • Tokens per minute (TPM) • Tokens per day (TPD) • Concurrent requests Synqly aggregates these limits and provides unified rate limit headers in responses.
Implementing Exponential Backoff
Retry failed requests with increasing delays:
async function makeRequestWithRetry(
fn: () => Promise<any>,
maxRetries = 3
) {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error: any) {
lastError = error;
// Don't retry on client errors (4xx except 429)
if (error.status >= 400 && error.status < 500 && error.status !== 429) {
throw error;
}
if (attempt < maxRetries) {
// Calculate delay: 1s, 2s, 4s, 8s, etc.
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
// Add jitter to prevent thundering herd
const jitter = Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, delay + jitter));
}
}
}
throw lastError;
}Handling Different Error Types
Respond appropriately to different error scenarios:
async function handleAPICall(request: any) {
try {
return await synqly.chat.completions.create(request);
} catch (error: any) {
switch (error.status) {
case 400:
console.error('Invalid request:', error.message);
throw new Error('Please check your input');
case 401:
console.error('Authentication failed');
throw new Error('Invalid API key');
case 429:
const retryAfter = error.headers?.['retry-after'];
console.log(`Rate limited. Retry after ${retryAfter}s`);
throw new Error('Too many requests');
case 500:
case 502:
case 503:
console.error('Server error:', error.status);
throw new Error('Service temporarily unavailable');
default:
console.error('Unexpected error:', error);
throw new Error('An unexpected error occurred');
}
}
}Circuit Breaker Pattern
Prevent cascading failures:
class CircuitBreaker {
private failures = 0;
private lastFailureTime: number | null = null;
private state: 'closed' | 'open' | 'half-open' = 'closed';
constructor(
private threshold = 5,
private timeout = 60000
) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (Date.now() - this.lastFailureTime! > this.timeout) {
this.state = 'half-open';
} else {
throw new Error('Circuit breaker is open');
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess() {
this.failures = 0;
this.state = 'closed';
}
private onFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.threshold) {
this.state = 'open';
}
}
}