How to Think About HTTP Status Codes
Thursday, 11 May 2017
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
409
or400
? Or is500
better? My colleague just wants to use200
for 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
foo
link relation type have representations available in the following formats:application/foo+json
andapplication/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/example
format to the resource identified with thefoo
link 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.