NC Logo UseToolSuite
Web Development

HTTP Status Codes: The Complete Guide for Developers

Every HTTP status code explained with real-world examples. Learn when APIs should return 200 vs 201, why 403 and 401 are different, and how to debug 5xx errors in production.

Necmeddin Cunedioglu Necmeddin Cunedioglu

Practice what you learn

JSON Formatter & Validator

Try it free →

Early in my career, I built an API that returned 200 OK for literally everything — successful requests, validation errors, authentication failures, all of them. The response body had an error field, and the frontend checked that to figure out what happened. It worked, but it was wrong in ways that caused real problems: HTTP caches stored error responses as valid results, monitoring tools reported 100% success rates while the system was half-broken, and every client consumer had to parse the body before knowing if the request succeeded.

Status codes exist for a reason. They’re the first thing a client checks, the first thing monitoring tools count, and often the only thing proxies and CDNs understand. Here’s everything you need to know to use them correctly.

The Five Categories

HTTP status codes are grouped into five classes by their first digit:

RangeCategoryMeaning
1xxInformationalRequest received, processing continues
2xxSuccessRequest was successfully processed
3xxRedirectionFurther action needed to complete the request
4xxClient ErrorThe request has a problem (your fault)
5xxServer ErrorThe server failed to process a valid request (our fault)

The distinction between 4xx and 5xx is fundamental: 4xx means the client did something wrong, 5xx means the server did something wrong. Getting this right matters for monitoring, debugging, and SLA reporting.

2xx Success: More Than Just 200

200 OK

The most common status code. Use it for successful GET requests that return data, and for successful updates that return the modified resource.

GET /api/users/123  →  200 { "id": 123, "name": "Alice" }
PUT /api/users/123  →  200 { "id": 123, "name": "Alice Updated" }

201 Created

Use 201 when a request successfully creates a new resource. Always include a Location header pointing to the newly created resource.

POST /api/users  →  201 Created
Location: /api/users/456
{ "id": 456, "name": "New User" }

I’ve seen many APIs return 200 for creation. It works, but it makes it impossible for clients to distinguish between “I updated an existing resource” and “I created a new one” without inspecting the response body.

204 No Content

Use 204 for successful requests that don’t return any body — typically DELETE operations or updates where the client doesn’t need the response.

DELETE /api/users/123  →  204 (no body)

Important: A 204 response must not contain a body. If you need to return data after a DELETE (like a confirmation), use 200 instead.

3xx Redirection: Get Them Right for SEO

301 Moved Permanently

The resource has permanently moved to a new URL. Search engines transfer SEO authority to the new URL. Use this when you’re changing URL structures permanently.

GET /old-page  →  301 Moved Permanently
Location: /new-page

302 Found (Temporary Redirect)

The resource temporarily lives at a different URL. The client should continue using the original URL for future requests. Use this for maintenance pages, A/B testing, or locale-based redirects.

304 Not Modified

The resource hasn’t changed since the client’s last request. The browser uses its cached version. This is handled automatically by the browser and server using ETag and If-None-Match headers — you rarely set 304 manually, but understanding it helps with debugging cache issues.

The 301 vs 302 Mistake

Using 302 when you mean 301 is one of the most common SEO mistakes. A 302 tells search engines “this page has temporarily moved, keep indexing the old URL.” A 301 tells them “this page has permanently moved, update your index.” If you’re reorganizing your site structure, always use 301.

4xx Client Errors: Be Specific

400 Bad Request

The request is malformed. Use this for invalid JSON, missing required fields, or values that fail basic validation.

POST /api/users
{ "email": "not-an-email", "age": -5 }

400 Bad Request
{
  "error": "Validation failed",
  "details": [
    { "field": "email", "message": "Invalid email format" },
    { "field": "age", "message": "Must be a positive number" }
  ]
}

Always include a meaningful error message. A bare 400 with no explanation is useless for debugging.

401 Unauthorized vs 403 Forbidden

These two are confused constantly, even by experienced developers.

  • 401 Unauthorized — The client didn’t provide authentication credentials, or the credentials are invalid. The fix is to authenticate (log in, provide a valid token).
  • 403 Forbidden — The client is authenticated (we know who you are), but you don’t have permission to access this resource. Re-authenticating won’t help.
# No token provided
GET /api/admin/users  →  401 Unauthorized

# Valid token, but user is not an admin
GET /api/admin/users  →  403 Forbidden
Authorization: Bearer valid-user-token

