HTTP Status Code for Unauthorized Request: 401 vs 403
An unauthorized request returns HTTP 401 when login is missing or invalid, and 403 when the user is known but not allowed. Rules, real responses and test cases.

Short answer
The HTTP status code for an unauthorized request is 401 Unauthorized. The server returns it when the request has no valid credentials: no token, an expired token, or a token it does not recognise. RFC 9110 also says a 401 response must include a WWW-Authenticate header telling the client how to authenticate.
If the user is logged in but is not allowed to do the action, the correct code is 403 Forbidden. The name "Unauthorized" is misleading: in practice 401 is about authentication (who you are), and 403 is about authorization (what you may do).
401 vs 403: the decision table
Use this table when you need to decide which code a request should get.
| Situation | Expected code | Why |
|---|---|---|
| No Authorization header at all | 401 | No credentials were sent. |
| Token expired | 401 | Credentials are no longer valid; logging in again fixes it. |
| Token malformed or signed by someone else | 401 | The server cannot identify the user. |
| Wrong username or password on a login endpoint | Usually 401 (some APIs use 400) | Follow the API's documented contract. |
| Valid token, but a read-only role tries to write | 403 | The user is known but lacks permission. |
| Valid token, but user A opens user B's private order | 403 or 404 | 404 is a common choice to avoid revealing the order exists. |
| Account is blocked or IP is not allowed | 403 | Re-authenticating will not help. |
A quick memory rule: if logging in again could fix it, it is 401. If it could not, it is 403.
Real 401 and 403 responses from a practice API
These responses come from the free practice API behind the SoftwareTestPilot API Playground. We sent them on 5 November 2026 and copied the output (headers trimmed to the ones that matter).
1. No token: 401
POST /products
Content-Type: application/json
{"name":"x"}
HTTP/1.1 401
WWW-Authenticate: Bearer error="invalid_request"
{"error":{"status":401,"code":"token_missing","message":"Missing Bearer token. POST /auth/login first, then send Authorization: Bearer <token>."}}
2. Wrong token: 401
POST /products
Authorization: Bearer wrong
HTTP/1.1 401
WWW-Authenticate: Bearer error="invalid_token"
{"error":{"status":401,"code":"token_invalid","message":"Token not recognised for this workspace. Use the token from your latest login."}}
Notice the two different error values in the header. RFC 6750 defines them: invalid_request when the request is missing something, and invalid_token when the token is expired, revoked or wrong.
3. Logged in as a read-only user: 403
The practice API has a read-only login (qa.viewer). After logging in with it and sending a valid token, a write is refused:
HTTP/1.1 403
{"error":{"status":403,"message":"Your role (viewer) can read but not change data. Log in as qa.tester to write.","details":{"role":"viewer","required":"editor"}}}
Same endpoint, same body, three results. That is exactly what an auth test suite should prove.
Test cases to cover for unauthorized requests
For every protected endpoint, run these checks. Each line is one test case.
- No Authorization header → 401,
WWW-Authenticatepresent, no data in the body. - Empty token (
Authorization: Bearer) → 401, not 500. - Malformed header (
Authorization: Token abcor no "Bearer" word) → 401. - Random or tampered token → 401.
- Expired token → 401. If you cannot wait for expiry, ask developers for a short-lived test token.
- Token after logout → 401 if the API revokes tokens on logout.
- Valid token, lower role → 403, and the data must not change (read it back to confirm).
- Valid token, another user's resource → 403 or 404, as documented. Never 200.
- Error body does not leak stack traces, SQL, or whether a username exists.
Case 8 is the one that finds real security bugs. It is called broken object level authorization, and it tops the OWASP API Security Top 10.
Automating the check
Here is the same idea as a Playwright API test in TypeScript. BASE is a placeholder for your API's base URL. We have not run this exact file; adapt the endpoint and body to your API.
import { test, expect } from "@playwright/test";
const BASE = process.env.API_BASE_URL!; // e.g. your practice API base URL
test("write without token returns 401 with WWW-Authenticate", async ({ request }) => {
const res = await request.post(`${BASE}/products`, { data: { name: "x" } });
expect(res.status()).toBe(401);
expect(res.headers()["www-authenticate"]).toContain("Bearer");
});
test("write with a bad token returns 401", async ({ request }) => {
const res = await request.post(`${BASE}/products`, {
headers: { Authorization: "Bearer wrong" },
data: { name: "x" },
});
expect(res.status()).toBe(401);
});
For the 403 case, log in with the lower-role user first, keep its token, then repeat the write and assert 403. In Java, the same checks fit naturally into REST Assured.
Getting 401 when you expected success? Check these
- Header name or format. It must be
Authorization: Bearer <token>with one space, and no quotes around the token. - Token expired. Many tokens last minutes, not hours. The practice API's tokens last 900 seconds.
- Wrong environment. A staging token will not work on production.
- Session or workspace mismatch. On the practice API, send back the
X-Sandbox-Keyheader from the login response, or the token will be rejected astoken_invalid. - A proxy stripped the header. Some gateways drop
Authorizationon redirects. Check whether the URL redirected (a 301 or 302) before the 401.
Interview answer: what is the difference between 401 and 403?
Say this: "401 Unauthorized means the request was not authenticated: no credentials, or invalid or expired ones. The response should include a WWW-Authenticate header, and logging in again can fix it. 403 Forbidden means the server knows who I am but my role is not allowed to do the action, so logging in again does not help. For example, a viewer account trying to delete a product should get 403, while the same request with no token should get 401."
Likely follow-ups: "When would you return 404 instead of 403?" (to hide that a private resource exists). "What would you check besides the status code?" (that no data changed, that the body leaks nothing, and the WWW-Authenticate header). More questions like these are in the API testing interview questions article.
Practice exercise
Open the API Playground and try to get all three results from one endpoint: a 401 with no token, a 401 with a made-up token, and a 403 after logging in as qa.viewer (password Practice@123, public practice credentials). Then log in as qa.tester and confirm the same write now succeeds.
Answer check: the two 401s should show different WWW-Authenticate error values (invalid_request and invalid_token), and the 403 body should name the required role.
Next step
Status codes are one part of a wider plan. See the types of API testing for where security and negative tests fit, or work through the eight hands-on checks in how to test a REST API online. Stuck on a confusing auth bug? Ask in the QA Community.
Key takeaways
- 1401 means 'I don't know who you are': credentials are missing, expired or invalid.
- 2403 means 'I know who you are, and you can't do this': the user is authenticated but lacks permission.
- 3A correct 401 includes a WWW-Authenticate header that names the auth scheme, such as Bearer.
- 4Logging in again can fix a 401; logging in again will not fix a 403.
- 5Some APIs return 404 instead of 403 to hide that a resource exists. Check the API's documented rule before logging a bug.
Common mistakes to avoid
- Logging a bug for 401 versus 403 without checking whether the user was actually logged in.
- Accepting 200 with an error message in the body for a request that should have been rejected.
- Testing only a missing token and never an expired, malformed or other-workspace token.
- Ignoring the WWW-Authenticate header on 401 responses.
- Returning 500 when the Authorization header is malformed instead of 401.
References
- 1RFC 9110 section 15.5.2: 401 Unauthorized— rfc-editor.org
- 2RFC 9110 section 15.5.4: 403 Forbidden— rfc-editor.org
- 3RFC 6750: Bearer token usage (error codes)— rfc-editor.org
- 4MDN: 401 Unauthorized— developer.mozilla.org
