HTTP Status Codes Explained: 200, 301, 404, 500 and the Rest
Every response from a server starts with a three-digit number that says how it went. What the 2xx, 3xx, 4xx, and 5xx families mean, the dozen codes you'll actually meet, what each one tells you about where a bug lives, and which ones your own API should return.
When your browser asks a server for something, the server's reply starts with a three-digit status code. Most of the time you never see it. When something breaks, it's often the single most useful clue you have — because the first digit tells you whose fault it is.
The five families
| First digit | Meaning | In plain English |
|---|---|---|
| 1xx | Informational | "Hang on…" (rare; you can ignore these) |
| 2xx | Success | "Here you go." |
| 3xx | Redirection | "It's over there." |
| 4xx | Client error | "You asked for something wrong." |
| 5xx | Server error | "I broke trying to answer." |
That 4xx/5xx split is the big one. A 4xx means the request was the problem: wrong address, not logged in, bad data. A 5xx means the server's code or infrastructure failed. When debugging, it tells you whether to look at what was sent or at what happened on the server.
The codes you'll actually meet
Success
- 200 OK — worked, here's the result.
- 201 Created — worked, and a new thing was created (a new user, a new order).
- 204 No Content — worked, nothing to send back (common after a delete).
Redirects
- 301 Moved Permanently — this address has moved for good; update your bookmarks. Search engines transfer ranking to the new address.
- 302 Found / 307 Temporary Redirect — go there for now. Used after logging in, for example.
- 304 Not Modified — "you already have the latest version cached." A good thing; it saves bandwidth. (What Is Caching?.)
Use 301 when you permanently change a URL (like moving www.yourapp.com to yourapp.com). Use 302/307 for temporary hops.
Client errors (look at the request)
- 400 Bad Request — the request was malformed or the data was invalid. Missing a required field, badly formatted JSON.
- 401 Unauthorized — you're not logged in (or your login token expired). Despite the name, it means "not authenticated."
- 403 Forbidden — you're logged in, but not allowed to do this. (Authentication vs Authorization.)
- 404 Not Found — nothing at that address. A typo, a deleted item, or a route that doesn't exist.
- 405 Method Not Allowed — the address exists, but not for that kind of request (a
POSTto something that only acceptsGET). - 409 Conflict — clashes with the current state, like signing up with an email that's already taken.
- 422 Unprocessable Content — the data was well-formed but failed validation. Some APIs use this instead of 400.
- 429 Too Many Requests — you've hit a rate limit. Slow down. (What Is Rate Limiting?.)
Server errors (look at the server)
- 500 Internal Server Error — the server's code crashed. The real error is in the server logs, not the browser.
- 502 Bad Gateway — a server in front of your app (a proxy or load balancer) couldn't get a sensible answer from your app. Often: your app crashed, didn't start, or is listening on the wrong port.
- 503 Service Unavailable — overloaded or down for maintenance.
- 504 Gateway Timeout — the server in front waited too long for your app. Something is slow: a heavy database query, a call to an outside API that hangs.
How to see the status code
Open your browser's developer tools, go to the Network tab, and reload. Each request shows its status. Red ones are 4xx and 5xx. Click one to see the full request and response. (Browser Developer Tools for Beginners.)
From the terminal:
curl -I https://yourapp.com/some-page
# HTTP/2 404
What to do with each
| You see | Look at |
|---|---|
| 400 / 422 | The data you're sending. Compare it to what the API expects. |
| 401 | Are you logged in? Is the token or cookie being sent? Expired? |
| 403 | Does this user have permission? Is a security rule blocking it? |
| 404 | The URL: typos, missing IDs, a route that was never deployed. |
| 429 | How often you're calling. Add waiting and retries. |
| 500 | Server logs. The browser won't tell you more. |
| 502 / 504 | Is the app running at all? Did it crash on startup? Is something slow? |
A tip for AI-assisted debugging: when you paste an error into Claude Code or another tool, include the status code and the server log line, not just "it doesn't work." "The form returns a 500 and the log says column "email" does not exist" gets fixed in one step. (How to Read an Error Message.)
Returning the right codes from your own API
If you're building an API, return honest codes:
- Don't return 200 with an error message inside. Clients, monitoring, and retries all rely on the status code.
- Use 404 for things that don't exist or that the user may not see — returning 403 can confirm to an attacker that the thing exists.
- Use 401 vs 403 correctly: not logged in versus not allowed.
- Reserve 5xx for genuine server failures, and make sure each one is logged.
Designing a REST API That Won't Embarrass You Later goes further.
EasySpawn gives Claude Code access to your running app and its server logs in the same workspace, so a 500 in the browser can be traced to the actual error line instead of guessed at. See how it works or join the waitlist.
Related: Anatomy of a URL · What Is an API? · Debugging for Beginners
Keep reading
How to Test Your App Before Launch (Without Writing Tests)
You don't need to be a programmer to find most bugs before your users do. A practical, one-afternoon testing plan for AI-built apps: the journeys to walk through, the 'try to break it' checks, the security tests, and how to keep track of what you find.
How to Read an Error Message (and Fix Things Faster)
Error messages look like walls of gibberish, but they usually tell you exactly what went wrong and where. How to find the one line that matters, what common errors actually mean, where to look when there's no error at all, and what to give an AI so it can fix it first time.