Guide10 min read

The Complete Guide to API Error Handling

Building resilient systems that don't break when things go wrong.

Error handling is often an afterthought in API development. We focus on the "happy path"—the 200 OK responses—and treat errors as exceptional cases. But in distributed systems, errors are inevitable. Networks fail, services go down, and rate limits are hit.

Robust error handling is the difference between a system that crashes under pressure and one that gracefully recovers.

Understanding HTTP Status Codes

The first step in error handling is correctly interpreting HTTP status codes. They are grouped into categories:

  • 2xx (Success): The request was received, understood, and accepted.
  • 4xx (Client Error): The request contains bad syntax or cannot be fulfilled. These should generally not be retried without modification.
    • 400 Bad Request: The server cannot process the request due to client error.
    • 401 Unauthorized: Authentication is required and has failed or has not yet been provided.
    • 403 Forbidden: The server understood the request but refuses to authorize it.
    • 404 Not Found: The requested resource could not be found.
    • 429 Too Many Requests: The user has sent too many requests in a given amount of time.
  • 5xx (Server Error): The server failed to fulfill an apparently valid request. These should be retried.
    • 500 Internal Server Error: A generic error message when an unexpected condition was encountered.
    • 502 Bad Gateway: The server was acting as a gateway or proxy and received an invalid response from the upstream server.
    • 503 Service Unavailable: The server cannot handle the request (usually due to overloading or maintenance).
    • 504 Gateway Timeout: The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.

Retry Strategies

When you encounter a transient error (like a 5xx or a network timeout), you should retry the request. But how you retry matters.

1. Exponential Backoff

Don't retry immediately. If a server is struggling, hitting it again instantly will only make things worse. Instead, increase the wait time between retries exponentially (e.g., 1s, 2s, 4s, 8s).

2. Jitter

If multiple clients all retry at the exact same intervals, they can create "thundering herd" problems. Adding "jitter"—a small amount of randomness to the wait time—spreads out the load and helps the server recover.

3. Circuit Breaker

If a service is consistently failing, stop trying. A circuit breaker "trips" after a certain number of failures, preventing further requests for a period. This gives the failing service time to recover without being bombarded.

Implementing Retries in JavaScript

Here's a basic implementation of a fetch wrapper with exponential backoff:

async function fetchWithRetry(url, options, retries = 3, backoff = 1000) {
  try {
    const response = await fetch(url, options);
    if (response.ok) return response;
    
    if (retries > 0 && response.status >= 500) {
      console.log(`Retrying in ${backoff}ms...`);
      await new Promise(resolve => setTimeout(resolve, backoff));
      return fetchWithRetry(url, options, retries - 1, backoff * 2);
    }
    return response;
  } catch (error) {
    if (retries > 0) {
      await new Promise(resolve => setTimeout(resolve, backoff));
      return fetchWithRetry(url, options, retries - 1, backoff * 2);
    }
    throw error;
  }
}

The Better Way: Let FetchAPI Handle It

Writing and testing retry logic for every API integration is tedious and error-prone. You have to handle timeouts, edge cases, and state management for every single call.

FetchAPI was built to solve this. When you use FetchAPI, you get production-grade error handling by default:

  • Automatic Retries: We automatically retry on 5xx errors and network timeouts using a smart exponential backoff strategy.
  • Configurable Policies: You can customize how many times we retry and the maximum delay.
  • Durable Execution: We ensure your request is eventually delivered, even if your server is down for minutes or hours.
  • Observability: See exactly why a request failed and every retry attempt in our dashboard.

Instead of building a complex retry system, you just make one call to FetchAPI:

// FetchAPI handles the retries, backoff, and timeouts for you
await fetch('https://api.fetchapi.dev/v1/fetch', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer relay_sk_...',
    'X-Fetch-Url': 'https://api.yourserver.com/endpoint'
  },
  body: JSON.stringify({ data: '...' })
});

Conclusion

API error handling is critical for reliability, but it's also a lot of boilerplate. By understanding status codes and implementing smart retry strategies, you can build much more resilient systems. Or, you can use FetchAPI and skip the boilerplate entirely, letting us handle the complexity of distributed systems for you.

Stop Writing Retry Logic

Build resilient API integrations in minutes. FetchAPI handles retries, backoff, and timeouts so you don't have to.

Get Started Free