HTTP Status Codes Explained

HTTP Status Codes Explained

Status codes are the first thing your code should check in any HTTP response. They communicate the outcome of a request in a standardised, machine-readable way. Understanding them deeply helps you write better error handling and debug issues faster.


1. The Five Categories

Every status code begins with a digit that tells you the broad category:

1xx ─── Informational  "I received your request, still working..."
2xx ─── Success        "It worked!"
3xx ─── Redirection    "It's over there now."
4xx ─── Client Error   "You did something wrong."
5xx ─── Server Error   "I did something wrong."

The remaining two digits are specific codes within that category.


2. 1xx — Informational

Rarely seen by application code. These are protocol-level signals.

CodeNameWhen used
100ContinueServer received the request headers; client should proceed with the body
101Switching ProtocolsServer agrees to upgrade (e.g. HTTP → WebSocket)
102ProcessingServer is working on a long operation (WebDAV)

3. 2xx — Success

200 OK

The gold standard. Everything worked perfectly.

GET /api/courses/1 HTTP/1.1
→ HTTP/1.1 200 OK
  Content-Type: application/json
  {"id":1,"title":"HTML5","chapters":18}

201 Created

A new resource was successfully created. Should always include a Location header pointing to the new resource's URL.

POST /api/users HTTP/1.1
{"email":"alice@example.com"}
→ HTTP/1.1 201 Created
  Location: /api/users/99
  {"id":99,"email":"alice@example.com","createdAt":"2025-03-31T07:38:00Z"}

202 Accepted

The request has been received but processing has not completed. Used for async operations.

POST /api/reports/generate HTTP/1.1
→ HTTP/1.1 202 Accepted
  {"jobId":"gen-7d3c9f","status":"queued","checkAt":"/api/jobs/gen-7d3c9f"}

204 No Content

Success, but there's nothing to return (body is empty). Common for DELETE and PATCH operations.

DELETE /api/courses/42 HTTP/1.1
→ HTTP/1.1 204 No Content
  [no body]

206 Partial Content

Sent in response to a Range header. Used by video players to stream mid-file.

GET /videos/intro.mp4 HTTP/1.1
Range: bytes=0-999999
→ HTTP/1.1 206 Partial Content
  Content-Range: bytes 0-999999/52428800
  [first 1MB of video data]

4. 3xx — Redirection

301 Moved Permanently

The resource has moved forever. Search engines update their index; browsers cache the redirect.

GET /old-courses HTTP/1.1
→ HTTP/1.1 301 Moved Permanently
  Location: /courses

302 Found (Temporary Redirect)

Temporarily redirected. Search engines keep the old URL; browsers don't cache it.

GET /api/login HTTP/1.1
→ HTTP/1.1 302 Found
  Location: /api/v2/auth/login

304 Not Modified

The client's cached version is still valid. No body is sent — the browser uses its cache.

GET /api/courses/1 HTTP/1.1
If-None-Match: "a1b2c3"
→ HTTP/1.1 304 Not Modified
  [no body — use your cached version]

307 Temporary Redirect

Like 302, but the HTTP method must not change. A POST to /action stays POST at the redirect target.

308 Permanent Redirect

Like 301, but the HTTP method must not change. Used when moving API endpoints permanently.


5. 4xx — Client Errors

These mean the request had a problem that the client needs to fix.

400 Bad Request

Malformed request — missing required fields, invalid JSON, wrong data type.

// Client sent this:
{ "username": 123 }  // should be a string!

// Server responds:
// HTTP/1.1 400 Bad Request
// {"error":"username must be a string"}

401 Unauthorized

The client needs to authenticate first. Despite the name, this means unauthenticated — you haven't proven who you are yet.

// No Authorization header sent
fetch('/api/profile')
// → 401 Unauthorized
// WWW-Authenticate: Bearer realm="api"

// After logging in:
fetch('/api/profile', {
  headers: { 'Authorization': `Bearer ${token}` }
})
// → 200 OK

403 Forbidden

You are authenticated, but you don't have permission to access this specific resource.

// Alice is logged in but tries to access Bob's private data:
GET /api/users/bob/private-notes HTTP/1.1
Authorization: Bearer alice-token
→ HTTP/1.1 403 Forbidden
  {"error":"You can only access your own notes"}

404 Not Found

The requested resource does not exist at this URL.

GET /api/courses/99999 HTTP/1.1
→ HTTP/1.1 404 Not Found
  {"error":"Course not found"}

Be careful: Some APIs return 404 instead of 403 to avoid revealing that a resource exists at all (security through obscurity). Others prefer the explicit 403.

405 Method Not Allowed

The URL is valid but doesn't support the HTTP method used.

DELETE /api/public-announcements/1 HTTP/1.1
→ HTTP/1.1 405 Method Not Allowed
  Allow: GET, HEAD
  {"error":"Public announcements cannot be deleted via the API"}

Note the Allow header listing the permitted methods.

408 Request Timeout

The server gave up waiting for the client to send the complete request.

409 Conflict

The request conflicts with the current state of the resource.

POST /api/users HTTP/1.1
{"email":"existing@example.com"}
→ HTTP/1.1 409 Conflict
  {"error":"A user with this email already exists"}

410 Gone

Like 404, but explicitly permanent — the resource once existed and has been deleted. Search engines should de-index it.

422 Unprocessable Entity

The request is syntactically valid, but the data fails business logic validation.

