DevToys Web Pro iconDevToys Web ProBlog
Traducido con LocalePack logoLocalePack
Califícanos:
Prueba la extensión del navegador:
← Back to Blog

HTTP Status Codes Guide: 1xx–5xx Classes, REST Conventions, and Retry Logic

11 min read

HTTP status codes are the first words an API speaks. Before a client reads a single byte of the response body, the three-digit code tells it whether the request succeeded, where to redirect, or what went wrong. Misusing them breaks automatic retries, invalidates caches, confuses monitoring dashboards, and forces every consumer to parse a JSON body just to detect failure. Use the HTTP Status Reference alongside this guide to look up any code quickly.

The Five Classes

The first digit of every status code indicates the class. Understanding the class is more important than memorizing individual codes, because it tells you the semantics at a glance.

ClassRangeMeaningBody expected?
1xx Informational100–199Request received, processing continuesNo
2xx Success200–299Request was received, understood, and acceptedDepends on code
3xx Redirection300–399Further action needed to complete the requestOptional
4xx Client Error400–499Request is malformed or not allowedUsually yes (error detail)
5xx Server Error500–599Server failed to fulfill a valid requestUsually yes (error detail)

2xx Success in Detail

Most APIs overuse 200 OK and ignore the other 2xx codes. Each code carries precise semantics that clients can act on without parsing the body.

  • 200 OK — Generic success. The response body contains the requested representation. Use for GET and POST operations that return data.
  • 201 Created — A new resource was created. The Location header should point to the new resource URI. Use for POST requests that create entities.
  • 202 Accepted — The request has been accepted for asynchronous processing but is not yet complete. Return a job ID or polling URL so the client can check back.
  • 204 No Content — Success with no body. Clients must not try to parse a body. Use for DELETE and PUT operations that return nothing. Sending a 200 with an empty JSON object {} instead of 204 is a common mistake.
  • 206 Partial Content — The server is delivering only part of a resource in response to a Range request header. Used by video streaming and resumable downloads. The Content-Range header specifies which bytes are included.

3xx Redirection Subtleties

Redirect codes differ in two ways: whether the redirect is permanent or temporary, and whether the HTTP method is preserved.

CodePermanent?Method preserved?Use when
301YesNo — may change to GETResource moved permanently; SEO juice passes to new URL
302NoNo — may change to GETTemporary redirect; most browsers rewrite POST to GET
307NoYes — method must be preservedTemporary redirect that must keep the original method (POST stays POST)
308YesYes — method must be preservedPermanent redirect with method preservation
304N/AN/ACache validation: resource not modified since If-Modified-Since or If-None-Match; no body sent

The practical rule: use 307 instead of 302 whenever you want to guarantee the method is preserved. Use 308 instead of 301 for permanent redirects on non-GET endpoints such as form submission handlers.

4xx Client Errors

4xx codes mean the client sent something the server cannot or will not process. These are not server bugs — they are signals back to the caller about what to fix.

  • 400 Bad Request — The request is syntactically malformed: missing required fields, wrong JSON structure, unparseable date string. The client must fix the request before retrying.
  • 401 Unauthorized — Authentication is required and has not been provided, or the credentials are invalid. Despite the name, this is about authentication, not authorization. Do not return 401 specifically for wrong passwords if the account exists — that leaks account existence. Return 401 generically for any invalid credential.
  • 403 Forbidden — The client is authenticated but does not have permission. Use this when the identity is known but the action is not allowed.
  • 404 Not Found — The resource does not exist at this URL. Safe to return when you intentionally want to hide a resource's existence (as opposed to 403 which confirms the resource exists).
  • 410 Gone — The resource existed but has been permanently deleted and will not return. Unlike 404, it signals that the client should remove any saved reference. Useful for content that was deliberately retired.
  • 409 Conflict — The request conflicts with current server state. Classic uses: duplicate unique key, optimistic concurrency conflict (version mismatch), trying to transition a resource to an invalid state.
  • 422 Unprocessable Entity — The request is syntactically valid (well- formed JSON, correct content-type) but semantically invalid: validation failures, constraint violations, business rule rejections. This is where 422 beats 400 — use 400 for syntax problems, 422 for semantic/validation problems.
  • 429 Too Many Requests — Rate limit exceeded. Should include a Retry-After header indicating when the client may try again.

