HTTP · REST · API testing · Reference

HTTP status codes explained, with API testing tips

A reference to the HTTP status codes APIs actually return, what RFC 9110 says each one means, what to verify when you see it, and how to assert status codes in swc.* tests.

By · · 9 min read

An HTTP status code is the three-digit number at the start of every response that tells the client how the server handled the request. The first digit gives the class: 1xx informational, 2xx success, 3xx redirection, 4xx client error and 5xx server error. RFC 9110 defines their meaning; for example 200 OK means the request succeeded, 404 Not Found means the target resource does not exist, and 503 Service Unavailable means the server cannot handle the request right now. This guide covers the codes APIs actually return, what to check when you see each one, and how to assert them in API tests.

How status codes work

Every HTTP response starts with a status code and an optional reason phrase, such as HTTP/1.1 201 Created. The code is for software; the reason phrase is for humans and is not even sent in HTTP/2 and HTTP/3. RFC 9110 says clients should ignore the phrase, so tests and client code should act on the number only.

RFC 9110, section 15, defines the five classes and most of the codes. One rule there is useful to know when testing: a client that does not recognise a code must treat it as the x00 code of its class. An unknown 499 is handled like 400, an unknown 599 like 500. That is why tests that check the class (any 2xx, any 4xx) are often more robust than tests that pin one exact code, and why APIs should not invent codes when a standard one fits.

ClassMeaningWho should act
1xxInformational, interim responseThe HTTP library, usually invisibly
2xxThe request was received, understood and acceptedNobody: success
3xxFurther action is needed, usually a redirectThe client, often automatically
4xxThe request has an errorThe client must change the request
5xxThe server failed to fulfil a valid requestThe server operator

The 4xx/5xx split matters most when testing. A 4xx says "your request is wrong; retrying it unchanged will not help". A 5xx says "the server failed". An API that returns 500 for invalid input makes clients guess.

1xx informational

You rarely see these in an API client, because the HTTP library handles them before the final response arrives.

  • 100 Continue: the server is willing to receive the body of a request that was sent with Expect: 100-continue. Used for large uploads.
  • 101 Switching Protocols: the server agrees to switch protocols, most often the WebSocket upgrade. If you are testing WebSockets, see testing a WebSocket API online.

2xx success

200 OK

The request succeeded and the body carries the result. When testing: do not stop at the status. Check Content-Type, the body shape and the values; a 200 with an error object inside is a common bug.

201 Created

A new resource was created (RFC 9110, section 15.3.2). The server usually identifies it with a Location header, and many APIs also return the created object. When testing: assert 201 rather than any 2xx for create endpoints, check the Location header or returned id, and fetch the new resource to confirm it really exists.

202 Accepted

The request was accepted for processing, but processing is not finished and may still fail. Common for jobs, exports and webhooks. When testing: the body should tell you how to follow up, typically a job id or a status URL. Poll it until the job finishes; the collection runner's setNextRequest is made for this (see below).

204 No Content

Success, with no body. Typical for DELETE and for PUT or PATCH when the API does not echo the resource. When testing: do not call swc.response.json() on a 204, because there is nothing to parse. Assert the status and, if relevant, fetch the resource afterwards.

3xx redirection

301 Moved Permanently and 308 Permanent Redirect

The resource has a new permanent URL, given in Location. The difference: on a 301, clients historically changed a POST into a GET when following it; a 308 (section 15.4.9) forbids changing the method.

302 Found and 307 Temporary Redirect

The same pair for temporary moves. 302 allows the historical POST-to-GET change; 307 requires the client to repeat the same method and body.

303 See Other

Points the client to another resource that it should fetch with GET. Often used after a POST to send the client to the result.

304 Not Modified

Returned to a conditional GET (with If-None-Match or If-Modified-Since) when the client's cached copy is still valid. It has no body. When testing caching: send the request once, copy the ETag, send it again with If-None-Match: "<etag>" and expect 304.

Testing redirects in Swastik Codes. By default the client follows redirects, so you see the final response. To test the redirect itself, open the request's Settings tab and turn off Automatically follow redirects; you then see the 3xx and its Location header. The same tab has Follow original HTTP method and a maximum number of redirects. Try https://httpbin.org/redirect-to?url=https://httpbin.org/get&status_code=307 with following on and off.

4xx client errors

400 Bad Request

The server cannot or will not process the request because of something it sees as a client error: malformed JSON, a missing required field, an invalid parameter. When testing: send deliberately broken input and check that the error body says what is wrong. A 400 with no explanation is hard for client developers to use.

401 Unauthorized

The request lacks valid authentication credentials. Despite the name, this is about authentication. RFC 9110 requires a 401 response to include a WWW-Authenticate header that tells the client how to authenticate. When testing: call each protected endpoint with no credentials, an invalid token and an expired token, and expect 401 each time. The JWT authentication testing guide walks through those cases.

403 Forbidden

The server understood the request and refuses to fulfil it: this user may not do this. When testing: use a valid token for a user without the right role and expect 403. Some APIs answer 404 instead to hide that a resource exists; that is allowed, but should be consistent.

404 Not Found

The server did not find anything at the target URL, or will not say whether it exists. When testing: request an id that does not exist and expect 404; a 500 here means the API crashes on unknown ids. Also check that a typo in the path gives 404 and not a misleading 200 from a catch-all route.

405 Method Not Allowed

The resource exists but does not support the method. The server must include an Allow header listing the methods it does support (section 15.5.6). When testing: send DELETE to a read-only endpoint and check both the status and Allow.

406 Not Acceptable and 415 Unsupported Media Type

406 means the server cannot produce a response in any format listed in your Accept header. 415 means it will not accept the format of the body you sent, usually a wrong or missing Content-Type. When testing: send XML to a JSON-only endpoint and expect 415.