The naming is misleading — “401 Unauthorized” should really be called “401 Unauthenticated.” The distinction matters because clients handle them differently: on 401, redirect to login; on 403, show a “you don’t have access” message.

Working with JWT tokens? Our JWT Decoder lets you inspect token claims and expiration without verification — essential for debugging 401/403 issues.

404 Not Found

The resource doesn’t exist. This seems straightforward, but there’s a security nuance: if a resource exists but the user doesn’t have access, should you return 403 or 404? Returning 404 prevents information leakage — an attacker can’t enumerate which resources exist by checking for 403 vs 404 responses.

405 Method Not Allowed

The HTTP method isn’t supported for this endpoint. Include an Allow header listing the supported methods:

DELETE /api/users  →  405 Method Not Allowed
Allow: GET, POST

409 Conflict

The request conflicts with the current state of the server. Use this for duplicate entries, version conflicts, or state machine violations.

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

422 Unprocessable Entity

The request is well-formed (valid JSON, correct content type) but semantically invalid. This is distinct from 400: the syntax is fine, but the business logic rejects it.

POST /api/orders
{ "quantity": 5, "product_id": "ABC" }  →  422 Unprocessable Entity
{ "error": "Product ABC is out of stock" }

429 Too Many Requests

Rate limiting. Include a Retry-After header telling the client how long to wait:

GET /api/data  →  429 Too Many Requests
Retry-After: 60
{ "error": "Rate limit exceeded. Try again in 60 seconds." }

5xx Server Errors: Keep Them Rare

500 Internal Server Error

The generic “something went wrong” on the server. In production, never expose internal error details (stack traces, database error messages) in the response body — log them server-side and return a generic message to the client.

// DON'T do this in production
{ "error": "Connection refused: PostgreSQL at 10.0.0.4:5432" }

// DO this
{ "error": "An internal error occurred. Please try again later." }

502 Bad Gateway

The server, acting as a gateway or proxy, received an invalid response from an upstream server. You’ll see this when Nginx can’t reach your application server, or when a microservice is down.

503 Service Unavailable

The server is temporarily unable to handle the request — usually due to maintenance or overload. Include a Retry-After header when possible:

GET /api/data  →  503 Service Unavailable
Retry-After: 300

504 Gateway Timeout

The upstream server didn’t respond in time. Common when database queries are slow or downstream APIs are unresponsive.

Designing API Error Responses

A consistent error response format saves hours of debugging. Here’s the pattern I use across all my APIs:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "One or more fields failed validation",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address",
        "value": "not-an-email"
      }
    ]
  }
}

Key principles:

  • Machine-readable error code (VALIDATION_ERROR, not just the HTTP status)
  • Human-readable message for debugging
  • Field-level details for validation errors
  • Consistent shape across all error responses

Monitoring and Alerting by Status Code

Once you’re using status codes correctly, monitoring becomes powerful:

  • Alert on 5xx spike — servers are failing, investigate immediately
  • Track 4xx trends — rising 404s might mean broken links; rising 401s might mean token expiration issues
  • Ignore 3xx — redirects are expected behavior
  • Dashboard 2xx success rate — your real success metric

A well-instrumented system that uses status codes properly can detect production issues in seconds, not hours.

Quick Reference

CodeNameWhen to Use
200OKSuccessful GET, PUT, PATCH
201CreatedSuccessful POST that creates a resource
204No ContentSuccessful DELETE or update with no body
301Moved PermanentlyPermanent URL change (SEO-safe)
302FoundTemporary redirect
304Not ModifiedCache hit (automatic)
400Bad RequestMalformed request / validation failure
401UnauthorizedMissing or invalid authentication
403ForbiddenAuthenticated but insufficient permissions
404Not FoundResource doesn’t exist
409ConflictDuplicate or state conflict
422UnprocessableSemantically invalid request
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server failure
502Bad GatewayUpstream server error
503Service UnavailableTemporary overload or maintenance
504Gateway TimeoutUpstream server timeout

Further Reading


Debugging API responses? Our JSON Formatter beautifies API responses instantly, and the JWT Decoder helps you inspect authentication tokens when troubleshooting 401/403 errors.

Necmeddin Cunedioglu
Necmeddin Cunedioglu Author

Software developer and the creator of UseToolSuite. I write about the tools and techniques I use daily as a developer — practical guides based on real experience, not theory.