400 vs 422 in practice

// 400 Bad Request — request body is not valid JSON
// POST /users with body: {name: "Alice" (missing closing brace)

// 422 Unprocessable Entity — valid JSON but fails validation
// POST /users with body:
{
  "name": "",
  "email": "not-an-email",
  "age": -5
}
// Response body should list field-level errors:
{
  "errors": [
    { "field": "name", "message": "must not be blank" },
    { "field": "email", "message": "invalid email format" },
    { "field": "age", "message": "must be a positive integer" }
  ]
}

5xx Server Errors

5xx codes mean the server failed. The client sent a valid request and the server could not fulfill it. This is a server bug or operational issue, not a client mistake.

  • 500 Internal Server Error — Generic catch-all for unhandled exceptions. Useful as a fallback but avoid using it for errors that have a more specific code.
  • 501 Not Implemented — The server does not support the HTTP method used. Rare in APIs; most use 405 Method Not Allowed for disallowed methods on a specific route.
  • 502 Bad Gateway — The server, acting as a proxy or gateway, received an invalid response from an upstream server. Common when a load balancer cannot reach an application instance.
  • 503 Service Unavailable — The server is temporarily unable to handle the request: overloaded, in maintenance, or during deployment. Should include a Retry-After header.
  • 504 Gateway Timeout — The upstream server timed out. Common during slow database queries, external API calls, or cascading failures.

REST API Conventions

These are the standard mappings between CRUD operations and HTTP status codes that clients and frameworks expect.

OperationMethodSuccess codeNotes
List resourcesGET /resources200Empty list is still 200, not 404
Get single resourceGET /resources/:id200404 if not found
Create resourcePOST /resources201Include Location header pointing to the new resource
Full updatePUT /resources/:id200 or 204200 if returning updated resource; 204 if not
Partial updatePATCH /resources/:id200 or 204Same as PUT
Delete resourceDELETE /resources/:id204No body; 404 if resource does not exist
Async actionPOST /resources/:id/action202Return job ID or polling endpoint

Retry-Safe Codes and the Retry-After Header

Not all error codes are safe to retry. Retrying a non-idempotent operation (like a payment POST) on the wrong code can create duplicates. These codes are the canonical retry candidates:

CodeRetry?Condition
408 Request TimeoutYesThe server timed out waiting for the request; safe to retry immediately
429 Too Many RequestsYes, after delayRespect the Retry-After header; use exponential backoff if absent
502 Bad GatewayYesUpstream is temporarily unreachable; retry with backoff
503 Service UnavailableYes, after delayCheck Retry-After; may indicate planned maintenance window
504 Gateway TimeoutYesUpstream timed out; retry with backoff, but only if the operation is idempotent

The Retry-After header value is either a number of seconds or an HTTP date:

# Seconds form
HTTP/1.1 429 Too Many Requests
Retry-After: 60

# Date form
HTTP/1.1 503 Service Unavailable
Retry-After: Mon, 21 Apr 2026 10:00:00 GMT

Idempotency is the key safety property. GET, PUT, DELETE, and HEAD are idempotent by definition. POST is not unless the server implements an idempotency key (a client-provided unique request ID in a header like Idempotency-Key). Always make retries idempotent before enabling automatic retry logic on POST endpoints.