409 Conflict

The request conflicts with the current state of the resource: a duplicate username, an edit based on an outdated version. When testing: create the same unique resource twice and expect 409 the second time.

412 Precondition Failed

A condition in the request headers, such as If-Match: "<etag>", was false. This is how APIs implement optimistic concurrency. When testing: update a resource with a stale ETag and expect 412.

422 Unprocessable Content

The server understands the content type and the syntax is correct, but it cannot process the instructions: a validation error such as an end date before a start date. Many APIs use 422 for semantic validation errors and 400 for malformed requests; others use 400 for both. Either is defensible; mixing them at random is not.

429 Too Many Requests

Defined in RFC 6585, section 4. The client has sent too many requests in a given time. The response may include a Retry-After header saying how long to wait. When testing: check that rate-limited responses carry Retry-After (or the API's documented rate-limit headers) and that your client backs off. In a collection run, a delay between requests keeps you under the limit.

5xx server errors

500 Internal Server Error

A generic server failure. When testing: a 500 is always worth a bug report; also check the body does not leak stack traces or internal hostnames.

502 Bad Gateway

A gateway or proxy got an invalid response from the server behind it. The API process may have crashed or be restarting.

503 Service Unavailable

The server is temporarily unable to handle the request, because of overload or maintenance. It may send Retry-After. When testing: this is the code a well-behaved API returns under load shedding; load testing is how you find out when it starts.

504 Gateway Timeout

A gateway or proxy did not get a response from the upstream server in time. Usually a slow query or a downstream dependency that hangs.

Asserting status codes in tests

In Swastik Codes, post-response scripts use the swc.* API, which is the same object as Postman's pm.*. The most direct check:

JavaScript
swc.test('status is 201', function () {
  swc.response.to.have.status(201);
});

Readable shortcuts exist for common codes and classes:

JavaScript
swc.test('request succeeded (any 2xx)', function () {
  swc.response.to.be.success;
});

swc.test('unknown id gives 404', function () {
  swc.response.to.be.notFound;
});
AssertionPasses when the status is
swc.response.to.be.okexactly 200
swc.response.to.be.success200–299
swc.response.to.be.redirection300–399
swc.response.to.be.clientError400–499
swc.response.to.be.serverError500–599
swc.response.to.be.error400–599
swc.response.to.be.accepted202
swc.response.to.be.withoutContent204
swc.response.to.be.badRequest400
swc.response.to.be.unauthorized401
swc.response.to.be.forbidden403
swc.response.to.be.notFound404
swc.response.to.be.rateLimited429

When more than one code is acceptable, compare the number directly:

JavaScript
swc.test('upsert returns 200 or 201', function () {
  swc.expect(swc.response.code).to.be.oneOf([200, 201]);
});

Check headers that belong with the code, too:

JavaScript
if (swc.response.code === 429) {
  swc.test('rate limit response says when to retry', function () {
    swc.response.to.have.header('Retry-After');
  });
}

if (swc.response.code === 201) {
  swc.test('created resource has a Location', function () {
    swc.response.to.have.header('Location');
  });
}

Put the status test first in each script. If the body is an HTML error page, swc.response.json() throws, and a failing status test tells you why far more clearly than a JSON parse error. The swc.* scripts guide covers the rest of the assertion API.

Testing negative cases with a data file

Status code tests become much more useful when they cover the failure cases. Put the inputs and expected codes in a CSV file:

CSV
userId,expectedStatus
1,200
99999,404
abc,404

Then request https://jsonplaceholder.typicode.com/users/{{userId}} and assert against the row:

JavaScript
swc.test('status matches the data row', function () {
  swc.response.to.have.status(Number(swc.iterationData.get('expectedStatus')));
});

Attach the file in the collection runner and each row becomes one iteration. Data-driven API testing explains the setup.

Polling a 202 job

JavaScript
// Post-response script of "Check job status"
const job = swc.response.json();
if (job.status === 'done') {
  swc.execution.setNextRequest('Download result');
} else {
  swc.execution.setNextRequest('Check job status');
}

In a collection run this repeats the status check until the job is done. Add a delay between requests in the runner settings so you do not hammer the API.

To try the assertions, send a GET to https://httpbin.org/status/<code> in the app: it returns whatever status you ask for, such as 429 or 503.

Frequently asked questions

What is the difference between 401 and 403?

401 Unauthorized means the request has no valid authentication; sending correct credentials may fix it, and the response must include WWW-Authenticate. 403 Forbidden means the server knows who you are, or does not need to, and still refuses; re-authenticating as the same user will not help.

Should an API return 400 or 422 for validation errors?

Both are acceptable under RFC 9110. A common convention is 400 for malformed requests (invalid JSON, wrong types) and 422 for well-formed requests that fail business validation. Pick one convention, document it and test it.

Is it wrong to return 200 with an error in the body?

It works, but it hides failures from every tool that looks at status codes: caches, proxies, monitoring and test runners. Use a 4xx or 5xx code and put the details in the body.

What is the difference between 301, 302, 307 and 308?

301 and 308 are permanent, 302 and 307 temporary. 307 and 308 require the client to keep the original method and body; 301 and 302 historically let clients change POST to GET.

When should an API use 404 instead of 403?

When revealing that a resource exists would itself leak information, RFC 9110 allows a server to answer 404 instead of 403. Whichever you choose, apply it consistently.

How do I test that an API returns the right status code?

Send the request and assert the code in a post-response test, for example swc.response.to.have.status(404). Cover the error paths as well as the happy path, and run them together in a collection run.

Try it on your own API

Swastik Codes runs in the browser and is free to use. Create an account to send requests, sync your workspaces and invite your team.