API Error Codes Cheat Sheet: What Every HTTP Status Code Means (2026)

by API Status Check

API errors are inevitable. Whether you're integrating with Stripe, OpenAI, Twilio, or any other third-party API, you'll encounter HTTP status codes that signal something went wrong. But what does each code actually mean? And more importantly—is it your fault or theirs?

This comprehensive cheat sheet breaks down every common HTTP status code you'll encounter, with practical examples and handling strategies for each.

Quick Reference Table

Code Meaning Who's Responsible Retry?
2xx - Success
200 OK N/A N/A
201 Created N/A N/A
202 Accepted N/A N/A
204 No Content N/A N/A
3xx - Redirection
301 Moved Permanently API Provider Follow redirect
302 Found (Temporary) API Provider Follow redirect
304 Not Modified N/A No
4xx - Client Errors
400 Bad Request You No (fix request)
401 Unauthorized You No (add auth)
403 Forbidden You/Provider No (check permissions)
404 Not Found You/Provider No (check endpoint)
409 Conflict You Maybe (resolve conflict)
422 Unprocessable Entity You No (fix data)
429 Too Many Requests You Yes (with backoff)
5xx - Server Errors
500 Internal Server Error Provider Yes (exponential backoff)
502 Bad Gateway Provider Yes (exponential backoff)
503 Service Unavailable Provider Yes (exponential backoff)
504 Gateway Timeout Provider Yes (exponential backoff)

Deep Dive: The Codes You'll Actually See

400 Bad Request: You Sent Something Wrong

What it means: Your request was malformed. Missing required fields, invalid JSON, wrong data types—something in your request doesn't match what the API expects.

Common causes:

  • Invalid JSON syntax (missing comma, unclosed bracket)
  • Missing required parameters
  • Wrong data types (string instead of integer)
  • Invalid enum values

How to handle it:

try {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: 'user@example.com',
      age: 'twenty-five' // Wrong! Should be a number
    })
  });
  
  if (!response.ok) {
    const error = await response.json();
    console.error('Bad request:', error.message);
    // Log the full error for debugging
    console.error('Details:', error.details);
  }
} catch (err) {
  console.error('Request failed:', err);
}

Fix it: Read the error message carefully. Most APIs return detailed validation errors telling you exactly what's wrong. Don't retry—fix your request first.

401 Unauthorized: Missing or Invalid Authentication

What it means: You didn't provide credentials, or the ones you provided are invalid/expired.

Common causes:

  • Missing API key or token
  • Expired JWT token
  • API key in wrong header (should be Authorization: Bearer or X-API-Key)
  • Revoked credentials

How to handle it:

const apiKey = process.env.API_KEY;

if (!apiKey) {
  throw new Error('API_KEY not configured');
}

const response = await fetch('https://api.example.com/data', {
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  }
});

if (response.status === 401) {
  // Token might be expired - refresh if possible
  const newToken = await refreshToken();
  // Retry with new token
  return retryWithNewToken(newToken);
}

Fix it: Double-check your API key is correct, in the right header, and hasn't expired. For OAuth tokens, implement automatic refresh before they expire.

403 Forbidden: You Don't Have Permission

What it means: You're authenticated, but you're not allowed to access this resource. Think of it like having a valid ID but not being on the guest list.

Common causes:

  • Trying to access another user's data
  • Account doesn't have the right subscription tier
  • API endpoint requires special permissions
  • IP address not whitelisted

How to handle it:

if (response.status === 403) {
  const error = await response.json();
  
  // Check if it's a plan limitation
  if (error.code === 'UPGRADE_REQUIRED') {
    throw new Error('This feature requires a paid plan');
  }
  
  // Check if it's a permission issue
  if (error.code === 'INSUFFICIENT_PERMISSIONS') {
    throw new Error('User lacks required permissions');
  }
  
  // Otherwise, this is a genuine forbidden access
  throw new Error('Access denied');
}

