Docs
Error Handling

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

CodeDescriptionWhen It Occurs
200OKRequest succeeded, data returned
201CreatedResource created successfully (if applicable)

Client Error Codes (4xx)

CodeDescriptionCommon CausesSolutions
400Bad RequestInvalid parameters, malformed requestCheck request format, verify all parameters
401UnauthorizedMissing or invalid API keyVerify X-API-KEY header is present and correct
403ForbiddenAccess denied (private account, insufficient permissions)Check if resource requires special permissions
404Not FoundResource doesn't exist or was deletedVerify resource ID is correct, check if resource still exists
422Unprocessable EntityValid format but invalid data (e.g., query too complex)Simplify query, check data constraints
429Too Many RequestsRate limit exceededImplement exponential backoff, check rate limits

Server Error Codes (5xx)

CodeDescriptionCommon CausesSolutions
500Internal Server ErrorServer-side errorRetry after delay, contact support if persistent
502Bad GatewayGateway errorRetry after delay
503Service UnavailableService temporarily unavailableRetry with exponential backoff
504Gateway TimeoutRequest timeoutRetry 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

FieldTypeDescription
codeintegerHTTP status code
messagestringHuman-readable error message
detailsstringAdditional error details
request_idstringUnique 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 error

2. 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`);
}