How HTTP Works

Guide

HTTP Status Codes: Full List with Meanings

Every HTTP status code class from 1xx to 5xx with what each code means and when to return it, including the ones you meet most: 200, 301, 304, 404 and 500.

Reviewed 11 min readbeginner2 sourcesTry itMarkdown
On this page

TL;DR: The first digit of a status code tells you what happened: 1xx means “still working”, 2xx means it worked, 3xx means look elsewhere (a redirect or your cached copy), 4xx means the request was wrong, and 5xx means the server failed. In your own API, return 201 for creates, 202 for queued work, 204 when there’s nothing to send back, and a real 4xx or 5xx for errors instead of 200 with an error in the body.

Every HTTP response starts with a three-digit status code. It’s how the server tells the client whether the request succeeded, failed, or needs another step. Reading it correctly is the first move when you debug a failing request, and picking it correctly is part of designing your own API.

This guide walks through the five classes, gives you a decision tree for each, and ends with server and client patterns you can copy. The HTTP responses below are trimmed examples, not captures from a specific server.

Introduction

The codes are standardized, so a 404 means the same thing to nginx, Chrome, curl and your mobile app. The first digit carries the category, and the last two pick a specific outcome within it. A client that doesn’t recognize a code still knows how to treat its class.

Different consumers read the status for different reasons. Browsers use it to decide whether to follow a redirect, use a cached copy or show an error page. Search engines use it to decide what to index. Your client code branches on it, and your monitoring counts it. Getting it wrong shows up everywhere: an error page served as 200 gets indexed, and a 500 that should have been a 400 wakes up whoever is on call.

Each class has a distinct job. Once you know which class an outcome belongs to, picking the specific code is usually a short decision.

The Five Status Code Classes

1xx Informational Responses

A 1xx response says the server has the request and is still working on it. It’s an interim response: a final status always follows.

Key Characteristics:

  • Provisional responses during request processing
  • Client should wait for final response
  • Rarely used in typical web applications
  • More common in streaming and long-running operations

Common 1xx Codes:

100 Continue

HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1247

{"status": "upload complete"}

The server sends this when a client asked with Expect: 100-continue before uploading a large body. The client waits for the go-ahead, sends the body, then gets the final status.

101 Switching Protocols

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

The server agrees to switch protocols on this connection, most often from HTTP/1.1 to WebSocket. The cleartext HTTP/1.1-to-HTTP/2 upgrade (h2c) also used 101, but RFC 9113 deprecated it; HTTP/2 is negotiated through TLS ALPN instead.

102 Processing (WebDAV)

HTTP/1.1 102 Processing

Tells the client the server is still working on a long request, so it shouldn’t time out yet.

2xx Success Responses

A 2xx means the server received, understood and accepted the request. The specific code tells the client what kind of success it got.

Decision Framework for 2xx Codes:

Request successful?
├─ Yes → Was resource created?
│  ├─ Yes → 201 Created
│  └─ No → Was content returned?
│     ├─ Yes → 200 OK
│     ├─ No content → 204 No Content
│     └─ Accepted for processing → 202 Accepted
└─ No → Use 4xx or 5xx

Common 2xx Codes:

200 OK - Standard Success

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 156

{
  "users": [
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"}
  ]
}

The everyday success response. The request worked and the body carries the result.

201 Created - Resource Creation

HTTP/1.1 201 Created
Location: /api/users/123
Content-Type: application/json

{
  "id": 123,
  "name": "Charlie",
  "createdAt": "2026-01-18T10:30:00Z"
}

The request created a new resource. Send a Location header that points to it.

202 Accepted - Asynchronous Processing

HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "jobId": "job_abc123",
  "status": "processing",
  "estimatedCompletion": "2026-01-18T10:35:00Z"
}

The server accepted the request but hasn’t finished the work. Use it for background jobs, and give the client a job ID or status URL to poll.

204 No Content - Successful with No Response Body

HTTP/1.1 204 No Content
Cache-Control: no-cache

The request worked and there’s nothing to send back. You’ll see it on DELETEs and on updates that don’t echo the resource.

