Every HTTP response includes a status code that tells the client what happened with its request. Knowing which code to return in each situation is fundamental to building good APIs. Using the wrong status code confuses clients, breaks caching, and makes debugging harder.

Here is a practical breakdown of the most important status codes grouped by category.

1xx - Informational

These are rarely used directly in application code but are part of the protocol.

  • 100 Continue - The server received the request headers and the client should proceed to send the body. Used when a client sends an Expect: 100-continue header before uploading a large file.
  • 101 Switching Protocols - The server is switching to a different protocol as requested. This is what happens during a WebSocket handshake - the connection upgrades from HTTP to WebSocket.

2xx - Success

The request was received, understood, and accepted.

  • 200 OK - The standard success response. Use this for successful GET requests that return data, and for PUT/PATCH requests that update a resource and return the updated version.
  • 201 Created - A new resource was created. Use this for successful POST requests. The response should include the created resource and ideally a Location header pointing to it.
  • 204 No Content - The request succeeded but there is nothing to send back. Use this for successful DELETE requests or PUT requests where the client does not need the updated resource.
// Express.js examples
app.post('/users', (req, res) => {
  const user = createUser(req.body);
  res.status(201).json(user);         // 201: resource created
});

app.delete('/users/:id', (req, res) => {
  deleteUser(req.params.id);
  res.status(204).send();             // 204: deleted, nothing to return
});

3xx - Redirection

The client needs to take additional action to complete the request.

  • 301 Moved Permanently - The resource has moved to a new URL permanently. Browsers and search engines will update their records. Use this when you change URL structure.
  • 302 Found - Temporary redirect. The resource is temporarily at a different URL. Browsers will keep using the original URL for future requests.
  • 304 Not Modified - The resource has not changed since the last request. The client should use its cached version. This saves bandwidth and speeds up loading.

The difference between 301 and 302 matters for SEO and caching. A 301 tells search engines to index the new URL. A 302 tells them to keep the original.

4xx - Client Errors

The request contains bad syntax or cannot be fulfilled. These indicate the client did something wrong.

  • 400 Bad Request - The request body is malformed or missing required fields. Use this when validation fails.
  • 401 Unauthorized - The client is not authenticated. Despite the name, this means "unauthenticated" - the server does not know who the client is. Return this when no valid token or session is provided.
  • 403 Forbidden - The client is authenticated but does not have permission. The server knows who you are, but you are not allowed to access this resource.
  • 404 Not Found - The resource does not exist. Also commonly used to hide the existence of a resource from unauthorized users (instead of returning 403).
  • 409 Conflict - The request conflicts with the current state of the server. Use this when trying to create a resource that already exists (e.g., duplicate email registration).
  • 422 Unprocessable Entity - The request is well-formed but semantically invalid. Some teams use this instead of 400 for validation errors, particularly in APIs following JSON:API or similar specs.
  • 429 Too Many Requests - Rate limiting. The client has sent too many requests in a given time window. Include a Retry-After header to tell the client when to try again.
// Common pattern for auth vs authz
app.get('/admin/settings', (req, res) => {
  if (!req.user) {
    return res.status(401).json({ error: 'Authentication required' });
  }
  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Admin access required' });
  }
  // ... return settings
});

5xx - Server Errors

The server failed to fulfill a valid request. These indicate something went wrong on the server side.

  • 500 Internal Server Error - A generic server error. Use this as a fallback when an unexpected exception occurs. Never expose stack traces or internal details in the response body in production.
  • 502 Bad Gateway - The server acting as a gateway or proxy received an invalid response from the upstream server. Common when a reverse proxy (Nginx) cannot reach the application server.
  • 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.
  • 504 Gateway Timeout - The upstream server did not respond in time. Similar to 502, but specifically a timeout rather than an invalid response.

Choosing the Right Code

A quick decision tree:

Did the request succeed?
  Yes -> Was a resource created? -> 201
         Was there content to return? -> 200
         No content needed? -> 204
  No  -> Is it the client's fault?
           Not authenticated? -> 401
           Not authorized? -> 403
           Resource not found? -> 404
           Bad input? -> 400
           Rate limited? -> 429
         Is it the server's fault?
           Unexpected error? -> 500
           Upstream down? -> 502
           Temporarily unavailable? -> 503

Final Notes

Be consistent across your API. If you use 422 for validation errors, use it everywhere. Document which codes your API returns and under what conditions. Good status code usage makes your API self-documenting and significantly easier to debug.