Home/Coding & Tech Skills

HTTP Status Codes: The 12 You Actually Need (2026 Guide)

coding-tech-skills · Coding & Tech Skills

I spent a Saturday afternoon last month debugging a payment API that kept returning 500 errors. After an hour of digging, I realized the issue wasn't on their end—it was my code misinterpreting a 302 redirect as success, then blindly retrying the wrong endpoint. That's when it hit me: knowing HTTP status codes isn't just about passing a certification; it's about keeping your app from falling apart in production. In this 2026 guide, I'm cutting through the noise and covering the 12 codes you'll actually encounter—the ones that break APIs, confuse users, and waste your time if you get them wrong.

Why You Need to Know These 12 HTTP Status Codes Right Now

Every day, millions of HTTP requests fly between browsers, APIs, and servers. Each response carries a three-digit code that's supposed to tell you what happened. But in practice, many developers treat status codes like afterthoughts—throwing a 200 OK on everything or using 404 as a catch-all for unauthorized access. That sloppiness has real consequences: broken integrations, corrupted data, and users staring at cryptic error screens.

In 2026, with REST APIs, GraphQL, and serverless functions powering everything from fintech apps to smart home devices, getting status codes right matters more than ever. Tools like Postman and browser DevTools expose these codes directly, and clients—whether a mobile app or a third-party service—rely on them to decide next steps. Misuse a 301 when you meant 308, and you might accidentally change a POST to GET, losing form data. Return 200 on a validation failure, and your frontend won't know to show an error message.

This guide focuses on the 12 codes that are most common, most misunderstood, and most critical for modern web development. I've grouped them by category (2xx success, 3xx redirects, 4xx client errors, 5xx server errors) and paired each with a concrete example from real projects. By the end, you'll know exactly which code to use and, more importantly, which to avoid.

The 12 HTTP Status Codes You Actually Need (and When to Use Each)

2xx Success Codes (3 codes)

200 OK — The workhorse. Use it for successful GET requests (fetching a resource), successful PUT requests (updating a resource), and any request where the server processed everything correctly. But here's the trap: don't return 200 with an error message in the body. I've seen APIs that return { "error": "invalid input", "code": 400 } with a 200 status. That breaks HTTP semantics and confuses every client library that checks the status code first. Use 200 only when everything is genuinely fine.

201 Created — For POST requests that create a new resource. Always include a Location header pointing to the new resource's URL. For example, after creating a user, return 201 with Location: /users/123. This lets the client know exactly where to find what was just created.

204 No Content — Perfect for DELETE requests or PUT requests that don't need to return a body. I used this in a recent project to confirm a user's account deletion—the response was empty but the status told the frontend to clear the UI and redirect. No body, no confusion.

3xx Redirection Codes (2 codes)

301 Moved Permanently — Tells the client and search engines that the resource has a new permanent URL. Browsers cache this, so subsequent requests go straight to the new location. But for APIs, be cautious: many HTTP clients change POST to GET on 301, which can break your endpoints. That's why I prefer 308 for permanent redirects in APIs—it preserves the HTTP method.

307 Temporary Redirect — Use this when a resource is temporarily at a different URL (e.g., during maintenance). The key difference from 302 is that 307 guarantees the method and body won't change. For APIs, this is a lifesaver. I once had a mobile app that kept losing form data because the server sent a 302 on a POST; switching to 307 fixed it immediately.

4xx Client Error Codes (5 codes)

400 Bad Request — Use when the client sends malformed input—broken JSON, missing required headers, or invalid syntax. Don't use 400 for business logic errors; that's what 422 is for. Example: if a user sends { "name": "Alice" } when you expect { "username": "Alice" }, that's a 400 because the structure is wrong.

401 Unauthorized — The client hasn't authenticated. Maybe they didn't send a token, or the token expired. Always include a WWW-Authenticate header telling them how to authenticate. I've seen many beginners return 403 here, but that's wrong—403 means the server knows who you are but you don't have permission.

403 Forbidden — The client is authenticated but lacks permissions. For example, a regular user trying to access an admin endpoint. Do not expose why they're forbidden (e.g., "you need admin role") because that leaks information. Just return 403 with a generic message.

404 Not Found — The resource doesn't exist. Simple, but easily abused. Some APIs return 404 for unauthorized access to hide the existence of resources (a security pattern called "opaque 404"). That's valid, but be consistent—don't mix 401 and 404 for the same scenario. Also, never use 404 for validation errors; that's confusing and breaks tooling.

429 Too Many Requests — Rate limiting. Include a Retry-After header in seconds (e.g., Retry-After: 120). Your client code should read this header and wait before retrying, ideally with exponential backoff. In my own setup, I once forgot to check this header and hammered an API with retries every second—got banned for an hour. Don't be like me.

5xx Server Error Codes (2 codes)

500 Internal Server Error — Something went wrong on the server. Don't overuse it; log the actual error and return a more specific code if possible. For example, if a database connection fails, 500 is fine, but if a third-party service times out, consider 502 or 504.

503 Service Unavailable — The server is temporarily overloaded or down for maintenance. Always include a Retry-After header. This tells clients (and search engines) to try again later rather than giving up permanently. I've seen sites return 500 for planned maintenance, which makes caching and monitoring a nightmare.

HTTP Status Code Best Practices for Modern Web Applications

