HTTP status codes cheat sheet for API developers (RFC 9110)
Which HTTP status codes a JSON API needs, when to use each, the headers that go with them and common mistakes, checked against RFC 9110 and the IANA registry.
7 min read

In this article
Status codes are the first thing every API client reads, and the first thing it gets wrong when they are inconsistent. A 200 with an error message inside, a 500 for a validation failure or a 403 where a 401 was meant all push work onto every developer who integrates with you.
This cheat sheet covers the status codes a typical REST or JSON API actually needs, with the rules from RFC 9110 (the current HTTP Semantics standard) and the IANA registry, both checked on 9 October 2026. Each entry says when to use the code, what headers go with it, and the common mistakes. Keep it open while you design endpoints or review a pull request.

The five classes in one table
| Class | Meaning | Who should act |
|---|---|---|
1xx | Informational: the request is in progress | Usually handled by the HTTP library |
2xx | Success | Client continues normally |
3xx | Redirection: look elsewhere or use a cached copy | Client follows Location or uses its cache |
4xx | Client error: the request must change before retrying | The caller fixes the request |
5xx | Server error: the request may be fine; the server failed | The operator; the caller may retry later |
The class matters more than the exact number. Clients, proxies and monitoring tools treat any unknown code as the x00 of its class, so a 4xx must always mean "the caller did something wrong" and a 5xx "we did".
2xx: success codes for APIs
| Code | Name | Use it when |
|---|---|---|
200 | OK | A read succeeded, or an update returns the updated resource |
201 | Created | A POST (or PUT) created a resource. RFC 9110 says the new resource is identified by the Location header, so send it. |
202 | Accepted | Work was queued, not finished: imports, exports, video processing. Return a link to a status resource. |
204 | No Content | Success with nothing to return, such as a DELETE or an update where the client already has the data. The response has no body. |
206 | Partial Content | You serve byte ranges, for example resumable downloads |
3xx: redirects and caching
| Code | Name | Method on redirect | Use it for |
|---|---|---|---|
301 | Moved Permanently | May change POST to GET | Permanent page moves (SEO redirects) |
302 | Found | May change POST to GET | Temporary redirects of GET requests |
303 | See Other | Always GET | After a form POST, send the browser to a result page |
304 | Not Modified | Not a redirect | Conditional GET with ETag/If-None-Match: the cached copy is still valid |
307 | Temporary Redirect | Must not change | Temporary redirects of API calls |
308 | Permanent Redirect | Must not change | Permanent moves of API endpoints |
RFC 9110 keeps a historical note on 301 and 302: a user agent may change the request method from POST to GET when following them, and the standard points to 307 and 308 if that is undesired. For APIs, that settles it: use 307 or 308, because a client that silently turns your POST into a GET loses the request body.

