What they're testing
Whether you treat status codes as part of the API contract or as numbers you return to fill a field.
The short answer~30 seconds
401 Unauthorized means unauthenticated or an invalid token — the name is a historical misnomer, it's about authentication. 403 Forbidden means we know who you are and you may not, and retrying with a different token won't help. 404 means the resource doesn't exist — or is used deliberately in place of 403 when admitting the resource exists is itself a leak.
The long answer
The 404-instead-of-403 case deserves detail: if /orders/123 returns 403 to someone who doesn't own it, an attacker scans ids and learns which orders exist — resource enumeration. Returning 404 in both cases makes the two indistinguishable. The trade is that your logs must still distinguish them, or support loses the ability to diagnose.
The 4xx/5xx split assigns responsibility, not severity: 4xx says "this request is wrong and resending it identically will be wrong again", 5xx says "my side failed, retrying may work". That split matters because clients and infrastructure use it to decide whether to retry — returning 500 for a malformed body makes clients retry forever a request that can never succeed.
A few underused codes worth using: 409 Conflict for state conflicts (a failed optimistic lock), 422 when the syntax is fine but the business rule isn't, 429 with Retry-After for rate limits, and 503 with Retry-After when you're overloaded. The last three tell the client WHAT TO DO, not merely that something failed.
What they'll ask next
?What about 200 with {"error": …} in the body?
It breaks every layer beneath: proxies cache the error, dashboards report a 0% error rate, and clients must parse the body to find out. If legacy reasons force it, at least make sure it isn't cacheable.
These lose points
- Returning 200 for everything with the real code in the body. Extremely common, and it blinds your observability.