3xx Redirection Responses

A 3xx means the client has to do something else to finish. Usually that’s following a Location header to a new URL; with 304 it means reusing the copy it already has.

Redirection Decision Tree:

Resource moved?
├─ Permanently → 301 Moved Permanently
├─ Temporarily → 302 Found or 307 Temporary Redirect
├─ Not modified → 304 Not Modified
└─ Multiple choices → 300 Multiple Choices

Common 3xx Codes:

301 Moved Permanently - Permanent Redirect

HTTP/1.1 301 Moved Permanently
Location: https://newdomain.com/api/users
Cache-Control: max-age=31536000

<html>
<body>Resource has moved to <a href="https://newdomain.com/api/users">new location</a></body>
</html>

The resource has a new permanent URL. Clients and caches can remember the redirect, and search engines move their index entries to the new URL.

302 Found - Temporary Redirect

HTTP/1.1 302 Found
Location: /login?redirect=/dashboard
Cache-Control: no-cache

The resource is temporarily somewhere else. Keep using the original URL for future requests.

304 Not Modified - Cached Content Valid

HTTP/1.1 304 Not Modified
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: max-age=300

The client’s cached copy is still current, so the server sends no body and the client uses what it has.

307 Temporary Redirect - Method Preserved

HTTP/1.1 307 Temporary Redirect
Location: /api/v2/users

Like 302, but the client must repeat the request with the same method and body. A POST stays a POST.

308 Permanent Redirect - Method Preserved

HTTP/1.1 308 Permanent Redirect
Location: https://api.example.com/v2/users

Like 301, but the method and body stay the same on the redirected request.

4xx Client Error Responses

A 4xx means the problem is on the client’s side: bad syntax, missing credentials, a resource that isn’t there, or a request the server won’t fulfill. Sending the same request again unchanged usually gets the same answer; the client has to fix something first.

Client Error Decision Framework:

Request valid?
├─ No → Malformed syntax → 400 Bad Request
├─ Yes → Authorized?
│  ├─ No → Authentication required → 401 Unauthorized
│  ├─ Forbidden → 403 Forbidden
│  └─ Yes → Resource exists?
│     ├─ No → 404 Not Found
│     ├─ Yes → Method allowed?
│     │  ├─ No → 405 Method Not Allowed
│     │  └─ Yes → Content acceptable?
│     │     ├─ No → 406 Not Acceptable
│     │     └─ Yes → Rate limited?
│     │        ├─ Yes → 429 Too Many Requests
│     │        └─ No → Process request
└─ Validation failed → 422 Unprocessable Entity

Common 4xx Codes:

400 Bad Request - Malformed Request

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "Invalid JSON syntax",
  "details": "Unexpected token '}' at position 45",
  "code": "INVALID_JSON"
}

The server can’t parse or understand the request, such as malformed JSON.

401 Unauthorized - Authentication Required

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
Content-Type: application/json

{
  "error": "Authentication required",
  "message": "Please provide a valid access token"
}

The request has no valid credentials. Send a WWW-Authenticate header so the client knows how to authenticate.

403 Forbidden - Access Denied

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "Insufficient permissions",
  "message": "Admin access required for this operation"
}

The server knows what you asked for and won’t allow it. Repeating the request with the same credentials won’t change the answer.

404 Not Found - Resource Missing

HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "error": "Resource not found",
  "message": "User with ID 999 does not exist"
}

There’s nothing at this URL, or the server won’t say whether there is.

422 Unprocessable Entity - Validation Failed

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "error": "Validation failed",
  "details": [
    {"field": "email", "message": "Invalid email format"},
    {"field": "age", "message": "Must be between 18 and 120"}
  ]
}

The request parses fine, but the content doesn’t make sense, such as a field that fails validation.

429 Too Many Requests - Rate Limited

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

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

The client hit a rate limit. Send Retry-After so it knows when to try again.

5xx Server Error Responses

A 5xx means the server failed to handle a request that looked valid. The client can’t fix it by changing the request; it can wait and retry, if retrying is safe for that method.

