beginner·9 min read·Updated May 1, 2026

HTTP Status Codes: The Complete Tester's Reference

2xx means success, 4xx means you messed up, 5xx means they messed up — but the details matter. Here's the list every tester should know by heart.

The three-digit mental model

Status codes are three digits. The first digit tells you the category:

  • 1xx — Informational (rare; mostly WebSocket upgrades)
  • 2xx — Success
  • 3xx — Redirection
  • 4xx — Client error (you sent something wrong)
  • 5xx — Server error (they broke)

Memorize the categories and you can handle any status code you've never seen. A 418 I'm a teapot is, somehow, a client error. A 599 Network Connect Timeout is a server error. The category is reliable.

The 2xx codes testers should know

  • 200 OK — generic success. Use for successful GETs, PUTs, PATCHes, and responses with a body.
  • 201 Created — a new resource was created. Use for successful POSTs that create resources.
  • 202 Accepted — the request was accepted but not yet completed. Common in async workflows where a long-running job has been queued.
  • 204 No Content — success, but no response body. Standard for DELETE and for PUT/PATCH when you don't want to echo the resource back.

The 4xx codes testers should know

  • 400 Bad Request — the request itself is malformed. Missing required fields, bad JSON, invalid query parameters. This is your default "you sent me garbage" response.
  • 401 Unauthorized — actually means "unauthenticated." You didn't send credentials, or they're invalid.
  • 403 Forbidden — you're authenticated, but you're not allowed to do this specific thing. 401 says "prove who you are"; 403 says "I know who you are, and the answer is no."
  • 404 Not Found — the resource doesn't exist. Also used when you want to hide the existence of a resource (instead of 403) for security reasons.
  • 409 Conflict — the request conflicts with current server state. Creating a user with an email that already exists is a classic 409.
  • 422 Unprocessable Entity — syntactically valid but semantically wrong. The JSON parses, but the values are invalid (e.g., age: -5). Some APIs use 400 here; both are defensible.
  • 429 Too Many Requests — rate-limited. Back off and retry. The server should send a Retry-After header.

The 5xx codes testers should know

  • 500 Internal Server Error — something broke on the server and they don't know what. Generic. Indicates a bug.
  • 502 Bad Gateway — an upstream service failed. The API you called is trying to call another API and that one is unreachable.
  • 503 Service Unavailable — temporarily down, usually for maintenance or overload. Try again later.
  • 504 Gateway Timeout — an upstream service didn't respond in time.

5xx codes are where monitoring lives. A well-tested API should rarely return 5xx — and when it does, you should know within minutes.

Seeing errors in action

Let's look at a real 404 from the sandbox. The ?error=404 parameter forces the endpoint to respond with a 404 so you can inspect the error shape.

GET/api/v1/users?error=404
Force a 404 response to see how error bodies are structured.
curl -X GET 'https://demo.totalshiftleft.ai/api/v1/users?error=404'

Two things worth noticing:

  1. The HTTP status code itself is 404. Tools know this without parsing the body.
  2. The response body has a consistent structure: success, error code, message. Good APIs standardize their error envelope so clients can handle errors generically.

400 with field-level details

A good validation error tells the client which fields failed and why. Let's send a request with an empty name and an invalid email:

POST/api/v1/users
Send invalid data to trigger 400 with field-level errors.
curl -X POST 'https://demo.totalshiftleft.ai/api/v1/users' \
  -H 'Content-Type: application/json' \
  -d '{"name":"","email":"not-an-email"}'

The response status is 400 and the body contains a details array listing each field that failed validation. This is the gold standard: structured, field-level errors mean clients can show exact error messages next to the right form fields, and tests can assert on specific failures rather than just "the response was bad."

Common mistakes

1. Using 200 for errors. Some APIs return 200 OK with { "success": false, "error": "..." }. This breaks every generic HTTP client, breaks retries, and breaks monitoring. Always reflect the real outcome in the status code.

2. Using 500 as a catch-all. A malformed request from the client is a 400, not a 500. Teams that map every exception to 500 hide real client-side bugs as server-side alerts.

3. Treating 401 and 403 as interchangeable. They're different problems. 401 = log in again. 403 = ask your admin for access. The response status drives different UI flows.

4. Ignoring Retry-After on 429. When rate-limited, blindly retrying makes the problem worse. Honor the Retry-After header (seconds or HTTP date).

What to assert in tests

Every API test should assert the status code. At a minimum:

  • Happy-path test → assert 200 (or 201 for POST creates, 204 for DELETE).
  • Invalid input test → assert 400 (or 422) and assert the error body shape.
  • Unauthenticated test → assert 401.
  • Forbidden test → assert 403.
  • Missing resource test → assert 404.
  • Duplicate / conflict test → assert 409.

If you use OpenAPI, the spec declares which status codes each endpoint can return. Tools like ShiftLeft generate a test for every declared status code automatically — but you should still know what each one means when you review the generated tests.

What's next

Now that you know what the server can tell you, the next lesson breaks down the anatomy of a request and response — headers, bodies, query params, and the edge cases that trip people up.

Related lessons

Read more on the blog