A 500 response to invalid input is always a bug: the server should return a 4xx, not crash. A missing 403 check is an authorization bypass: if a regular user hits an admin endpoint and gets 200 instead of 403, access control isn't working. This guide covers every status code you'll encounter in API tests, what each one should and shouldn't return, and Playwright examples for asserting status codes on auth, validation, and creation endpoints.

How Status Codes Are Organized

HTTP status codes are three-digit numbers organized into five classes:

| Range | Class | Meaning |

|-------|-------|---------|

| 1xx | Informational | Request received, processing continues |

| 2xx | Success | Request completed successfully |

| 3xx | Redirection | Client needs to take another action (usually follow a redirect) |

| 4xx | Client Error | Request has a problem: the client sent something wrong |

| 5xx | Server Error | Request was valid: the server failed to fulfill it |

The first digit tells you the category. 4xx = your request was bad. 5xx = the server broke.

2xx: Success Codes

200 OK

The most common success code. The request worked, and there's content in the response.

Used for:
  • GET requests returning data
  • POST requests that don't create a new resource (like a search)
  • PUT/PATCH updates
What to check in tests:
  • Response body contains expected data
  • Response is in the right format (JSON, XML)
  • Required fields are present

201 Created

A resource was created successfully. Should be returned for POST requests that create new items.

What to check:
  • Response includes the created resource with its new ID
  • Location header often points to the new resource URL
  • POST /users201 with { id: 123, email: "...", ... }
Common bug to catch: API returns 200 instead of 201 for creation endpoints. Technically it works, but it's semantically wrong and can confuse consumers.

204 No Content

Success, but nothing to return. Used for DELETE requests and some PUT/PATCH responses.

What to check:
  • Response body is empty (some APIs incorrectly return a body with 204)
  • The resource was actually deleted (follow up with a GET to confirm)

206 Partial Content

Returned when only part of a resource is sent (chunked downloads, video streaming). Mostly relevant for testing file downloads and media streaming.

3xx: Redirect Codes

301 Moved Permanently

The resource has permanently moved to a new URL. Search engines update their indexes for 301.

Testing relevance: Check that old URLs redirect correctly, that HTTPS redirects from HTTP are 301 (not 302), and that redirect destinations load correctly.

302 Found (Temporary Redirect)

The resource is temporarily at a different URL. Used for login redirects ("go here now, original URL still valid").

304 Not Modified

The client has a cached version and it's still current — no content sent, use the cache. You'll see this in the browser Network tab for static assets.

Testing relevance: If you're testing cache behavior, 304 means caching is working. If you're seeing 304 for data that should have changed, you have a caching bug.

4xx: Client Error Codes

These mean your request was the problem — bad data, missing auth, wrong URL.

400 Bad Request

The server couldn't understand the request. Usually means malformed syntax, invalid JSON, or a validation error.

When you should see 400:
  • Invalid JSON in the request body
  • Missing required header
  • Malformed URL
What to check in tests:
  • Returns useful error message explaining what's wrong
  • Doesn't reveal internal details (stack traces)
  • Returns 400, not 500 (500 for invalid input is a server bug)

401 Unauthorized

The request lacks valid authentication credentials. "You're not logged in" or "your token is expired."

