mnot’s blog

Design depends largely on constraints.” — Charles Eames

Thursday, 11 May 2017

How to Think About HTTP Status Codes

Filed under: Standards HTTP

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 or 400? Or is 500 better? My colleague just wants to use 200 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:

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 and application/bar; you can update its state with PUT using application/foo+json, and PATCH it with application/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 the foo 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.

Creative Commons