HTTP Status Codes Beyond 200 OK: What They Mean for Web Testing
Learn what HTTP status codes mean, how to test response behavior beyond 200 OK, and how to diagnose redirects, client errors, and gateway failures.
HTTP status codes summarize how a server handled a request, but the first digit and the number alone are not enough to decide whether a web test passed. Test the specific code against the endpoint contract, then check the relevant headers, response body, application state, and any follow-up behavior. For example, 202 Accepted does not prove asynchronous work is finished, while 204 No Content means a successful response should contain no representation.
This guide explains the common codes and shows how to turn their protocol meanings into reliable web and API checks. The definitions follow RFC 9110, HTTP Semantics; the IANA HTTP Status Code Registry lists registered codes and their specifications.
1. What an HTTP status code tells you
A status code is a three-digit part of an HTTP response. Its first digit places it in a broad class:
| Class | Broad meaning | Testing question |
|---|---|---|
| 1xx | Informational response | Is this interim protocol behavior, or the final response exposed by the client? |
| 2xx | Request succeeded | What specific success outcome does this endpoint promise? |
| 3xx | Redirection-related response | Should the client follow a redirect or reuse a cached representation? |
| 4xx | Client error | Was the request malformed, unauthenticated, forbidden, missing, conflicting, or limited? |
| 5xx | Server error | Did the origin fail, become temporarily unavailable, or encounter an upstream problem? |
The class is a starting point, not a test expectation. RFC 9110 notes that a client may encounter a registered status code it does not understand. Its response should still be interpreted using the class and any applicable protocol rules; application code should avoid assuming every possible code has a custom meaning it recognizes. The endpoint contract determines which outcome is correct.
2. Test the response contract, not just the number
A useful HTTP test describes the request and the observable result. Record the method, URL, request headers, and relevant starting state, then check:
- Status: Is this the specific code promised for this request and state?
- Headers: Are required headers present and meaningful, such as
WWW-Authenticate,Location, or cache validators? - Body: Should there be a representation? If so, does it meet the documented content type and schema? If not, is content absent?
- State and follow-up: Was a resource created? Does polling eventually report completion? Does a redirect reach the intended destination? Does a conditional request reuse a stored representation?
Keep standard semantics separate from application choices. HTTP defines what a code means; it does not prescribe one universal JSON error shape, retry delay, or business workflow for every API.
3. Common status codes and their testing implications
2xx: successful, with different outcomes
| Code | Meaning | What to test |
|---|---|---|
200 OK |
The request succeeded. | Check the expected representation and headers for this method and endpoint. A 200 alone does not prove the content is correct. |
201 Created |
The request succeeded and created one or more resources. | Verify the created resource, identifier, or Location when the contract specifies it. Check the resulting state, not only the response code. |
202 Accepted |
The request was accepted for processing, which may not be complete. | Do not infer completion from acceptance. Follow the documented status, polling, or callback flow if one exists. |
204 No Content |
The request succeeded without response content. | Assert that the response has no content. Do not try to parse a representation that the endpoint should not return. |
These success codes are not interchangeable. The method and endpoint contract decide which is expected and what result to verify.
3xx: redirects and cache validation
| Code | Meaning | What to test |
|---|---|---|
301 Moved Permanently |
The resource has a permanent redirect. | Check the redirect target and intended permanence behavior. |
302 Found |
The resource is temporarily available at a different URI. | Check the target and how the actual client handles the response. Do not assume every client handles method changes identically. |
304 Not Modified |
A conditional request indicates that the stored representation remains current. | Send the relevant conditional request and verify cache behavior. This is not an ordinary redirect and is not a fresh representation body. |
Many HTTP clients follow redirects automatically, so a test may expose the final response instead of the intermediate 3xx. Turn off automatic following when the test needs to assert the original status and Location; otherwise assert the final destination and result. Exercise the same behavior your application or integration uses.
4xx: request, identity, permission, and state problems
| Code | Meaning | What to test |
|---|---|---|
400 Bad Request |
The server cannot or will not process a request it perceives as a client error, such as malformed syntax or framing. | Exercise the invalid request condition and assert the documented error category and stable fields. Do not assume a universal error payload. |
401 Unauthorized |
The response challenges the request for authentication. | Check the applicable WWW-Authenticate challenge and the authentication flow. RFC 9110 requires at least one applicable challenge in this response. |
403 Forbidden |
The server understood the request but refuses to fulfill it. | Test the refusal condition separately from absent or invalid credentials. |
404 Not Found |
No current representation was found, or the server is unwilling to disclose that one exists. | Test missing routes and resources, while allowing for intentional concealment of existence. |
409 Conflict |
The request conflicts with the target resource’s current state. | Create a state conflict and check the documented resolution or resubmission path. |
429 Too Many Requests |
Commonly used to signal rate limiting. | If rate limits are in scope, inspect the actual API’s retry guidance and response headers. The code alone does not establish a universal delay policy. |
401 versus 403: treat these as distinct cases. A 401 is an authentication challenge; a 403 says the server refuses a request it understood. A response code alone does not describe every authorization rule, so test the endpoint’s documented behavior.
5xx: server and upstream failures
| Code | Meaning | What to test |
|---|---|---|
500 Internal Server Error |
The server encountered an unexpected condition that prevented fulfillment. | Record this as a server-side failure, but do not infer the internal cause from the code alone. |
502 Bad Gateway |
A gateway or proxy received an invalid response from an upstream server. | Investigate the intermediary-to-upstream path and the upstream response. |
503 Service Unavailable |
The server is temporarily unable to handle the request. | Check any retry guidance and recovery behavior provided by the response or application. |
504 Gateway Timeout |
A gateway or proxy did not receive a timely response from an upstream server. | Distinguish an upstream timeout from an application returning a generic 500. |
502 versus 503 versus 504: these point to different situations: invalid upstream response, temporary unavailability, and upstream timeout, respectively. They help locate the failure path but do not reveal its root cause by themselves.
4. A practical testing workflow
- Describe the request. Note the method, URL, important request headers, credentials, and relevant resource state.
- Write the expected outcome. Specify the status and any state transition. For asynchronous work, distinguish accepted from completed.
- Check semantics and contract. Use the endpoint documentation alongside the relevant HTTP definition. Do not turn a broad status class into a more specific promise.
- Assert relevant headers. Examples include authentication challenges, redirect destinations, and conditional-cache metadata. Assert only headers the protocol or endpoint contract requires for the scenario.
- Check body expectations. Validate content when a representation is expected; verify absence of response content for cases such as 204 and conditional 304 handling.
- Exercise what happens next. Follow redirects where appropriate, poll a 202 workflow as documented, retry only according to the actual guidance, or confirm cache reuse.
- Keep failure reports actionable. Include the request context, observed status, relevant headers, and a safe summary of the body. Redact credentials and sensitive response data.
5. Runnable examples for checking HTTP responses
The examples below send a request and inspect the status, selected headers, and body behavior. Replace the sample URL and expected results with the endpoint contract for your application. They deliberately do not invent a universal error schema.
cURL
curl --include --max-time 30 https://httpbin.org/status/201
--include shows response headers with the body; --max-time sets a client-side time limit. This command is useful for inspection, but shell success alone does not assert that the HTTP status is the one your test expects. For an explicit check, capture the status and fail on mismatch:
status=$(curl --silent --show-error --output response.body --write-out '%{http_code}' --max-time 30 https://httpbin.org/status/201) || exit 1
if [ "$status" != "201" ]; then
printf 'Expected HTTP 201, got %s\n' "$status" >&2
exit 1
fi
Python
import requests
url = "https://httpbin.org/status/201"
response = requests.get(url, timeout=(5, 30), allow_redirects=False)
expected_status = 201
if response.status_code != expected_status:
raise AssertionError(
f"Expected HTTP {expected_status}, got {response.status_code}"
)
print("Status:", response.status_code)
print("Location:", response.headers.get("Location"))
print("Body bytes:", len(response.content))
For an endpoint whose contract expects a representation, validate its documented media type and fields before parsing. For a 204 response, assert an empty body rather than calling response.json(). Use a context-appropriate method and request data; a sample GET to a status endpoint is not a substitute for testing your real operation.
Node.js
const url = 'https://httpbin.org/status/201';
const response = await fetch(url, { redirect: 'manual', signal: AbortSignal.timeout(30000) });
const expectedStatus = 201;
if (response.status !== expectedStatus) {
throw new Error(`Expected HTTP ${expectedStatus}, got ${response.status}`);
}
console.log('Status:', response.status);
console.log('Location:', response.headers.get('location'));
const body = await response.text();
console.log('Body characters:', body.length);
To check a no-content contract, assert that the body is empty and avoid JSON parsing. To test a redirect, use manual redirect handling when you need the initial 3xx; with automatic handling, inspect the final response and URL according to your runtime and test goal.
6. Edge cases that commonly break status assertions
- Automatic redirects: the library may return the destination’s status, hiding the original 301 or 302. Configure redirect behavior explicitly for the assertion.
- Interim 1xx responses: clients commonly present the final response to application code. Test interim behavior only when the protocol feature itself is in scope.
- Empty bodies: successful status does not imply JSON exists. A 204 has no response content; a 304 is cache validation rather than a fresh body.
- Asynchronous work: 202 indicates acceptance, not completion. Verify the documented follow-up mechanism.
- Authentication and concealment: a service can return 404 when it does not wish to reveal that a resource exists. Do not assume every inaccessible resource must return 403.
- Intermediaries: a proxy or gateway can affect what status reaches the client. When diagnosing 502 or 504, capture enough request and response context to identify the intermediary path.
- Unknown registered codes: a client might not understand a particular code. Keep handling robust and consult the registry and endpoint contract before assigning application-specific behavior.
- Unstable error details: internal messages and error bodies can vary. Assert stable, documented fields instead of incidental wording.
7. Troubleshooting status-code test failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Expected 301 or 302 but received 200 | The client followed the redirect automatically. | Disable redirect following to check the first response, or assert the final URL and result instead. |
| JSON parsing fails on a successful response | The endpoint may return no content, non-JSON content, or a different success representation. | Check the specific status and documented content type before parsing; handle 204 as bodyless. |
| A test treats 202 as completed work | Acceptance was confused with completion. | Use the documented polling, status resource, or callback flow to verify completion. |
| A 401 test has no challenge header | The authentication response may not satisfy the expected challenge contract, or the request reached another layer. | Check WWW-Authenticate and confirm which component generated the response. |
| A missing resource returns 404 instead of 403 | The service may intentionally conceal whether the resource exists. | Align the assertion with the endpoint’s documented security behavior. |
| A 429 test assumes a fixed retry delay | The test inferred timing from the status code alone. | Read retry guidance from the response and API contract; avoid a universal fixed-delay assertion. |
| A 502 or 504 is reported as an application bug | The failure may lie between a gateway and an upstream service. | Inspect gateway, proxy, and upstream observations before assigning the fault. |
| Status varies between runs | Request state, environment, cache, credentials, or a transient service condition may differ. | Make setup deterministic, record relevant headers and state, and separate transient infrastructure failures from contract assertions. |
8. Performance, reliability, and cost of status testing
Status assertions themselves are lightweight; the request and the test environment usually determine the cost. Keep checks focused on the contract, avoid repeatedly exercising expensive workflows without need, and use the same redirect, timeout, authentication, and cache behavior as the integration under test. A short client timeout can make a slow but valid endpoint look unavailable; an excessively long one can make failures slow to diagnose.
For reliability, distinguish an expected application result from an environmental failure. Record the status and useful response metadata, but avoid logging tokens or sensitive bodies. Do not automatically retry every 5xx or 429: retry behavior depends on the method, operation safety, server guidance, and endpoint contract. Repeating a non-idempotent operation can have unintended effects.
When a test also needs a visual record of a rendered page, a screenshot is a separate artifact from the HTTP response contract. ScreenshotNeo is a website screenshot API and MCP server; it captures a rendered page, while status and API assertions should still be checked against the HTTP contract.
9. Or skip the browser setup
For a visual capture alongside your HTTP checks, ScreenshotNeo takes a screenshot with one GET request. See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing state in headers.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for AI agents and MCP clients. - The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 shots; all plans include every feature.
Sign up free for 1,000 screenshots a month, with no card required.
10. Frequently asked questions
Does a 200 status prove the page or API worked correctly?
No. It confirms a successful response class and code, but the test should also check the representation, headers, and expected application outcome.
Should every web test assert a 1xx response?
No. Informational responses are often protocol-level interim behavior. Assert them when that specific protocol behavior matters; most endpoint tests should focus on the final response their client exposes.
Is 304 a redirect?
No. It is used in conditional cache validation to indicate that a stored representation remains current.
Is there one correct response body for each 4xx or 5xx code?
No. HTTP defines status semantics, but the exact application error body is determined by the endpoint contract.
Where can I look up a less familiar status code?
Use the IANA registry to find registered codes and their defining specifications, then check how your client and endpoint contract handle the code.


