Addaly is in open beta. Things will change, and AI answers can be wrong — check anything that matters.

What a status code is telling you

Shipping It · lesson 3 of 8 · 7 min

A request is just text

Strip away the libraries and an HTTP request is a few lines sent over a socket:

GET /rooms/chai HTTP/1.1
Host: addaly.com
Accept: text/html
Cookie: session=abc123

And the response:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: public, max-age=60

<!doctype html>...

A method, a path, headers, an optional body. A status line, headers, an optional body. Everything else — REST, GraphQL, your framework — is a convention layered on top of that.

The first digit answers "whose problem is this"

This is the part worth internalising.

  • 2xx — it worked.
  • 3xx — look somewhere else.
  • 4xx — the request was wrong. The caller has to change something.
  • 5xx — the request was fine and the server failed. You have to change something.

A graph with two lines, 4xx and 5xx, tells you at a glance whether to open your editor or open a conversation with whoever is calling you. Almost no other single signal is that cheap.

The distinctions that bite

301 versus 302. A 301 is permanent, and browsers cache it aggressively — sometimes until the user clears their profile. Ship a wrong 301 and you cannot take it back from the people who received it. While you are experimenting, use 302 or 307. Switch to 301 when you are certain.

401 versus 403. 401 means "I do not know who you are, send credentials". 403 means "I know exactly who you are, and no". Send 401 to a logged-in user who lacks permission and their client will try to re-authenticate forever, in a loop, against a login they already completed.

429. Too many requests. Pair it with a Retry-After: 30 header so the caller knows to wait rather than retry immediately and make things worse.

502 versus 500 versus 504. A 500 is your code throwing. A 502 or 504 comes from a proxy in front of your app: 502 means your app answered with garbage or not at all, 504 means it did not answer in time. If you see 502s, your process probably crashed or failed to start. If you see 504s, something in your handler is slower than the proxy's timeout, often 30 or 60 seconds.

Do not lie in the status line

This is common and expensive:

json
HTTP/1.1 200 OK
{ "ok": false, "error": "expired token" }

Every layer between you and the user reads the status code, not your JSON. Your uptime monitor reads it. Your CDN reads it and may now cache the failure for a minute. A client library's retry logic reads it and does not retry. Your error dashboard reads it and stays green while people are locked out.

The status code is not decoration on top of the real answer. It is the summary of the outcome, written where the rest of the internet can see it. Say 401 when you mean 401.

GET must not change anything

A GET /posts/12/delete link will eventually be followed by a crawler, a link preview bot in a chat app, a browser prefetching what it thinks you will click, or an email scanner opening every URL in a message to check for malware. All of these will delete post 12 with no human involved.

This is not a style preference. GET and HEAD are defined as safe — meaning callers are entitled to assume they cause nothing. Destructive actions go behind POST or DELETE.

Before you move on

An API returns HTTP 200 with `{"ok": false, "error": "expired token"}` when a session expires. During an incident, users were locked out for three hours while the uptime dashboard stayed green. What did that 200 actually cost?

Pick the one you would defend. Nobody sees your answer.

No ads. No data sale. No public scores on people. Ever.

© 2026 Addaly