Error Handling
Comprehensive guide to handling errors, understanding status codes, and implementing robust error handling in your API integrations.
Overview
Proper error handling is crucial for building reliable applications that use the Twitter API. This guide covers all error scenarios, status codes, and best practices for handling errors gracefully.
Why Error Handling Matters
- User Experience: Graceful error handling provides better user experience
- Reliability: Proper error handling makes your application more robust
- Debugging: Good error handling helps identify and fix issues quickly
- Rate Limits: Proper handling prevents unnecessary API calls
HTTP Status Codes
The API uses standard HTTP status codes to indicate the result of a request. Understanding these codes is essential for proper error handling.
Success Codes
| Code | Description | When It Occurs |
|---|---|---|
| 200 | OK | Request succeeded, data returned |
| 201 | Created | Resource created successfully (if applicable) |
Client Error Codes (4xx)
| Code | Description | Common Causes | Solutions |
|---|---|---|---|
| 400 | Bad Request | Invalid parameters, malformed request | Check request format, verify all parameters |
| 401 | Unauthorized | Missing or invalid API key | Verify X-API-KEY header is present and correct |
| 403 | Forbidden | Access denied (private account, insufficient permissions) | Check if resource requires special permissions |
| 404 | Not Found | Resource doesn't exist or was deleted | Verify resource ID is correct, check if resource still exists |
| 422 | Unprocessable Entity | Valid format but invalid data (e.g., query too complex) | Simplify query, check data constraints |
| 429 | Too Many Requests | Rate limit exceeded | Implement exponential backoff, check rate limits |
Server Error Codes (5xx)
| Code | Description | Common Causes | Solutions |
|---|---|---|---|
| 500 | Internal Server Error | Server-side error | Retry after delay, contact support if persistent |
| 502 | Bad Gateway | Gateway error | Retry after delay |
| 503 | Service Unavailable | Service temporarily unavailable | Retry with exponential backoff |
| 504 | Gateway Timeout | Request timeout | Retry after delay, check if request is too complex |
Error Response Format
All error responses follow a consistent format:
{
"error": {
"code": 404,
"message": "Tweet not found",
"details": "The requested tweet ID does not exist or has been deleted.",
"request_id": "abc123def456"
}
}Error Response Fields
| Field | Type | Description |
|---|---|---|
code | integer | HTTP status code |
message | string | Human-readable error message |
details | string | Additional error details |
request_id | string | Unique request ID for support |
Common Error Scenarios
1. Invalid API Key (401)
Error Response:
{
"error": {
"code": 401,
"message": "Unauthorized",
"details": "Invalid or missing API key"
}
}Causes:
- API key not included in request
- API key is incorrect or expired
- API key format is invalid
Solutions:
// ✅ Correct: Include API key in headers
const response = await fetch(url, {
headers: {
'X-API-KEY': 'YOUR_API_KEY' // Make sure this is correct
}
});
// ❌ Wrong: Missing header
const response = await fetch(url); // No API key!Prevention:
- Store API key securely (environment variables, not in code)
- Validate API key format before making requests
- Check API key is included in all requests
2. Resource Not Found (404)
Error Response:
{
"error": {
"code": 404,
"message": "Tweet not found",
"details": "The requested tweet ID does not exist or has been deleted."
}
}Causes:
- Resource ID is incorrect
- Resource was deleted
- Resource never existed
Solutions:
async function getTweetSafely(tweetId) {
try {
const response = await fetch(`/tweet/${tweetId}`, {
headers: { 'X-API-KEY': API_KEY }
});
if (response.status === 404) {
return {
error: 'Tweet not found',
deleted: true
};
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error fetching tweet:', error);
return { error: error.message };
}
}Best Practices:
- Validate resource IDs before making requests
- Handle 404 errors gracefully in your UI
- Cache "not found" results to avoid repeated requests
3. Rate Limit Exceeded (429)
Error Response:
{
"error": {
"code": 429,
"message": "Rate limit exceeded",
"details": "Too many requests in the time window",
"retry_after": 60
}
}Causes:
- Making too many requests too quickly
- Exceeding plan's rate limits
- Not implementing rate limit handling
Solutions:
Exponential Backoff:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(
response.headers.get('Retry-After') ||
response.headers.get('X-RateLimit-Reset') ||
'60'
);
const waitTime = Math.pow(2, i) * 1000; // Exponential backoff
const finalWait = Math.max(waitTime, retryAfter * 1000);
console.log(`Rate limited. Waiting ${finalWait}ms before retry ${i + 1}/${maxRetries}`);
await new Promise(resolve => setTimeout(resolve, finalWait));
continue;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
}
}Rate Limit Headers:
function checkRateLimit(response) {
const limit = parseInt(response.headers.get('X-RateLimit-Limit') || '0');
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
const reset = parseInt(response.headers.get('X-RateLimit-Reset') || '0');
return {
limit,
remaining,
reset,
resetDate: new Date(reset * 1000),
percentageUsed: ((limit - remaining) / limit) * 100
};
}
// Usage
const response = await fetch(url, { headers: { 'X-API-KEY': API_KEY } });
const rateLimitInfo = checkRateLimit(response);
if (rateLimitInfo.remaining < 10) {
console.warn('Rate limit almost reached!', rateLimitInfo);
}Prevention:
- Monitor rate limit headers in every response
- Implement request queuing for high-volume applications
- Cache responses to reduce API calls
- Use bulk endpoints when available
4. Private Resource Access (403)
Error Response:
{
"error": {
"code": 403,
"message": "Forbidden",
"details": "Access to private account denied"
}
}Causes:
- Attempting to access private account data
- Insufficient permissions for resource
- Resource requires special access
Solutions:
async function getUserSafely(userId) {
const response = await fetch(`/user/${userId}`, {
headers: { 'X-API-KEY': API_KEY }
});
if (response.status === 403) {
return {
error: 'Private account',
private: true,
accessible: false
};
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}Best Practices:
- Check if resource is private before attempting access
- Handle 403 errors gracefully
- Inform users when content is private
5. Invalid Parameters (400)
Error Response:
{
"error": {
"code": 400,
"message": "Bad Request",
"details": "Invalid tweet ID format"
}
}Causes:
- Invalid parameter format
- Missing required parameters
- Parameter values out of range
Solutions:
function validateTweetId(tweetId) {
// Tweet IDs should be numeric strings
if (!/^\d+$/.test(tweetId)) {
throw new Error('Invalid tweet ID format');
}
return true;
}
function validateSearchQuery(query) {
if (!query || query.trim().length === 0) {
throw new Error('Search query cannot be empty');
}
if (query.length > 500) {
throw new Error('Search query too long');
}
return true;
}
// Usage
try {
validateTweetId(tweetId);
const response = await fetch(`/tweet/${tweetId}`, {
headers: { 'X-API-KEY': API_KEY }
});
} catch (error) {
console.error('Validation error:', error.message);
}6. Query Too Complex (422)
Error Response:
{
"error": {
"code": 422,
"message": "Unprocessable Entity",
"details": "Search query too complex. Maximum 10 operators allowed."
}
}Causes:
- Search query has too many operators
- Query structure is too complex
- Date range is invalid
Solutions:
function simplifyQuery(query) {
// Count operators
const operatorCount = (query.match(/\b(OR|AND|NOT)\b/gi) || []).length;
if (operatorCount > 10) {
// Simplify query
return query.split(/\b(OR|AND)\b/i).slice(0, 5).join(' ');
}
return query;
}
// Usage
const simplifiedQuery = simplifyQuery(complexQuery);
const response = await fetch(`/search/tweets?q=${encodeURIComponent(simplifiedQuery)}`, {
headers: { 'X-API-KEY': API_KEY }
});Error Handling Patterns
Pattern 1: Try-Catch with Retry
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return await response.json();
}
// Handle specific errors
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
if (response.status === 404) {
return { error: 'Not found', status: 404 };
}
if (response.status >= 500) {
// Server error - retry
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
continue;
}
// Client error - don't retry
const error = await response.json();
throw new Error(error.message || `HTTP ${response.status}`);
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
}
}Pattern 2: Error Wrapper
class APIError extends Error {
constructor(code, message, details) {
super(message);
this.code = code;
this.details = details;
this.name = 'APIError';
}
}
async function handleAPIResponse(response) {
if (response.ok) {
return await response.json();
}
const error = await response.json().catch(() => ({
code: response.status,
message: response.statusText
}));
throw new APIError(
error.code || response.status,
error.message || response.statusText,
error.details
);
}
// Usage
try {
const response = await fetch(url, { headers: { 'X-API-KEY': API_KEY } });
const data = await handleAPIResponse(response);
} catch (error) {
if (error instanceof APIError) {
console.error(`API Error ${error.code}: ${error.message}`);
// Handle specific error codes
} else {
console.error('Network error:', error);
}
}Pattern 3: Error Handler Middleware
function createErrorHandler() {
return {
handle: async (request) => {
try {
const response = await request();
if (!response.ok) {
return await this.handleError(response);
}
return await response.json();
} catch (error) {
return this.handleNetworkError(error);
}
},
handleError: async (response) => {
const error = await response.json().catch(() => ({
code: response.status,
message: response.statusText
}));
switch (response.status) {
case 401:
return { error: 'Authentication failed', code: 401 };
case 403:
return { error: 'Access denied', code: 403 };
case 404:
return { error: 'Resource not found', code: 404 };
case 429:
const retryAfter = response.headers.get('Retry-After');
return {
error: 'Rate limit exceeded',
code: 429,
retryAfter: parseInt(retryAfter || '60')
};
default:
return { error: error.message, code: response.status };
}
},
handleNetworkError: (error) => {
return {
error: 'Network error',
message: error.message,
code: 'NETWORK_ERROR'
};
}
};
}
// Usage
const errorHandler = createErrorHandler();
const data = await errorHandler.handle(() =>
fetch(url, { headers: { 'X-API-KEY': API_KEY } })
);Best Practices
1. Always Check Response Status
// ✅ Good
const response = await fetch(url, { headers: { 'X-API-KEY': API_KEY } });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
// ❌ Bad
const response = await fetch(url, { headers: { 'X-API-KEY': API_KEY } });
const data = await response.json(); // May fail if response is error2. Implement Exponential Backoff
async function fetchWithBackoff(url, options, retries = 3) {
for (let i = 0; i < retries; i++) {
const response = await fetch(url, options);
if (response.ok) return await response.json();
if (response.status !== 429 && response.status < 500) {
throw new Error(`HTTP ${response.status}`);
}
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error('Max retries exceeded');
}3. Log Errors for Debugging
function logError(error, context) {
console.error('API Error:', {
message: error.message,
code: error.code,
context,
timestamp: new Date().toISOString()
});
// Send to error tracking service (e.g., Sentry)
// errorTrackingService.captureException(error, { context });
}4. Provide User-Friendly Messages
function getUserFriendlyMessage(error) {
const messages = {
401: 'Please check your API key',
403: 'This content is private',
404: 'The requested content was not found',
429: 'Too many requests. Please try again later.',
500: 'Server error. Please try again later.'
};
return messages[error.code] || 'An error occurred. Please try again.';
}5. Handle Timeouts
function fetchWithTimeout(url, options, timeout = 10000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
)
]);
}Testing Error Scenarios
Test Invalid API Key
async function testInvalidAPIKey() {
const response = await fetch('/tweet/123', {
headers: { 'X-API-KEY': 'invalid_key' }
});
if (response.status === 401) {
console.log('✅ Correctly handled invalid API key');
}
}Test Rate Limiting
async function testRateLimit() {
const requests = Array(100).fill().map(() =>
fetch('/tweet/123', { headers: { 'X-API-KEY': API_KEY } })
);
const responses = await Promise.all(requests);
const rateLimited = responses.filter(r => r.status === 429);
console.log(`Rate limited: ${rateLimited.length} requests`);
}Related Documentation
- Rate Limits Guide - Understanding rate limits
- Best Practices - API usage best practices
- Authentication Guide - Authentication details