Fix it: Don't retry. Either upgrade your plan, request additional permissions, or avoid accessing this resource.

404 Not Found: The Resource Doesn't Exist

What it means: The endpoint or resource you're looking for doesn't exist.

Common causes:

  • Typo in the URL (/usres instead of /users)
  • Resource was deleted
  • Wrong API version in URL (/v1/ vs /v2/)
  • Resource ID is invalid

The tricky part: Is it you or them?

  • Your fault: Typo, wrong ID, deleted resource
  • Their fault: Endpoint was removed without notice, documentation is outdated

How to handle it:

async function getUser(userId) {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  
  if (response.status === 404) {
    // Could be deleted user or invalid ID
    console.warn(`User ${userId} not found`);
    return null; // Handle gracefully
  }
  
  return response.json();
}

// For endpoints (not resources), check API status
async function safeApiCall(endpoint) {
  try {
    const response = await fetch(endpoint);
    if (response.status === 404) {
      // Might be API issue - check status page
      console.error('Endpoint not found. Check API Status Check for outages.');
    }
  } catch (err) {
    console.error('Request failed:', err);
  }
}

Fix it: Verify your URL is correct. If it's a resource (user, order, etc.), handle gracefully—it might have been deleted. If it's an endpoint, check the API's changelog or status page.

409 Conflict: The Resource Already Exists

What it means: Your request conflicts with the current state of the resource. Most common when trying to create something that already exists.

Common causes:

  • Duplicate user registration (email already exists)
  • Trying to create a resource with an ID that's taken
  • Concurrent modification conflicts

How to handle it:

async function createUser(email, password) {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password })
  });
  
  if (response.status === 409) {
    const error = await response.json();
    if (error.code === 'EMAIL_EXISTS') {
      throw new Error('An account with this email already exists');
    }
  }
  
  return response.json();
}

Fix it: Don't retry with the same data. Either update the existing resource (PATCH) or change your data to avoid the conflict.

422 Unprocessable Entity: Valid JSON, Invalid Data

What it means: Your request is syntactically correct (valid JSON), but semantically wrong. The data doesn't make sense in the business logic context.

Common causes:

  • Email format is invalid
  • Date is in the past when it should be future
  • Price is negative
  • Password doesn't meet complexity requirements

How to handle it:

async function updateProduct(productId, data) {
  const response = await fetch(`https://api.example.com/products/${productId}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  
  if (response.status === 422) {
    const error = await response.json();
    // Usually contains field-level validation errors
    console.error('Validation errors:', error.errors);
    // { "errors": { "price": "must be positive", "sku": "already exists" } }
    throw new ValidationError(error.errors);
  }
  
  return response.json();
}

Fix it: Read the validation errors and correct your data. Don't retry without changes.

429 Too Many Requests: Slow Down!

What it means: You've exceeded the API's rate limit. You're making too many requests too quickly.

Common causes:

  • Tight loop making requests without delays
  • Multiple concurrent requests without throttling
  • Exceeded daily/hourly quota

How to handle it:

async function makeRequestWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);
    
    if (response.status === 429) {
      // Check for Retry-After header
      const retryAfter = response.headers.get('Retry-After');
      const waitTime = retryAfter 
        ? parseInt(retryAfter) * 1000 
        : Math.pow(2, i) * 1000; // Exponential backoff
      
      console.log(`Rate limited. Waiting ${waitTime}ms before retry ${i + 1}/${maxRetries}`);
      await new Promise(resolve => setTimeout(resolve, waitTime));
      continue;
    }
    
    return response;
  }
  
  throw new Error('Max retries exceeded');
}

Fix it: Implement exponential backoff, respect Retry-After headers, and consider implementing a request queue to stay under rate limits.

500 Internal Server Error: Something Broke on Their End

What it means: The API server encountered an unexpected error. This is a server-side problem, not your fault.

Common causes:

  • Bug in the API code
  • Database connection failure
  • Unhandled exception
  • Memory/resource exhaustion

How to handle it:

async function robustApiCall(url, options) {
  let lastError;
  const maxRetries = 3;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      
      if (response.status === 500) {
        lastError = await response.json();
        console.error(`Server error (attempt ${i + 1}/${maxRetries}):`, lastError);
        
        // Exponential backoff: 1s, 2s, 4s
        await new Promise(resolve => 
          setTimeout(resolve, Math.pow(2, i) * 1000)
        );
        continue;
      }
      
      return response;
    } catch (err) {
      lastError = err;
    }
  }
  
  // After retries fail, check if it's a widespread outage
  console.error('API appears to be down. Check API Status Check.');
  throw lastError;
}

Fix it: Retry with exponential backoff. If it persists, check the API's status page. Not your bug to fix.

502 Bad Gateway: The Proxy Can't Reach the Server

What it means: The API gateway or load balancer received an invalid response from an upstream server.

Common causes:

  • API server is down
  • API server is overloaded and timing out
  • Network issues between gateway and API server
  • Deployment in progress

How to handle it: Same as 500—retry with exponential backoff. This is often transient during deployments.

503 Service Unavailable: Temporarily Down

What it means: The API is temporarily unable to handle requests. Often returned during maintenance or when the service is overloaded.

Common causes:

  • Scheduled maintenance
  • Overloaded servers (traffic spike)
  • Deliberate throttling under heavy load
  • Deployment window

How to handle it:

if (response.status === 503) {
  const retryAfter = response.headers.get('Retry-After');
  
  if (retryAfter) {
    console.log(`Service unavailable. Retry after ${retryAfter} seconds`);
    // Schedule retry
    setTimeout(() => retryRequest(), parseInt(retryAfter) * 1000);
  } else {
    // No Retry-After header - use exponential backoff
    console.log('Service temporarily unavailable. Retrying...');
  }
}

Fix it: Respect the Retry-After header if present. Otherwise, retry with exponential backoff.

504 Gateway Timeout: The Server Took Too Long

What it means: The gateway/proxy gave up waiting for the upstream server to respond.

Common causes:

  • API operation is taking too long (complex query, large file processing)
  • Server is under heavy load
  • Network latency issues

How to handle it: Retry with backoff, or if the operation is long-running, consider switching to an async pattern where the API returns immediately and you poll for results.

"Is It Me or the API?" Decision Tree

Got an error code?
│
├─ 4xx (400-499)? → Probably you
│  ├─ 400/422 → Fix your request data
│  ├─ 401 → Check your API key/auth
│  ├─ 403 → Check permissions/plan
│  ├─ 404 → Check URL/resource exists
│  └─ 429 → Slow down your requests
│
└─ 5xx (500-599)? → Probably them
   ├─ Retry a few times with backoff
   ├─ Still failing?
   │  └─ Check API Status Check
   │     ├─ Outage reported? → Not your fault, wait
   │     └─ No outage? → Might be your request triggering a bug
   └─ Success on retry? → Transient issue, you're good

Distinguishing Your Bugs from API Outages

Here's the key question: Is it just you, or is everyone affected?

Signs it's YOUR bug:

  • Error is consistent and immediate
  • Happens on every request to that endpoint
  • Started after YOU made changes
  • 4xx status codes (especially 400, 401, 403, 422)
  • Other endpoints work fine

Signs it's an API OUTAGE:

  • Started suddenly with no changes on your end
  • 5xx status codes (500, 502, 503, 504)
  • Intermittent—sometimes works, sometimes doesn't
  • Multiple endpoints affected
  • High response times even on successful requests
  • Other users reporting issues on Twitter/forums

The Fast Way: Check API Status Check

Instead of guessing, check API Status Check to see real-time status for hundreds of APIs:

async function isApiDown(apiName) {
  try {
    const response = await fetch(
      `https://apistatuscheck.com/api/status/${apiName}`
    );
    const data = await response.json();
    return data.status === 'down' || data.status === 'degraded';
  } catch {
    return false; // Assume up if status check fails
  }
}

