---
title: "HTTP Status Codes: A Developer's Cheat Sheet (With Fixes for the Common Errors)"
url: https://apyhub.com/blog/http-status-codes-cheat-sheet
author: Nikolas Dimitroulakis
published: 2026-07-01T00:00:00Z
tags: [engineering]
---

# HTTP Status Codes: A Developer's Cheat Sheet (With Fixes for the Common Errors)

# HTTP Status Codes: A Developer's Cheat Sheet (With Fixes for the Common Errors)

## Introduction

An **HTTP status code** is a three-digit number a server returns with every response to tell the client what happened to the request — whether it succeeded, was redirected, failed because of something the client did, or failed on the server. The first digit sets the class: `1xx` informational, `2xx` success, `3xx` redirection, `4xx` client error, `5xx` server error.

That first digit is the fastest triage tool you have. A `4xx` means *fix your request*; a `5xx` means *the problem is on the server side*. You can read any response's status directly:

```bash
curl -i https://api.example.com/users
```

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

The status line (`HTTP/1.1 200 OK`) is the code and its reason phrase. Everything below is about knowing what each one means and, for the ones that break things, what to do about it.

## The five classes at a glance

| Class   | Range   | Meaning                                                      | Whose move is it?                         |
| ------- | ------- | ------------------------------------------------------------ | ----------------------------------------- |
| **1xx** | 100–103 | Informational — request received, still processing.          | Neither; interim.                         |
| **2xx** | 200–206 | Success — the request worked.                                | Done.                                     |
| **3xx** | 300–308 | Redirection — more steps needed to complete.                 | Usually the client (follow the redirect). |
| **4xx** | 400–429 | Client error — the request was wrong.                        | You. Fix the request.                     |
| **5xx** | 500–504 | Server error — the server failed to fulfill a valid request. | The server. Retry may help for some.      |

## The cheat sheet: the codes you'll actually meet

These are the \~25 codes that come up in day-to-day API and web work. The rarer ones exist, but if you know these you can read almost any response.

| Code    | Name                   | What it means                                    | Typical cause / fix                                                           |
| ------- | ---------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------- |
| **200** | OK                     | Standard success, response has a body.           | Nothing to do.                                                                |
| **201** | Created                | A resource was created.                          | Read the `Location` header for the new resource URL.                          |
| **202** | Accepted               | Request accepted, processing async.              | Poll a status endpoint or wait for a webhook.                                 |
| **204** | No Content             | Success, no body returned.                       | Common for `DELETE`/`PUT`. Don't parse a body.                                |
| **206** | Partial Content        | A range of the resource was returned.            | Response to a `Range` request (resumable downloads).                          |
| **301** | Moved Permanently      | Resource has a new permanent URL.                | Update your stored URL to the `Location` value.                               |
| **302** | Found                  | Temporary redirect.                              | Follow `Location`; don't cache the redirect.                                  |
| **304** | Not Modified           | Cached copy is still valid.                      | Response to a conditional request (`ETag`/`If-None-Match`); serve from cache. |
| **307** | Temporary Redirect     | Temporary, method preserved.                     | Re-send the same method to `Location`.                                        |
| **308** | Permanent Redirect     | Permanent, method preserved.                     | Update the URL; keep the original method.                                     |
| **400** | Bad Request            | Request is malformed.                            | Fix syntax, JSON, or missing required fields.                                 |
| **401** | Unauthorized           | Not authenticated.                               | Send valid credentials; check the `WWW-Authenticate` header.                  |
| **403** | Forbidden              | Authenticated but not permitted.                 | Check roles/permissions on the resource.                                      |
| **404** | Not Found              | Resource or route doesn't exist.                 | Verify the path and the resource ID.                                          |
| **405** | Method Not Allowed     | Wrong HTTP method for the resource.              | Switch to a method in the `Allow` header.                                     |
| **406** | Not Acceptable         | Server can't match your `Accept` header.         | Relax or correct the requested content type.                                  |
| **408** | Request Timeout        | Client took too long to send the request.        | Retry; check network and payload size.                                        |
| **409** | Conflict               | Request conflicts with current state.            | Re-fetch, resolve the conflict, retry (e.g. version mismatch).                |
| **410** | Gone                   | Resource permanently removed.                    | Stop calling it; there's no new location.                                     |
| **415** | Unsupported Media Type | Server rejects the payload's format.             | Set the correct `Content-Type` (e.g. `application/json`).                     |
| **422** | Unprocessable Content  | Syntax is fine but validation failed.            | Fix the field values, not the request format.                                 |
| **429** | Too Many Requests      | You've hit a rate limit.                         | Back off; honor the `Retry-After` header.                                     |
| **500** | Internal Server Error  | Generic server-side failure.                     | Server-side bug; retry, then report if it persists.                           |
| **501** | Not Implemented        | Server doesn't support the method at all.        | Different from 405 — the server never handles this method.                    |
| **502** | Bad Gateway            | An upstream server returned an invalid response. | Usually transient; retry with backoff.                                        |
| **503** | Service Unavailable    | Server overloaded or down for maintenance.       | Honor `Retry-After`; retry with backoff.                                      |
| **504** | Gateway Timeout        | An upstream server didn't respond in time.       | Retry; check upstream health and timeouts.                                    |