Choosing the right status code is part art, part science. Here are the rules I follow in my own projects:

  • Validation errors: 422 vs. 400. Use 422 Unprocessable Entity when the request body is syntactically correct but semantically invalid (e.g., missing required fields, invalid email format). Use 400 only for malformed syntax like broken JSON. This distinction helps clients differentiate between "fix your request format" and "fix your data."
  • Redirects in APIs: prefer 307/308. As mentioned, 301/302 change POST to GET in many clients. For REST APIs, use 307 (temporary, preserves method) or 308 (permanent, preserves method). This ensures idempotent behavior for write operations.
  • Handling 503 with Retry-After. Always set a realistic Retry-After value. If you don't know exactly, err on the side of longer (e.g., 300 seconds for a temporary outage). Clients can implement exponential backoff, but they need a starting point.
  • Never return 200 for errors. This is the most common mistake I see. Returning 200 with an error object in the body breaks HTTP semantics and confuses proxies, caches, and developer tools. Always use the appropriate 4xx or 5xx code.
  • Be consistent across your API. If you use 404 for missing resources, use it everywhere. Don't switch to 400 for similar scenarios. Consistency reduces confusion for your API consumers.

How to Handle HTTP Status Codes in Your Code (with Examples)

Here's how I handle status codes in a typical JavaScript frontend. This snippet shows retry logic for 5xx and error display for 4xx:

async function fetchWithRetry(url, options = {}, retries = 3) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    const response = await fetch(url, options);
    
    if (response.ok) {
      return response.json();
    }
    
    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After');
      const waitMs = (retryAfter ? parseInt(retryAfter) : 2) * 1000 * attempt;
      console.log(`Rate limited. Waiting ${waitMs}ms before retry...`);
      await new Promise(resolve => setTimeout(resolve, waitMs));
      continue;
    }
    
    if (response.status >= 500) {
      // Server error — retry with exponential backoff
      const waitMs = Math.pow(2, attempt) * 1000;
      console.log(`Server error ${response.status}. Retrying in ${waitMs}ms...`);
      await new Promise(resolve => setTimeout(resolve, waitMs));
      continue;
    }
    
    // 4xx errors (except 429) — don't retry, throw with details
    const errorBody = await response.json().catch(() => null);
    throw new Error(`Client error ${response.status}: ${errorBody?.message || 'Unknown error'}`);
  }
  
  throw new Error(`Failed after ${retries} retries`);
}

On the server side (Node.js/Express), I use a middleware that maps errors to proper status codes:

function errorHandler(err, req, res, next) {
  if (err.name === 'ValidationError') {
    return res.status(422).json({ error: err.message });
  }
  if (err.code === 'NOT_FOUND') {
    return res.status(404).json({ error: 'Resource not found' });
  }
  if (err.code === 'RATE_LIMIT') {
    res.set('Retry-After', '60');
    return res.status(429).json({ error: 'Too many requests' });
  }
  // Default: 500
  console.error('Unhandled error:', err);
  res.status(500).json({ error: 'Internal server error' });
}

This pattern ensures that every error has a meaningful status code, and the frontend can act on it without guessing.

Common HTTP Status Code Myths and Mistakes (2026 Edition)

Even experienced developers fall into these traps. Here are the myths I hear most often:

  • Myth: Always return 200 with a status field. I've seen APIs where every response is 200, and the real status is inside a JSON field like { "status": "error", "code": 400 }. This is a disaster. Browsers, proxies, and monitoring tools all rely on the HTTP status code. You're effectively disabling those tools. Use the right code.
  • Myth: 404 for unauthorized access hides resources. While this is a valid security pattern (opaque 404), it's often misapplied. If you use 404 for missing resources and 401 for unauthenticated access, mixing them inconsistently confuses clients. Pick one approach and stick with it.
  • Myth: 304 Not Modified is an error. I've seen developers treat 304 as a failure, but it's actually a success for caching. When a client sends If-None-Match or If-Modified-Since, a 304 tells the client to use its cached version. Don't log it as an error.
  • Myth: 302 is the same as 307. No. 302 may change POST to GET in many clients, while 307 preserves the method. For APIs, always prefer 307/308 for redirects.
  • Myth: 500 is fine for any server issue. Overusing 500 hides the real problem. If your database times out, return 503. If an upstream service fails, return 502. Specific codes help with debugging and monitoring.

FAQs

What is the difference between 401 Unauthorized and 403 Forbidden?

401 means the client needs to authenticate—no valid credentials were provided. 403 means the server understands the request but refuses to fulfill it because the authenticated user lacks permissions. Think of it as: 401 = "Who are you?" and 403 = "You can't do that."

When should I use 422 Unprocessable Entity instead of 400 Bad Request?

Use 422 for validation errors where the request body is syntactically correct but semantically invalid—for example, a missing required field or an invalid email format. Use 400 for malformed syntax like broken JSON or missing headers. This distinction helps clients know whether to fix the data or fix the request format.

How do I know if I should use 301 vs 302 vs 307 for redirects?

301 is permanent and tells search engines to update their links. 302 is temporary but may change POST to GET in some clients. For APIs, prefer 307 (temporary, preserves method) or 308 (permanent, preserves method) to avoid unintended method changes.

What does 429 Too Many Requests mean and how should I handle it?

It means the client has exceeded a rate limit. The server should include a Retry-After header in seconds. Your client code should read that header, wait the specified time, then retry—ideally with exponential backoff for subsequent attempts.

Is it okay to return 200 with an error message in the body instead of a 4xx or 5xx status?

No, it's a bad practice that breaks HTTP semantics, confuses API clients, and prevents proper error handling in tools like browsers and proxies. Always use appropriate status codes. Your API consumers will thank you.

Takeaway

HTTP status codes are the language your server uses to talk to the outside world. Getting them right isn't just about following a spec—it's about building reliable, debuggable, and user-friendly applications. Start by auditing your current API: are you returning 200 for errors? Using 404 when you mean 401? If so, fix those patterns today. Your future self (and your API's consumers) will appreciate it.