// In your error handler
if (response.status >= 500) {
  const apiDown = await isApiDown('openai');
  if (apiDown) {
    console.log('OpenAI is experiencing an outage. Not your fault.');
    // Show user-friendly message, queue request, or use fallback
  } else {
    console.log('Unexpected 500 error. Investigate further.');
  }
}

Best Practices for Error Handling

1. Always Log Full Error Responses

if (!response.ok) {
  const errorBody = await response.text();
  console.error({
    status: response.status,
    statusText: response.statusText,
    body: errorBody,
    headers: Object.fromEntries(response.headers),
    url: response.url
  });
}

2. Implement Retries with Exponential Backoff

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);
    
    // Retry on 5xx or 429
    if (response.status >= 500 || response.status === 429) {
      const backoff = Math.min(1000 * Math.pow(2, i), 10000);
      await new Promise(resolve => setTimeout(resolve, backoff));
      continue;
    }
    
    return response;
  }
  
  throw new Error('Max retries exceeded');
}

3. Respect Rate Limits Proactively

class RateLimiter {
  constructor(maxRequests, timeWindow) {
    this.maxRequests = maxRequests;
    this.timeWindow = timeWindow;
    this.requests = [];
  }
  
  async waitIfNeeded() {
    const now = Date.now();
    this.requests = this.requests.filter(t => now - t < this.timeWindow);
    
    if (this.requests.length >= this.maxRequests) {
      const oldestRequest = this.requests[0];
      const waitTime = this.timeWindow - (now - oldestRequest);
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
    
    this.requests.push(now);
  }
}

const limiter = new RateLimiter(100, 60000); // 100 req/min
await limiter.waitIfNeeded();
await fetch('https://api.example.com/data');

4. Provide Context-Aware Error Messages

function getErrorMessage(status, context) {
  const messages = {
    400: `Invalid ${context.resource} data. Check your input.`,
    401: `Authentication failed. Check your API key.`,
    403: `You don't have permission to access this ${context.resource}.`,
    404: `${context.resource} not found. It may have been deleted.`,
    429: `Rate limit exceeded. Please wait before trying again.`,
    500: `${context.apiName} is experiencing issues. Try again later.`,
    502: `${context.apiName} is temporarily unavailable.`,
    503: `${context.apiName} is under maintenance.`
  };
  
  return messages[status] || `Unexpected error (${status})`;
}

FAQ

Q: Should I retry 4xx errors? No, except for 429 (rate limit). 4xx errors indicate a problem with your request—retrying the same request will get the same error.

Q: How long should I wait between retries? Use exponential backoff: 1s, 2s, 4s, 8s. Respect Retry-After headers when present.

Q: How many times should I retry? 3-5 retries is typical. For critical operations, you might retry more, but implement a circuit breaker to fail fast if the API is down.

Q: What's the difference between 401 and 403?

  • 401: "Who are you?" (authentication missing/invalid)
  • 403: "I know who you are, but you can't do that" (insufficient permissions)

Q: Why do I get 500 errors intermittently? Often caused by API bugs triggered by specific edge cases, or infrastructure issues like database connection pools being exhausted. If it's consistent with certain requests, report it. If random, it's likely infrastructure.

Q: Should I show raw HTTP codes to end users? No. Translate them into user-friendly messages: "Invalid email address" instead of "400 Bad Request".

Take Action: Monitor APIs Before They Fail

Don't wait for errors to tell you an API is down. API Status Check monitors 200+ APIs in real-time and alerts you the moment an outage begins.

Stop guessing. Start monitoring. Sign up for free at apistatuscheck.com.

Monitor Your APIs

Check the real-time status of 100+ popular APIs used by developers.

View API Status →