## 2xx: success isn't just 200

Most developers reach for `200` for everything, but the distinctions carry information. `201 Created` tells a client a new resource exists and where to find it (in the `Location` header). `202 Accepted` signals asynchronous work — the request was taken but isn't finished, so the client should poll or wait for a callback rather than treat the response as final. `204 No Content` is a success with deliberately no body, standard for a `DELETE` or an update that returns nothing — parsing it as JSON will throw.

## 3xx: redirects and the method trap

Redirects look simple until a `POST` turns into a `GET`. Historically, clients following a `301` or `302` often switched the method to `GET`, which silently breaks a form submission or an API write. If you need the method preserved across a redirect, the correct codes are `307` (temporary) and `308` (permanent), which mandate keeping the original method. `304 Not Modified` is a different animal — it's the payoff of conditional requests: send an `If-None-Match` with a stored `ETag`, and a `304` means your cache is still good and no body is sent.

## 4xx: the client errors you'll debug most

This is where most day-to-day debugging lives. The trap is that several of these codes look interchangeable and aren't:

* **400 vs 422** — `400 Bad Request` means the request is malformed (broken JSON, wrong syntax). `422 Unprocessable Content` means the request parsed fine but failed validation (an email field with no `@`, a negative quantity). If you're getting a 422, stop checking your syntax and start checking your values.
* **401 vs 403** — `401 Unauthorized` is about authentication: you're not logged in, or your token is missing or expired. `403 Forbidden` is about authorization: you're authenticated, but you're not allowed to do this. A 401 says *who are you*; a 403 says *you can't*.
* **404 vs 405** — `404 Not Found` means the route doesn't exist. `405 Method Not Allowed` means the route exists but rejects your verb. These get confused constantly, and the fix is entirely different — see the dedicated guide on [405 Method Not Allowed](https://apyhub.com/blog/405-method-not-allowed) for how to read the `Allow` header and resolve it.
* **409 Conflict** — the request clashes with the resource's current state, most often a version mismatch on a concurrent update. Re-fetch the latest state, reapply your change, and retry.
* **429 Too Many Requests** — you've exceeded a rate limit. The response frequently includes a `Retry-After` header telling you how long to wait:

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

{"error": "rate limit exceeded", "retry_after": 30}
```

Honoring `Retry-After` (here, 30 seconds) with exponential backoff is the correct handling — hammering a 429 just extends the block.

## 5xx: when it's the server, not you

A `5xx` means the server received a valid request and failed to handle it. The practical difference between them tells you whether retrying is worth it:

* **500 Internal Server Error** — a generic, unhandled server-side failure. There's nothing to fix in your request; retry once, and if it persists it's a bug to report.
* **502 Bad Gateway** — a gateway or proxy got an invalid response from an upstream server. Usually transient — retry with backoff.
* **503 Service Unavailable** — the server is overloaded or in maintenance. Like a 429, it may include a `Retry-After` header; honor it.
* **504 Gateway Timeout** — a gateway didn't get a response from an upstream server in time. Retry, and if you control the upstream, check its health and timeout settings.

The rule of thumb: `500` and `501` are rarely worth an immediate retry; `502`, `503`, and `504` are transient often enough that a retry with backoff is the right default.

## How to inspect a status code

You don't need to guess. Fire the request and read the status line and headers directly.

With **curl**, the `-i` flag includes the response headers so you see the status and the diagnostic headers (`Allow`, `Retry-After`, `Location`, `WWW-Authenticate`) that tell you what to do next:

```bash
curl -i -X POST https://api.example.com/orders \
  -H "Content-Type: application/json" \
  -d '{"item": "sku_123"}'
