Two HTTP codes mean "access denied," and developers mix them up daily: 401 Unauthorized and 403 Forbidden. Using the wrong one confuses clients and makes debugging harder. The good news: one short rule sorts them out forever.
The one-rule difference
It comes down to identity:
401 = "I don't know who you are." (Authenticate first.) 403 = "I know who you are — and you still can't." (Permission denied.)
A 401 is about authentication (proving who you are). A 403 is about authorization (what you're allowed to do once you're known). And here's a naming irony worth noting: the code literally named "Unauthorized" (401) is actually about authentication, not authorization.
Side by side
| 401 Unauthorized | 403 Forbidden | |
|---|---|---|
| Means | Not authenticated | Authenticated, but not permitted |
| The question | "Who are you?" | "Are you allowed?" |
| Fix | Log in / send valid credentials | Get the right permissions |
| Will retrying with login help? | ✅ Yes | ❌ No |
| Typical trigger | Missing/expired token | Valid token, insufficient role |
A quick analogy
Think of a members-only club:
- 401 — you're at the door with no membership card. "Show your card." Once you do, you're in.
- 403 — you're a member, card and all, but you're trying to enter the staff-only room. Your membership is valid; you're just not allowed there. Showing your card again changes nothing.
Examples
| Scenario | Code |
|---|---|
| Calling an API with no auth token | 401 |
| Token expired | 401 |
| Logged-in user opening another user's data | 403 |
| A "member" hitting an admin-only endpoint | 403 |
| Valid login, wrong account/tenant | 403 |
Why getting it right matters
For APIs, the distinction is a contract. A client that gets a 401 should try to refresh its token and retry; a client that gets a 403 should not retry — it'll just fail again. Returning the wrong code sends clients into pointless retry loops or makes them give up when a re-auth would've worked.
It also matters for monitoring: on a protected endpoint, a 401 or 403 might be the expected, healthy response — so configure checks to treat it as such, and only alert when the code is wrong for that endpoint.
The bottom line
| In one line | |
|---|---|
| 401 | Not authenticated — "log in." |
| 403 | Authenticated but not permitted — "you can't, even logged in." |
| Rule | 401 = who are you; 403 = are you allowed. |
| APIs | Retry after re-auth on 401; don't retry on 403. |
Remember the club: 401 is the front door, 403 is the staff room. Get the code right and your APIs (and your debugging) get a lot clearer.
Deep dives: 403 Forbidden and HTTP status codes explained.