POST /api/users HTTP/1.1
{"email":"not-an-email","age":-5}
→ HTTP/1.1 422 Unprocessable Entity
  {
    "errors": [
      {"field":"email","message":"Must be a valid email address"},
      {"field":"age","message":"Must be a positive number"}
    ]
  }

429 Too Many Requests

Rate limit exceeded. The client is sending too many requests.

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-Rate-Limit-Limit: 100
X-Rate-Limit-Remaining: 0
X-Rate-Limit-Reset: 1711864800

{"error":"Rate limit exceeded. Try again in 60 seconds."}

6. 5xx — Server Errors

These mean the server failed — not the client's fault.

500 Internal Server Error

An unhandled exception or bug on the server. The catch-all for server-side failures.

GET /api/courses/crash-this
→ HTTP/1.1 500 Internal Server Error
  {"error":"An unexpected error occurred. Reference: req-7e4c9f21"}

Always return a reference ID (not the stack trace) so developers can look it up in logs without exposing internals.

502 Bad Gateway

The server is acting as a gateway/proxy, and the upstream server it was talking to returned an invalid response.

Browser → nginx (reverse proxy) → Node.js app (crashed)
→ nginx responds: 502 Bad Gateway

503 Service Unavailable

The server is temporarily unable to handle the request (overloaded, maintenance mode, etc.).

HTTP/1.1 503 Service Unavailable
Retry-After: 300

{"error":"The service is under maintenance. Back in 5 minutes."}

504 Gateway Timeout

The upstream server didn't respond in time.

Browser → nginx → Slow database query (30s+)
nginx gives up after 25s → 504 Gateway Timeout

7. Handling Status Codes in JavaScript

async function apiRequest(url, options = {}) {
  const response = await fetch(url, options);
  const contentType = response.headers.get('content-type') ?? '';
  const isJson = contentType.includes('application/json');

  switch (true) {
    case response.status === 204:
      return null; // No content

    case response.ok:  // 200–299
      return isJson ? response.json() : response.text();

    case response.status === 401:
      // Redirect to login or refresh token
      window.location.href = '/login';
      throw new Error('Session expired. Please log in again.');

    case response.status === 403:
      throw new Error('You do not have permission to do this.');

    case response.status === 404:
      throw new Error('Not found.');

    case response.status === 409: {
      const body = isJson ? await response.json() : {};
      throw new Error(body.error ?? 'Conflict: resource already exists.');
    }

    case response.status === 422: {
      const body = isJson ? await response.json() : {};
      const messages = body.errors?.map(e => `${e.field}: ${e.message}`).join(', ');
      throw new Error('Validation failed: ' + messages);
    }

    case response.status === 429: {
      const retryAfter = response.headers.get('retry-after') ?? '60';
      throw new Error(`Rate limited. Try again in ${retryAfter}s.`);
    }

    case response.status >= 500:
      throw new Error(`Server error (${response.status}). Please try again later.`);

    default:
      throw new Error(`Unexpected status: ${response.status}`);
  }
}

8. Status Code Decision Guide (for API Design)

Request comes in — what should I respond?
│
├── Did it succeed?
│   ├── Reading?     → 200 OK
│   ├── Creating?    → 201 Created + Location header
│   ├── Async?       → 202 Accepted + jobId
│   └── Deleting or PATCH with no return value? → 204 No Content
│
├── Client's fault?
│   ├── Malformed body/missing fields? → 400 Bad Request
│   ├── Not logged in?                 → 401 Unauthorized + WWW-Authenticate
│   ├── Logged in, no permission?      → 403 Forbidden
│   ├── URL doesn't exist?             → 404 Not Found
│   ├── Wrong HTTP method?             → 405 Method Not Allowed + Allow header
│   ├── Duplicate resource?            → 409 Conflict
│   ├── Invalid field values?          → 422 Unprocessable Entity
│   └── Too many requests?             → 429 Too Many Requests + Retry-After
│
└── Server's fault?
    ├── Unhandled exception?   → 500 Internal Server Error
    ├── Upstream service down? → 502 Bad Gateway
    ├── Overloaded/maintenance?→ 503 Service Unavailable + Retry-After
    └── Upstream timed out?    → 504 Gateway Timeout

9. Common Mistakes

MistakeProblemFix
Returning 200 for errors (e.g. {"success":false})Client code can't detect failure without parsing bodyUse the correct 4xx or 5xx status code
Returning 500 for validation failuresMisleads client into thinking it's a server bugUse 400 for bad input, 422 for validation failures
Using 404 when resource is forbiddenHides auth issues — may frustrate legitimate usersUse 403 explicitly (unless hiding resource existence is intentional)
Not including error details in 4xx bodiesDeveloper can't debug without calling supportReturn a structured error body with field-level messages
Returning a stack trace in 500 responsesLeaks internal server information to attackersLog the trace server-side; return only a reference ID
Never using 201/204 — always returning 200API consumers can't know if something was createdUse semantically correct codes

10. Review Questions

  1. What is the difference between 401 and 403?
  2. When would you use 202 Accepted instead of 200 OK?
  3. What should a server always include in a 201 Created response?
  4. What does 304 Not Modified mean and what is the benefit of triggering it?
  5. What is the difference between 404 Not Found and 410 Gone?
  6. When should you use 422 Unprocessable Entity rather than 400 Bad Request?
  7. What header should accompany a 429 Too Many Requests response?