```

For interactive work, an API client makes this repeatable. [Voiden](https://voiden.md) is an offline-first, Git-native client that stores each request as a Markdown-based `.void` file committed alongside your code — so the exact call that produced a `409` or a `422` is versioned and reproducible by anyone on the team, not stuck in one person's local tool. It's open source (Apache 2.0) and you can [download it here](https://voiden.md/download).

## Where ApyHub Fits

Reading status codes is one problem; getting consistent, well-documented ones across every API you call is another. When you're wiring up ten different services, each with its own conventions for what a `422` body looks like or whether it returns `200` or `201` on create, the inconsistency is its own source of bugs.

[ApyHub](https://apyhub.com/) is a certified API marketplace — a catalog of 200+ APIs and 1,000+ endpoints for common utility work (file conversion, validation, PDF processing, data enrichment, and more). Every endpoint in the [catalog](https://apyhub.com/catalog) ships with a documented, machine-readable specification, including its methods and response shapes, so status-code behavior is predictable before you write the integration — and readable by AI agents consuming the catalog over MCP, not just by humans. It won't stop upstream services from returning a `503`, but it removes the guesswork about what a given endpoint returns and when.

[**Explore the catalog →**](https://apyhub.com/catalog)

## Conclusion

HTTP status codes are a diagnostic language, not noise. The class digit tells you whose problem it is, the specific code tells you what went wrong, and the response headers — `Allow`, `Retry-After`, `Location`, `WWW-Authenticate` — usually tell you how to fix it. Bookmark the table above, learn the pairs that get confused (401/403, 404/405, 400/422, 502/503/504), and most "mystery" errors stop being mysteries.

[**Try ApyHub →**](https://apyhub.com/)

## FAQ

### What are the five classes of HTTP status codes?

`1xx` is informational, `2xx` is success, `3xx` is redirection, `4xx` is a client error (the request was wrong), and `5xx` is a server error (the server failed on a valid request). The first digit tells you which class a code belongs to.

### How do I know if an error is my fault or the server's?

Read the first digit. A `4xx` code means the problem is in your request — wrong URL, method, credentials, or data — so fixing the request resolves it. A `5xx` means the server failed, and the fix is server-side, though retrying with backoff helps for `502`, `503`, and `504`.

### What's the difference between 401 and 403?

`401 Unauthorized` means you're not authenticated — your credentials are missing, invalid, or expired. `403 Forbidden` means you're authenticated but not permitted to perform the action. In short: 401 is about identity, 403 is about permission.

### What's the difference between 400 and 422?

`400 Bad Request` means the request is malformed — broken JSON or invalid syntax the server can't parse. `422 Unprocessable Content` means the request was parsed successfully but failed validation, such as a field with an invalid value. A 422 points you at your data, not your formatting.

### What's the difference between 404 and 405?

`404 Not Found` means the route or resource doesn't exist. `405 Method Not Allowed` means the route exists but doesn't accept the HTTP method you used, and the response includes an `Allow` header listing the methods it does accept. See the [405 Method Not Allowed guide](https://apyhub.com/blog/405-method-not-allowed) for the full fix.

### How should I handle a 429 Too Many Requests error?

Stop sending requests and wait. If the response includes a `Retry-After` header, wait that many seconds before retrying; otherwise use exponential backoff. Repeatedly retrying immediately typically extends the rate-limit block rather than resolving it.

### Which HTTP errors are safe to retry?

Transient server errors — `502 Bad Gateway`, `503 Service Unavailable`, and `504 Gateway Timeout` — are worth retrying with backoff, and `429` after honoring any `Retry-After`. A `500` is worth one retry at most, and `4xx` errors like `400`, `401`, and `404` won't fix themselves on retry — you have to change the request.

### What does a 304 Not Modified mean?

It's the successful result of a conditional request: you sent a stored validator (an `ETag` via `If-None-Match`, or a date via `If-Modified-Since`), and the server confirmed your cached copy is still current, so it returned no body. Serve the resource from your cache rather than re-downloading it.
