Dev.to WebDev 🛠 Dev 👁 0 📖 5 min read

401 vs 403 vs 404: The Status Code Mistakes That Break APIs

Originally published at ipcalcplus.com. Every API review I've ever done contains at least one of these bugs: a login failure that returns 500, a rate limit that returns 400, a "resource not found" that returns 403 for s

Originally published at ipcalcplus.com.

Every API review I've ever done contains at least one of these bugs: a login failure that returns 500, a rate limit that returns 400, a "resource not found" that returns 403 for security reasons and confuses every client developer for the next two years.

Status codes are the API's contract with its clients. Break the contract and your consumers write defensive code around your bugs — code that then breaks when you fix the bug. This is a guide to the codes that actually matter, the distinctions people get wrong, and the two codes almost nobody uses correctly.

The two-second mental model

First digit = who broke it:

  • 2xx — it worked
  • 3xx — go somewhere else
  • 4xx — the client broke it
  • 5xx — the server broke it
  • 1xx — hold on, I'm not done (mostly invisible; the one you'll see is 101 during a WebSocket upgrade)

That's the whole taxonomy. Everything interesting is in the distinctions within 4xx and 3xx.

401 vs 403: the distinction clients depend on

The most misused pair in REST.

401 Unauthorized actually means unauthenticated. The server does not know who you are. The client's correct response: acquire credentials (log in, refresh the token, add the API key) and retry.

403 Forbidden means authenticated but not allowed. The server knows exactly who you are and is declining to serve you. The client's correct response: give up. Retrying with the same credentials will produce the same answer.

When you return 403 for missing credentials, clients can't tell "I need to log in" from "I'm not permitted," and their retry logic breaks. The memorable version:

  • 401 = "who are you?"
  • 403 = "I know who you are, and no."

One deliberate exception: many APIs return 404 for resources the caller isn't allowed to know exist — a private repo is "not found" rather than "forbidden," so the API doesn't leak which IDs are valid. That's a legitimate design choice; document it, because it's the opposite of what clients expect.

404 vs 410, and the SEO angle nobody mentions

404 Not Found is the most famous code and the most expensive one to misuse on the web. The nuance: 410 Gone means "this used to exist and was deliberately removed." Search engines treat 410 as a stronger signal than 404 — a 404 page may come back, a 410 page won't, so it gets deindexed faster.

The related rule that preserves years of SEO work: when a URL moves permanently, 301 (or 308 for endpoints where the method must not change, like POST endpoints). A redesign that maps old URLs to 404 instead of 301 drops your search traffic within weeks — I've watched it happen, and the fix is always the same retrospective: export the old URL list, redirect everything, verify each target returns 200.

Within redirects, the pair that matters:

  • 301 vs 302: permanent vs temporary. 301 transfers ranking to the new URL; 302 does not. Browsers convert POST to GET after a 301.
  • 307 vs 308: same meanings, but method and body are preserved. For APIs, 307/308 are usually the correct choice; for public web pages, 301 remains the standard.

The pair everyone underuses: 202 and 204

Two codes that make APIs honest:

202 Accepted — "I received your request and will process it, but it isn't done." If your POST enqueues a job for async processing, 202 is the truthful answer. Returning 200 when the work hasn't happened yet means your client thinks the email was sent when it's actually still in a queue that will retry at 3 AM.

204 No Content — "succeeded, and there's nothing to return." The clean response for DELETE, or any operation with no meaningful body. 204 also carries a subtle contract: the response must have no body. Returning 200 with an empty string is what most APIs do, and it forces clients to handle two shapes for the same outcome.

The general principle behind both: return the code that describes what actually happened, not the code that makes the client happy. Fake-200 APIs push complexity downstream into consumer code, where it multiplies.

4xx codes that are worth their tokens

  • 429 Too Many Requests — you're rate limiting. Include a Retry-After header; well-behaved clients honor it, and your effective load drops immediately.
  • 422 Unprocessable Entity — syntactically valid JSON, semantically wrong data (email field contains a phone number). Many APIs collapse this into 400, which forces clients to parse the error body to learn what kind of failure it was.
  • 415 Unsupported Media Type — you sent XML to a JSON endpoint. Distinct from 400, and trivially cheap to check.
  • 451 Unavailable For Legal Reasons — the takedown code, named after Fahrenheit 451. You'll rarely return it, but you should know why your favorite site vanished in some region.

When the server is lying: 502, 503, 504

The 5xx trio behind every "is it just me?" incident:

  • 502 Bad Gateway — your proxy (nginx, the load balancer) reached the backend and got garbage back, or nothing. Usually means the backend crashed or is misconfigured. Read the proxy's error log, not just the app log.
  • 503 Service Unavailable — deliberately refusing: overloaded, or in maintenance. This is the "back off" signal, and the right response from a circuit breaker.
  • 504 Gateway Timeout — the backend is up but too slow. Different fix from 502: this one is about timeouts and slow queries, not crashes.

The debugging shortcut: 502/504 mean "the proxy couldn't talk to the app" and 503 means "the app or the operator chose to say no." Three different log files, three different fixes — which is why "the site is down" tickets get resolved so much faster when the first question asked is which exact 5xx.

A debugging workflow that works

  1. Reproduce with curl, capture headers, not just the body
  2. 4xx? It's the request — validate payload, URL, auth header, Content-Type
  3. 5xx? It's the server — application log first, proxy log second
  4. Read the response headers: Retry-After, Location, WWW-Authenticate, Allow usually contain the actual fix
  5. Fix minimally, redeploy, retest with the exact same request

The habit that pays compound interest: save the full headers of every interesting failure. The status code names the problem class; the headers usually name the problem.

If you want a searchable list of all this while you work — including the lesser-known codes and which fix goes with which — I keep a free HTTP status code reference and a longer 1xx-to-5xx guide at IPCalcPlus. Both run entirely in the browser, no signup. The Port Checker is next to it for the other half of "is it the network or the app" debugging.

Which wrong status code do you see most often in the wild? My vote: 500 for expired tokens.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.