Common Anti-Patterns

  • 200 with an error in the body. Returning 200 OK with body { "error": "User not found" } breaks every HTTP-aware tool: load balancers, APM monitors, retry middleware, and browser devtools all see a successful response. Use 4xx or 5xx codes so the transport layer can react correctly.
  • 500 for user input errors. If the user submits an invalid form, that is a client error, not a server error. Use 400 or 422. Returning 500 inflates your error rate dashboards and hides real server bugs.
  • 401 for wrong password when account exists. Returning a specific message like “password incorrect” (as opposed to “invalid credentials”) confirms the account exists and enables user enumeration attacks. Return a generic 401 with a neutral message for any authentication failure.
  • 404 vs 403 leaking resource existence. If you return 403 for a resource the requester should not know about, you have confirmed it exists. Return 404 when you want to hide existence entirely.
  • Always returning 200 for redirects. Some legacy APIs return 200 with a redirect URL in the body instead of using 3xx. This forces clients to understand a custom protocol and prevents browsers and HTTP clients from following the redirect automatically.

Rare but Useful Codes

  • 103 Early Hints — Sent before the final response to let clients start preloading resources (fonts, scripts, stylesheets) while the server is still preparing the response. Supported by Chrome and some CDNs. Useful on pages with large, predictable asset lists.
  • 418 I'm a teapot — An April Fools joke from RFC 2324 (Hyper Text Coffee Pot Control Protocol). Kept in the registry as a novelty. Do not use in production.
  • 451 Unavailable For Legal Reasons — The server is refusing to serve the resource for legal reasons (takedown order, GDPR compliance, geographic restriction). Named after the novel Fahrenheit 451. Preferred over 403 when the reason is specifically legal.
  • 511 Network Authentication Required — The client must authenticate with the network (typically a captive portal) before access is granted. Used by hotel and airport WiFi systems to redirect users to a login page.

Testing Status Codes

Always write assertions against specific status codes, not just “success”. A test that only checks for a 2xx response will miss the difference between 200 and 204, and will not catch a regression where a 201 endpoint starts returning 200 without a Location header.

curl

# Print only the status code
curl -s -o /dev/null -w "%{http_code}" https://api.example.com/users/1

# Print status code and response body
curl -i https://api.example.com/users/1

# Fail (exit 1) on 4xx or 5xx — useful in CI scripts
curl -f https://api.example.com/health

HTTPie

# HTTPie displays the status line clearly
http GET https://api.example.com/users/1

# Check the exit code
http --check-status GET https://api.example.com/users/1 && echo "OK" || echo "FAILED"

Writing assertions in tests

// Jest + supertest example
describe('POST /users', () => {
  it('returns 201 with Location header on create', async () => {
    const res = await request(app)
      .post('/users')
      .send({ name: 'Alice', email: 'alice@example.com' });

    expect(res.status).toBe(201);
    expect(res.headers['location']).toMatch(//users\/\d+/);
  });

  it('returns 422 with field errors on invalid input', async () => {
    const res = await request(app)
      .post('/users')
      .send({ name: '', email: 'not-an-email' });

    expect(res.status).toBe(422);
    expect(res.body.errors).toHaveLength(2);
  });

  it('returns 429 when rate limit exceeded', async () => {
    // hammer the endpoint beyond the limit
    for (let i = 0; i < 100; i++) {
      await request(app).post('/users').send({ name: 'x', email: 'x@x.com' });
    }
    const res = await request(app).post('/users').send({ name: 'y', email: 'y@y.com' });

    expect(res.status).toBe(429);
    expect(res.headers['retry-after']).toBeDefined();
  });
});

See API Debug Workflow for a practical guide to diagnosing failed requests, and Web Payload Workflow for inspecting request and response bodies in the browser. Use the HTTP Status Reference as a quick lookup when you encounter an unfamiliar code in a network trace.


HTTP status codes are a contract between client and server. Using the right code means clients can react correctly without reading the body, retries happen only when they are safe, caches are invalidated at the right moment, and monitoring systems count real errors instead of noise.