Difference from 403: 401 = not authenticated (who are you?). 403 = authenticated but not permitted (I know who you are, you just can't do this). What to test:
  • Request without any token → 401
  • Request with an expired token → 401
  • Request with an invalid/forged token → 401
  • Error response doesn't reveal information about valid tokens

403 Forbidden

Authenticated, but not allowed. "You're logged in, but you don't have permission for this."

What to test:
  • Regular user accessing admin endpoint → 403
  • User accessing another user's private data → 403
  • Response doesn't expose any data the user shouldn't see
Security relevance: A missing 403 check means unauthorized access works — a real security bug.

404 Not Found

The requested resource doesn't exist at that URL.

What to test:
  • Requesting a deleted resource → 404
  • Requesting with an ID that was never created → 404
  • Should NOT reveal whether the resource exists for sensitive resources (user accounts — returning 404 "user not found" vs 403 "forbidden" can reveal account existence)
  • Is it 404 or 410? (410 = permanently deleted — some APIs distinguish)

405 Method Not Allowed

You used the wrong HTTP method. GET on an endpoint that only accepts POST.

Testing relevance: Try wrong methods intentionally — DELETE /users when it should only be GET. Should return 405, not 500 or silent failure.

409 Conflict

The request conflicts with the current state of the resource.

Common uses:
  • Creating a user with an email that already exists
  • Editing a resource that someone else has locked
  • Version conflict (optimistic locking)
What to test:
  • Duplicate creation → 409 (not 200 or 500)
  • Response explains the conflict clearly

422 Unprocessable Entity

The request was syntactically valid JSON, but semantically wrong — validation failed.

Example: Valid JSON body, but age: -5 violates the rule that age must be positive. 400 vs 422:
  • 400 = can't even parse the request (bad JSON, wrong Content-Type)
  • 422 = parsed fine, but the values fail validation

Many APIs use 400 for both. What matters is consistency — pick one and use it correctly throughout the API.

What to test:
  • Each required field missing → 422 with the specific field name in the error
  • Field values outside valid range → 422
  • Business rule violations → 422

429 Too Many Requests

Rate limiting — you've sent too many requests in too short a time.

What to test:
  • Confirm rate limit headers are returned (X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After)
  • After hitting the limit, requests are rejected
  • After the window expires, requests work again
Also relevant for security: Login endpoints should rate-limit to prevent brute-force attacks. Test that 429 is returned after X failed login attempts.

5xx: Server Error Codes

These are always bugs. The request was valid — the server failed to handle it.

500 Internal Server Error

Generic server crash. Something unexpected happened.

In testing: If you can reliably reproduce a 500, it's a bug — even if the input was intentionally bad. Good API design means invalid input returns 4xx, not 500. What to test:
  • Invalid input should never produce 500 — it should produce 400/422
  • Null/missing optional fields shouldn't produce 500
  • Edge-case data (empty arrays, very long strings) shouldn't produce 500

502 Bad Gateway

The server was acting as a gateway/proxy and got a bad response from an upstream server. Often means a dependency is down.

503 Service Unavailable

The server is temporarily unavailable — overloaded or in maintenance mode.

Testing relevance: Check how the frontend handles 503. Does it show a useful error page? Does it retry intelligently? Does it degrade gracefully?

504 Gateway Timeout

Similar to 502 — a gateway timed out waiting for an upstream server. Often appears under high load or when a third-party service is slow.

Quick Reference Table

| Code | Meaning | Common cause in bugs |

|------|---------|---------------------|

| 200 | OK | Returned for creates that should be 201 |

| 201 | Created | Missing: API returns 200 for POST |

| 204 | No Content | Body returned when it shouldn't be |

| 301 | Permanent Redirect | Wrong type of redirect |

| 304 | Not Modified | Stale cache showing outdated data |

| 400 | Bad Request | Returned for valid-but-wrong input (should be 422) |

| 401 | Unauthorized | Missing: auth bypass vulnerability |

| 403 | Forbidden | Missing: authorization bypass vulnerability |

| 404 | Not Found | 500 returned for missing resources instead |

| 409 | Conflict | Missing: duplicates created silently |

| 422 | Unprocessable Entity | Confused with 400 |

| 429 | Too Many Requests | Missing: brute force possible |

| 500 | Internal Server Error | Triggered by invalid input (should be 4xx) |

| 503 | Service Unavailable | No graceful degradation in frontend |

Testing Status Codes with Playwright

test('creating a user returns 201', async ({ request }) => {
  const response = await request.post('/api/users', {
    data: { email: 'new@test.com', password: 'ValidPass1' },
  });
  expect(response.status()).toBe(201);
});

test('duplicate email returns 409', async ({ request }) => {
  // Create user first
  await request.post('/api/users', {
    data: { email: 'existing@test.com', password: 'ValidPass1' },
  });
  // Try again with same email
  const response = await request.post('/api/users', {
    data: { email: 'existing@test.com', password: 'ValidPass1' },
  });
  expect(response.status()).toBe(409);
});

test('missing token returns 401', async ({ request }) => {
  const response = await request.get('/api/profile');
  expect(response.status()).toBe(401);
});

test('wrong role returns 403', async ({ request }) => {
  // Use a member token, try to access admin endpoint
  const response = await request.get('/api/admin/users', {
    headers: { Authorization: `Bearer ${memberToken}` },
  });
  expect(response.status()).toBe(403);
});

Status code assertions are the fastest, most reliable API tests you can write. They run in milliseconds and catch authorization bugs, missing validation, and incorrect API design.

→ See also: API Testing 101: What Every QA Engineer Needs to Know in 2026 | What Is a REST API? A Practical Guide for QA Engineers | API Testing with Playwright: Beyond the UI | Authentication in API Tests: API Keys, Bearer Tokens, OAuth2, JWT