Server Error Decision Framework:

Server error occurred?
├─ Generic error → 500 Internal Server Error
├─ Feature not implemented → 501 Not Implemented
├─ Upstream server error → 502 Bad Gateway
├─ Service unavailable → 503 Service Unavailable
├─ Upstream timeout → 504 Gateway Timeout
└─ HTTP version unsupported → 505 HTTP Version Not Supported

Common 5xx Codes:

500 Internal Server Error - Generic Server Error

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
  "error": "Internal server error",
  "message": "An unexpected error occurred",
  "requestId": "req_abc123"
}

The catch-all for an unexpected failure. Include a request ID so you can find it in your logs.

502 Bad Gateway - Upstream Server Error

HTTP/1.1 502 Bad Gateway
Content-Type: application/json

{
  "error": "Bad gateway",
  "message": "Database connection failed"
}

A gateway or proxy got an invalid response from the server behind it.

503 Service Unavailable - Temporary Unavailability

HTTP/1.1 503 Service Unavailable
Retry-After: 120
Content-Type: application/json

{
  "error": "Service unavailable",
  "message": "Server is temporarily overloaded"
}

The server can’t take the request right now, typically during maintenance or overload. Add Retry-After when you know how long.

504 Gateway Timeout - Upstream Timeout

HTTP/1.1 504 Gateway Timeout
Content-Type: application/json

{
  "error": "Gateway timeout",
  "message": "Upstream server did not respond in time"
}

A gateway or proxy gave up waiting for the server behind it.

Status Code Selection Patterns

RESTful API Patterns

Resource Creation:

// POST /api/users
if (validationErrors.length > 0) {
  return res.status(422).json({ errors: validationErrors })
}

if (userAlreadyExists) {
  return res.status(409).json({ error: 'User already exists' })
}

const user = await createUser(userData)
return res.status(201).json(user)

Resource Retrieval:

// GET /api/users/:id
const user = await findUser(userId)

if (!user) {
  return res.status(404).json({ error: 'User not found' })
}

return res.status(200).json(user)

Resource Updates:

// PUT /api/users/:id
const user = await findUser(userId)

if (!user) {
  return res.status(404).json({ error: 'User not found' })
}

if (validationErrors.length > 0) {
  return res.status(422).json({ errors: validationErrors })
}

const updatedUser = await updateUser(userId, userData)
return res.status(200).json(updatedUser)

Resource Deletion:

// DELETE /api/users/:id
const user = await findUser(userId)

if (!user) {
  return res.status(404).json({ error: 'User not found' })
}

await deleteUser(userId)
return res.status(204).send()

Error Handling Patterns

Graceful Degradation:

try {
  const result = await processRequest(data)
  return res.status(200).json(result)
} catch (error) {
  if (error instanceof ValidationError) {
    return res.status(422).json({ error: error.message })
  }

  if (error instanceof NotFoundError) {
    return res.status(404).json({ error: error.message })
  }

  // Log unexpected errors
  logger.error('Unexpected error:', error)
  return res.status(500).json({
    error: 'Internal server error',
    requestId: req.id
  })
}

Authentication and Authorization Patterns

Authentication Flow:

// Check if token exists
if (!token) {
  return res.status(401).json({
    error: 'Authentication required',
    message: 'Please provide an access token'
  })
}

// Validate token
const user = await validateToken(token)
if (!user) {
  return res.status(401).json({
    error: 'Invalid token',
    message: 'Please log in again'
  })
}

// Check permissions
if (!user.hasPermission(requiredPermission)) {
  return res.status(403).json({
    error: 'Insufficient permissions',
    message: 'Admin access required'
  })
}

Getting Status Codes right

Consistency Guidelines

Use Standard Codes:

  • Use the well-known codes; clients and tools already understand them
  • Reach for a rarer code only when it tells the client something the common ones can’t
  • Document any non-standard code your API returns

Provide Helpful Error Messages:

