Quick test: what's the difference between 401 and 403? What about 502 vs 503? If you had to pause even for a second, you're not alone — and you're also not off the hook, because the client code (or the poor human) on the other end of your API is making decisions based on that number.
Status codes and headers are one of those "I'll just wing it" corners of web development. Everyone's shipped a 500 for a validation error at least once. This post is a quick, practical pass through the ones that actually matter, the ones people mix up, and the headers most APIs quietly get wrong.
Why this is worth five minutes
A status code is a contract. It tells the caller what kind of thing just happened without them reading your response body:
- Should I retry this request?
- Is this my fault or the server's?
- Should I refresh my auth token?
- Is this safe to cache?
Get the code wrong, and you break that contract. Clients retry things they shouldn't, cache things they can't, or silently swallow errors because your 200 said everything was fine.
The mix-ups that happen constantly
401 vs 403 — 401 Unauthorized actually means "I don't know who you are" (no or invalid credentials). 403 Forbidden means "I know exactly who you are, and the answer is no." Mixing these up gives users the wrong recovery path — one says "log in again," the other says "stop asking."
404 vs 410 — 404 says "not found," which invites retries and crawlers to keep checking. 410 Gone says "this existed and is never coming back." If you deleted a resource on purpose, 410 is the more honest answer and it tells search engines to stop indexing it.
500 vs 502 vs 503 — 500 is your app's own bug. 502 Bad Gateway means you're a proxy/gateway and the upstream service sent garbage. 503 Service Unavailable means you're intentionally down (maintenance, overload) and should usually come with a Retry-After header. These three get used interchangeably in a lot of codebases, and it makes on-call debugging harder than it needs to be.
PUT vs PATCH response codes — a successful PUT that creates a new resource should return 201 Created; one that updates an existing one should return 200 OK (or 204 No Content if you're not returning a body). A lot of APIs just return 200 for everything, which quietly discards useful information.
The headers people forget exist
-
Cache-Control— the single header most responsible for either a snappy site or a stale one.no-store,no-cache, andmax-age=0all mean subtly different things. -
ETagcombined withIf-None-Match— lets clients skip re-downloading unchanged resources. Massively underused outside of CDNs. -
Retry-After— pairs with429and503to tell clients exactly when to come back, instead of making them guess with exponential backoff. -
Content-Security-Policy— not just a security checkbox; misconfiguring it is one of the more common reasons a perfectly good frontend "randomly" stops loading fonts or scripts. -
Vary— if your response changes based on a header likeAccept-EncodingorAuthorization, and you're not settingVary, your CDN or browser cache can serve the wrong version to the wrong user.
Where this actually bites you
- A retry storm because your API returned
500instead of429for rate limiting, so clients treated a "slow down" as a random failure and hammered you harder. - A broken cache because a response with private data went out with
Cache-Control: public. - A frontend team that built retry logic around
503because your team returned it for something that was actually a permanent410.
None of these are exotic edge cases — they're the ordinary Tuesday bugs that come from treating status codes and headers as decoration instead of protocol.
A reference beats a memory test
Honestly, memorizing all ~60 status codes and every header's exact semantics isn't a good use of anyone's time. I put together a quick lookup tool that covers status codes (grouped by category, with the "what it actually means" and "when to use it" spelled out) and common request/response headers, so you can check instead of guess mid-code-review:
👉 HTTP Status Code & Header Reference
It's part of samtoolkit.com — a set of small, client-side developer tools (JWT decoder, .gitignore generator, env validator, webhook bin, and a few others). Nothing gets uploaded anywhere; everything runs in your browser.
If you've got a favorite status-code horror story — a 200 that lied to you, a 418 used unironically in production, whatever — drop it in the comments. I collect these.
Top comments (0)