4xx: client errors you will actually use
Authentication and permissions
401 Unauthorizedmeans "not authenticated": no credentials, or invalid ones. RFC 9110 says a server generating a 401 MUST send aWWW-Authenticateheader with at least one challenge, for exampleWWW-Authenticate: Bearer. MDN puts it plainly: semantically, 401 means "unauthenticated".403 Forbiddenmeans the server knows who you are, but you may not do this. The client should not retry with the same credentials.- Hiding existence: RFC 9110 allows a server to answer
404instead of403to hide that a forbidden resource exists. This is a good default for other tenants' records in multi-tenant APIs.
Requests that are wrong
| Code | Name | Use it when |
|---|---|---|
400 | Bad Request | The request is malformed: invalid JSON, wrong types, missing required parameters |
404 | Not Found | No such resource (or one you are deliberately hiding) |
405 | Method Not Allowed | The resource exists but not for this method. RFC 9110: the server MUST send an Allow header listing supported methods. |
406 | Not Acceptable | You cannot produce any format listed in the client's Accept header |
409 | Conflict | The request conflicts with current state: a stale version, a duplicate unique key, an invalid state transition |
410 | Gone | The resource existed and was removed on purpose, such as a retired API version |
412 | Precondition Failed | An If-Match or If-Unmodified-Since condition failed (optimistic locking via headers) |
413 | Content Too Large | An upload or body exceeds your limit |
415 | Unsupported Media Type | The body is in a format you do not accept, such as XML sent to a JSON-only endpoint |
422 | Unprocessable Content | The body is well-formed but semantically invalid: validation errors |
429 | Too Many Requests | Rate limiting. RFC 6585 says the response may include Retry-After. |
400 or 422 for validation errors?
Both are defensible. RFC 9110 defines 422 for content whose type and syntax are correct but whose instructions cannot be processed, which describes most validation failures. Laravel follows that convention: its documentation says a failed validation on a request expecting JSON returns a 422 response with the errors. Many other APIs use 400 for everything the caller got wrong. Pick one, document it, and apply it consistently. Whichever you choose, return field-level detail in the body; RFC 9457 Problem Details is a standard format for that.
Renamed codes
RFC 9110 renamed two familiar codes: 413 is now Content Too Large (formerly Payload Too Large) and 422 is Unprocessable Content (formerly Unprocessable Entity). The numbers did not change, and libraries lag behind: in Node.js 24.19.0, http.STATUS_CODES[422] still returns "Unprocessable Entity" and [413] returns "Payload Too Large". Clients should never depend on the reason phrase; HTTP/2 and HTTP/3 do not even send one.
5xx: server errors
| Code | Name | Use it when |
|---|---|---|
500 | Internal Server Error | An unexpected failure in your code. Log it with a request ID; never return stack traces. |
501 | Not Implemented | The server does not support the method at all (rare in application code) |
502 | Bad Gateway | A proxy or gateway received an invalid response from upstream |
503 | Service Unavailable | Temporary overload or maintenance. RFC 9110: the server may send Retry-After. |
504 | Gateway Timeout | A proxy did not get a response from upstream in time |
Your application code usually returns only 500 and 503. 502 and 504 normally come from nginx, a load balancer or a CDN in front of you, which makes them a useful hint during an incident: the problem is between the proxy and your app.
Codes to avoid or treat with care
418is listed in the IANA registry as "(Unused)". It began as an April Fools' joke; do not use it in production APIs.402 Payment Requiredis reserved for future use in RFC 9110. If you use it for billing problems, document exactly what it means in your API.200with an error body breaks retries, monitoring and caching. If the request failed, say so in the status code.- Temporary registrations: the IANA registry currently lists 104 (Upload Resumption Supported) as a temporary registration expiring on 13 November 2026, tied to an IETF draft. Check the registry before relying on anything new.
Check status codes from the terminal
# Status code only
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/api/orders/123
# Status line and headers (look for Location, Allow, Retry-After, WWW-Authenticate)
curl -si -X DELETE https://example.com/api/orders/123 | head -n 20In Node.js, import { STATUS_CODES } from 'node:http' gives you a lookup table of names, which is handy for logs but, as noted above, not always the newest wording. In PHP, http_response_code(422) sets the status in plain PHP; frameworks such as Laravel and Symfony provide response helpers.
What to do next
- List every endpoint and the status codes it can return; put them in your OpenAPI description.
- Choose 400 or 422 for validation and use it everywhere.
- Add the required headers:
Locationon 201,Allowon 405,WWW-Authenticateon 401, andRetry-Afteron 429 and 503 where you can. - Replace 301 and 302 with 307 and 308 for anything other than browser page moves.
- Write one test per error path, so a refactor cannot quietly turn a 404 into a 500.
Consistent status codes are cheap documentation. When every endpoint follows the same rules, your API's clients need fewer special cases, and your logs tell you at a glance whether the caller or the server is at fault.
Frequently asked questions
What is the difference between 401 and 403?
401 means the request is not authenticated and must include a
WWW-Authenticatechallenge. 403 means the caller is known but not allowed to do this.Should validation errors return 400 or 422?
Either works if you are consistent. RFC 9110 defines 422 for well-formed but semantically invalid content, and Laravel returns 422 for failed JSON validation.
When should I use 307 or 308 instead of 302 or 301?
Whenever the request method must be preserved, which is almost always the case for APIs. Clients may turn a POST into a GET when following 301 or 302.
Is 422 still called Unprocessable Entity?
RFC 9110 renamed it Unprocessable Content, and 413 became Content Too Large. Some libraries, including Node.js 24's
http.STATUS_CODES, still use the old names.Which header should accompany a 429 response?
Retry-After, telling the client how long to wait. RFC 6585 says the response may include it, and well-behaved clients use it to back off.Can I return 404 instead of 403?
Yes. RFC 9110 allows a server to respond 404 to hide the existence of a forbidden resource, which is useful for records belonging to other users or tenants.
Your next customer could start right here.
Introduce your tool, product or business to readers exploring technology.
Explore placements and request a private quote
Sources
- RFC 9110: HTTP Semantics — IETF, 2022-06-01
- Hypertext Transfer Protocol (HTTP) Status Code Registry — IANA
- RFC 6585: Additional HTTP Status Codes — IETF, 2012-04-01
- HTTP response status codes — MDN
- Laravel 13.x: Validation — Laravel
Comments
No comments yet. Be the first to join the conversation.
Leave a comment
Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.
More from Cloudsoft Zone

Resource guideResources
Cron syntax cheat sheet: crontab, GitHub Actions, Kubernetes
Cron syntax with tested expressions, the day-of-week trap, and how time zones and limits differ in crontab, GitHub Actions, Kubernetes and Laravel in 2026.
By Cloudsoft Zone

Resource guideResources
HTTP security headers checklist: what to send and how to test
The HTTP security headers worth sending in 2026, with OWASP-recommended values, nginx and Helmet configuration, a safe CSP rollout and free ways to test them.
By Cloudsoft Zone

Resource guideResources
Free accessibility testing tools and a 30-minute WCAG 2.2 check
The free, maintained accessibility tools worth installing in 2026, from WAVE and axe to NVDA, plus a 30-minute WCAG 2.2 release check and an axe-core script.
By Cloudsoft Zone
