401 Unauthorized vs 403 Forbidden
401 means the request lacks valid authentication; 403 means the caller is authenticated but not permitted. RFC 9110 makes the distinction explicit and requires a WWW-Authenticate header on 401.
401 Unauthorized and 403 Forbidden are the two most commonly confused HTTP status codes, and getting them wrong leaks information or breaks client retry logic. RFC 9110 defines them by the question they answer. 401 answers "who are you?" The server received the request but the authentication credentials were absent, expired, or invalid. RFC 9110 requires the response to include a WWW-Authenticate header naming the authentication scheme the client should use (Basic, Bearer, Digest, etc.). The de facto rule: if logging in or refreshing a token would fix the problem, 401 is correct. 403 answers "are you allowed?" The server identified the caller — the session cookie is valid, the API key parsed — but authorization policy denies the operation. A regular user hitting an admin endpoint, an API key without the required scope, or an IP outside an allowlist all warrant 403. No re-authentication will help. Two practical wrinkles. First, many systems deliberately return 404 Not Found instead of 403 to avoid disclosing the existence of protected resources — a privacy-over-correctness trade-off used by GitHub for private repositories and most secret-URL schemes. Second, some APIs (notably older AWS services) use 403 for missing credentials as well as forbidden access; this is non-conforming but widespread, so clients should not rely on 401-versus-403 to detect missing tokens. The practical rule for new APIs: emit 401 with WWW-Authenticate when credentials are absent, expired, or malformed; emit 403 when credentials are valid but access control denies the action; emit 404 only if you have a deliberate reason to hide the resource's existence.