gg
Home › Blog › 401 and 403 are not the same refusal

401 and 403 are not the same refusal

2026-08-28 · 4 min read
webdatasecurity

Every HTTP response opens with a three-digit number, and the first digit is the only part you need to read to know what to do next. It is a decision tree with four branches, and debugging goes wrong when somebody skips it and starts guessing.

The first digit

That single split — 4xx means fix your request, 5xx means the request never got a fair hearing — saves more time than any other piece of HTTP knowledge. People burn afternoons rewriting a payload that was never the problem.

401 against 403

This is the pair worth learning properly, because the names actively mislead. 401 is labelled “Unauthorized” and means unauthenticated: the server does not know who you are. Either no credentials arrived, or the ones that did are unreadable, expired or wrong. A correct 401 comes with a WWW-Authenticate header telling you how to try again. Retrying with better credentials is exactly the right move.

403 is “Forbidden” and means authenticated but not permitted: the server knows perfectly well who you are and the answer is still no. Sending the same credentials again will never work, and neither will refreshing the token. Something has to change on the other side — a role, a scope, an ownership, a plan.

So the debugging split is clean. On a 401, look at the credential: is the header present, is the token expired, is it being stripped by a proxy, are you pointing at the wrong environment? On a 403, look at the permission: is this token for the right account, does the scope cover this endpoint, is the object owned by somebody else? Chasing the token when you have a 403 is the single most common waste of time in API work.

One wrinkle: some services deliberately return 404 where 403 would be honest, so that an outsider cannot learn which records exist by watching which ones are forbidden. If a resource you are certain about reports 404, consider that you may be looking at a permission problem wearing a disguise.

301 against 302

301 Moved Permanently says this address is finished and the new one is authoritative. Browsers and intermediaries are entitled to remember it, sometimes for a very long time, and a visitor whose browser cached your 301 will not ask your server again to find out you changed your mind. That makes a wrong 301 genuinely expensive to undo.

302 Found is temporary: the original address stays authoritative and the client should keep asking there. If you are moving a URL forever — a domain change, a permanent restructure — use 301. If you are doing anything conditional, seasonal, or that you might reverse, use 302. When in doubt, 302, because the reversible mistake is the cheaper one.

404 against 410

404 Not Found means there is nothing at this address. It makes no claim about the past or the future: the URL might never have existed, might be misspelt, might come back tomorrow. It is the right answer for a typo.

410 Gone is a stronger and more specific statement: this existed, it has been deliberately removed, and it is not coming back. Serving 410 for a retired page tells crawlers and clients that the removal is intentional rather than an outage or a slip of the keyboard, and that they can stop asking. Use it when you have genuinely retired something on purpose; use 404 when you simply do not have it.

The 5xx family, briefly

500 Internal Server Error is the application itself failing — an unhandled exception, usually. 502 Bad Gateway means a proxy reached your application and got a reply it could not use, which often means the application crashed or is not listening. 504 Gateway Timeout means the proxy waited and got nothing at all, which points at something slow rather than something broken. 503 Service Unavailable is a deliberate “not right now”. Those four point at four different places, so read which one you got before opening a log.

And 429 Too Many Requests is 4xx because the fault is yours: too many requests. Look for a Retry-After header before writing your own back-off.

The scope of this lookup

It holds a curated list of nineteen codes — the ones that come up daily, plus 418 for the joke — and returns unknown code for anything outside it. That list does not include 410, or the modern method-preserving redirects 307 and 308, so those three will come back unrecognised even though this piece discusses them. It is a fast reminder while you are debugging, not a complete registry; the full list lives in the RFCs.

HTTP Status Lookup runs in your browser and sends nothing anywhere. The first digit is the part that does the work.

Try it free — with the steps shown

The HTTP Status Lookup runs in your browser and shows exactly how it got the answer, so the method sticks.

Open HTTP Status Lookup

More from the blog