Thursday, 11 May 2017
How to Think About HTTP Status Codes
There’s more than a little confusion and angst out there about HTTP status codes. I’ve received more than a few e-mails (and IMs, and DMs) over the years from stressed-out developers (once at 2am, their time!) asking something like this:
Should I use
409or400? Or is500better? My colleague just wants to use200for everything, and the person across the row wants to define a new one!
Let’s dig into what HTTP status codes are, and how to use them.
Two Levels of Meaning
The first thing to know about HTTP status codes is that they are structured; the first digit indicates what kind of response it is, and the other two indicate specific handling for the response. The kinds of responses defined are:
1xx- Informational (also a non-final response)2xx- Successful3xx- Redirection4xx- Client error5xx- Server error
So, for example, 201 Created is a 2xx status code, indicating a successful response, and the
01 specialisation indicates that the request created a resource, located at the URL indicated by
the Location header field.
Likewise, a 409 Conflict is a 4xx status code, so it means that there was a problem with the
request, rather than the server’s handling of it. 09 on a 4xx tells the client that its request
conflicted with the server’s state, and that conflict needs to be resolved before the request is
sent again.
Because the first digit has its own meaning, the recipient of a status code can examine it and understand the general meaning of the response, but not specifically how it should be handled. This allows newly defined status codes to gracefully degrade to generic handling (provided that they’re well-designed).
Generic Semantics
It’s also important to understand that status codes are defined to be potentially applicable to every HTTP resource; we say that they have generic semantics (just like HTTP methods).
Knowing that explains some of the confusion; mapping an application’s specific semantics onto a set of universally generic semantics is a difficult, error-prone process. Trying to make your application “fit” into a set of status codes is only going to cause pain and disappointment. Don’t do it.
It’s unfortunately common to see specs try to do this by saying things like “a 200 OK on the /foo
resource means that the widget has been ordered” or “a 404 Not Found on a widget means that the
widget is back-ordered.” This is effectively re-defining the semantics of the status code
(remember: generic), thereby effectively creating a private version of HTTP, and is effectively a
code smell (but for protocols).
Or, we’ve all seen HTTP “APIs” that try to list every resource and the possible status codes they
could generate; this is well-intentioned, but bad practice. The set of status codes that a
client can potentially encounter is much larger than the handful they list, because a proxy might
be configured, or the server might generate a 421 Misdirected Request, or a 429 Too Many
Requests, or a nice, zesty 500 Internal Server Error.
Instead, applications should define resource behaviours in terms of methods and representations; e.g.,
Resources identified by the
foolink relation type have representations available in the following formats:application/foo+jsonandapplication/bar; you can update its state with PUT usingapplication/foo+json, and PATCH it withapplication/json-patch+json.
The status codes received are already well-defined and understood; there’s no need to enumerate them or refine them.
POST presents a special case, since its semantics are so generic. Here, it makes sense to say something like:
To order a widget, POST the
application/exampleformat to the resource identified with thefoolink relation; success is indicated by a successful (2xx) status code.
Note that the entire 2xx class of status codes is called out as indicating success; this keeps your spec forwards-compatible with future status codes.
In some situations, you might need to talk about a POST returning a specific status code; e.g.,
when it’s used to create a new resource, you use 201 Created along with a Location header
indicating where it is. Or, processing a POST (or a PUT) could conflict with the current state of
the server, leading to a 409 Conflict. There aren’t too many of these, though.
And, of course, your specification should always include lots of examples.
Specific Effects
As mentioned above, status codes define the specific effects of the response.
For example, 301 Moved Permanently tells the client that the resource has moved, and it can
create a new request if it wishes. 401 Unauthorized triggers HTTP’s request
authentication mechanism. 429 Too Many Requests tells the
client that it’s rate-limited, so it ought to calm down and stop sending so many requests.
Even the humble 404 Not Found has specific handling; caches will often perform so-called
“negative caching” (i.e., assigning heuristic freshness lifetime to it), and automated agents like
Web crawlers will use it as a signal that the resource isn’t there any more.
These specific effects square with the generic semantics outlined above because they’re usually handled by HTTP-generic software; i.e., they’re not specific to your application, but instead by the generic HTTP “stack,” such as caches, clients and intermediaries, maybe Web spiders. Carrying information about the general state of a resource (“it’s here”, “it’s not here”, “it’s over there”) is about as application-specific as they get.
These are the things that you should be thinking about when considering what HTTP status code you should use, not how it maps into application-specific semantics. In particular, if the intended handling of the response is the same as an existing status code, use it.
A small number of status codes aren’t typically handled by the HTTP layer automatically. For
example, if your application uses 202 Accepted to indicate that a request has been queued but you
don’t know the outcome yet, it’s worth mentioning that in your documentation.
Even here, the specific effect – needing to determine the success of the request by some means other than the status code – is already defined, so don’t redefine the status code’s semantics; instead, see below.
Beyond that, it’s a mistake to try to map each part of your application “deeply” into HTTP status
codes; in most cases the level of granularity you want to be aiming for is much coarser. When in
doubt, it’s OK to use the generic status codes 200 OK, 400 Bad Request and 500 Internal
Service Error when there isn’t a better fit.
Refining the Semantics of a Status Code
The right place to put application-specific semantics is in the body’s format. In most cases, you won’t need to specify the status code that is used with it; “successful response” is probably enough. You might also create some HTTP headers to go along with it; doing that’s out of scope here.
For errors (4xx and 5xx), you can use this format if you
don’t want to create your own.
The exception to this is when you’re building a generic HTTP extension that leverages an existing
status code; for example, a new authentication scheme. If you’re doing that, pay attention to what
the status code’s specification says, as well as any existing headers that you want to use; you
need to be compatible with both; e.g., saying “a Location header on a 511 Network Authentication
Required response means…” doesn’t work unless you update the specification for 511.
So You Want To Create a HTTP Status Code
If after reading all of that, you still think you need a new status code – it’s something that’s potentially applicable to all resources, it has specific effect on HTTP implementations, there isn’t an existing status code that has that effect, and you’re willing to go through the process, take a look at these guidelines.
Thanks to Roy Fielding for his input.