{
  "error": "Validation failed",
  "message": "The request contains invalid data",
  "details": [
    {
      "field": "email",
      "code": "INVALID_FORMAT",
      "message": "Email must be a valid email address"
    }
  ],
  "requestId": "req_abc123"
}

Client-Side Handling

Branch on the class first, then handle the specific codes your app cares about. fetch follows redirects for you by default, so the 3xx branch below mainly matters for clients that don’t.

Status Code Categories:

function handleResponse(response) {
  const status = response.status

  if (status >= 200 && status < 300) {
    // Success - process response data
    return response.json()
  }

  if (status >= 300 && status < 400) {
    // Redirection - follow redirect or update URL
    window.location.href = response.headers.get('Location')
  }

  if (status >= 400 && status < 500) {
    // Client error - show user-friendly error message
    throw new ClientError('Please check your request and try again')
  }

  if (status >= 500) {
    // Server error - show generic error, maybe retry
    throw new ServerError('Something went wrong. Please try again later')
  }
}

Monitoring and Debugging

Status Code Metrics:

// Track status code distribution
const statusCodeCounts = {
  '2xx': 0, // Success rate
  '3xx': 0, // Redirect rate
  '4xx': 0, // Client error rate
  '5xx': 0 // Server error rate
}

// Alert on high error rates
if (statusCodeCounts['5xx'] / totalRequests > 0.01) {
  alert('High server error rate detected')
}

Common Anti-Patterns

Incorrect Status Code Usage

Don’t Use 200 for Errors:

// Bad: Error with 200 status
res.status(200).json({
  success: false,
  error: 'User not found'
})

// Good: Proper error status
res.status(404).json({
  error: 'User not found'
})

Don’t Use Generic Codes:

// Bad: Generic 400 for validation errors
res.status(400).json({ error: 'Invalid data' })

// Good: Specific 422 for validation errors
res.status(422).json({
  error: 'Validation failed',
  details: validationErrors
})

Missing Error Context

Provide Actionable Information:

// Bad: Vague error message
res.status(403).json({ error: 'Forbidden' })

// Good: Specific guidance
res.status(403).json({
  error: 'Insufficient permissions',
  message: 'This operation requires admin privileges',
  requiredRole: 'admin',
  currentRole: 'user'
})

Status codes connect to most of the rest of HTTP:

If you’re designing an API, start with the codes most clients already handle (200, 201, 204, 400, 401, 403, 404, 500) and add more specific ones when they tell the client something it can act on.

Check your understanding

Guided Lab

Choose status codes from real product scenarios

Use the status picker to compare outcomes for synchronous success, background processing, and validation failure.

Open Status Picker
  1. Answer the flow once for a successful resource creation and note why it lands on `201 Created`.
  2. Run it again for a job that was accepted but is still running and compare the result with `202 Accepted`.
  3. Run a third pass for invalid request data and note why the tool steers you away from generic success responses.

Look for: You should see that good status-code choices come from request outcome and client expectations, not from habit.

Why it matters: The right status code is part of the contract your client code relies on.

Interactive Check

Choose the correct async response

A client uploads a video, and your API queues transcoding that will finish later. Which response is the best default?

Choose the correct async response

Frequently asked questions

What are HTTP status codes?

Status codes are 3-digit numbers indicating the result of an HTTP request. They are grouped: 1xx informational, 2xx success, 3xx redirect, 4xx client error, 5xx server error.

What are the most common status codes?

200 OK (success), 301/302 (redirect), 400 (bad request), 401 (unauthorized), 403 (forbidden), 404 (not found), 500 (server error), 503 (service unavailable).

What is the difference between 4xx and 5xx errors?

4xx errors are client errors (bad request, not found). 5xx errors are server errors (server crashed, overloaded). 4xx means fix your request; 5xx means wait or contact support.

How do I handle status codes in my application?

Check the status code before processing the response. Handle success (2xx), follow redirects (3xx), show user-friendly errors for 4xx, and retry or alert for 5xx.

Sources

  1. MDN Web Docs: HTTP Status Codesdeveloper.mozilla.org
  2. RFC 9110: Status Codesrfc-editor.org

Keep going

Browse /search