How many HTTP status codes should your API use?

There are a lot of HTTP status codes. At the time of this writing, Wikipedia lists 75 different status codes, most of which you’ve probably never encountered. Many of us have heard of the tongue-in-cheek “418 I’m a teapot,” but very few are familiar with these:

  • 205 Reset Content
  • 300 Multiple Choices
  • 419 Authentication Timeout
  • 450 Blocked by Windows Parental Controls

Most API providers stick to a rather small set of status codes, which they list in their documentation. Facebook’s Graph API takes this to the extreme; it only returns a single status code: 200 (OK). A client can determine whether an error occurred by examining the JSON-encoded response body. The Twitter API documents 15 status codes. Our own Dropbox API lists 8 specific status codes for error conditions.

What’s the right approach? Is it better to use more status codes or fewer status codes?

The value of HTTP status codes

There are 16 status codes defined in RFC1945 (the HTTP 1.0 specification). These status codes were motivated by pragmatism. Web browsers are generic, in that they can be used to talk to any web server. That means both the web browser and web server need to conform to a known interface, and this interface includes a variety of error conditions. For example, web browsers need to know when to prompt a user for credentials, and that’s why we have status code 401 (Unauthorized).

Newer status codes serve similar purposes. For example, 206 (Partial Content) exists to let a browser know that its range retrieval request was actually fulfilled, so it should treat the content as such. (In contrast, a 200 status code would indicate that the server actually returned the full content.)

The value to a web browser of a standard set of status codes is that the browser can automatically—without any foreknowledge of the specific web server it’s talking to—take the right action.

Over the decades, this value has extended to things that aren’t exactly web browsers. For example, a caching proxy server uses conditional GET requests and relies on an understanding of status code 304 (Not Modified) to know when it’s safe to use the cached value.

Are API clients web browsers?

To get back to the titular question of this post, we need to think about what API clients do and do not have in common with web browsers.

Some parts of API clients are very similar to web browsers, in that they’re generic and have an understanding of common status codes. Here’s an illustration in Python:

>>> import requests
>>> requests.get('http://httpstat.us/302').status_code
200

The URL http://httpstat.us/302 actually returns a 302 (Found), with a Location header of http://httpstat.us. Just like a web browser, the requests library has an understanding of the 302 status code and knows how to act accordingly. In the exchange above, it followed the redirect, made a second HTTP request, and then returned the 200 OK it received from that request.

In other aspects, API clients are often not generic at all. A good Twilio library might understand error 21610 (“Message cannot be sent to the ‘To’ number because the customer has replied with STOP”), but there’s no such thing as a generic API client that understands that error. For these bespoke errors, most API providers (including Twilio) pick one HTTP status code—often 400 (Bad Request)—and use the response body to elaborate on the specific error encountered.

To the extent that API clients are generic and capable of responding to standard status codes, using those status codes provides value. To the extent that API clients are specifically designed to work with a single API, the use of particular status codes is a matter of taste.

What about the humans?

Speaking of taste, it’s important to remember that API design isn’t strictly about the practical implications on client and server software. The audience for an API is the developer who is going to consume it. Per the “principle of least astonishment,” developers will have an easier time learning and understanding an API if it follows the same conventions as other APIs they’re familiar with.

This is, of course, a moving target. There was a time when Rails-style plural URLs (/people and /person/123) were the style everyone emulated, but that convention no longer seems as common. Twitter ushered in an era of /foo.json and /foo.xml for a time, but it seems more fashionable these days to use content negotiation.

HTTP status codes are similar. Beyond a certain point, the use (or not) of specific status codes is a matter of personal preference and expectation. Keep your specific audience in mind and listen carefully to their feedback.

A pragmatic, minimalist approach

HTTP status codes are grouped into five numeric categories:

  • 1xx – informational
  • 2xx – successful
  • 3xx – redirection
  • 4xx – client error
  • 5xx – server error

Those categories are broadly understood by many types of clients. For example, popular HTTP libraries in a number of languages already treat a 4xx/5xx status as an error and throw an exception. API consumers will see immediate value from status codes organized this way.

Here are some common specific status codes that will provide immediate value when applicable. Note that each allows a generic client to take action on them:

  • 302 Found (and perhaps the other redirect codes) – Supporting this status code will let clients follow a redirect, which can be particularly useful when you need to reroute a request to another host.
  • 304 Not Modified – Supporting this status code, along with conditional GET requests (ETag/If-None-Match, Date/If-Modified-Since), will let clients and proxies cache responses. This is particularly important for JavaScript running in the browser, but other clients support this too, including OkHttp on Android and AFNetworking on iOS.
  • 429 Too Many Requests – Supporting this status code, along with the Retry-After header, will let clients perform automatic backoff in the case of rate-limiting.

Following this pragmatic approach, APIs should probably use at least 3 status codes (e.g. 200, 400, 500) and should augment with status codes that have specific, actionable meaning across multiple APIs. Beyond that, keep your particular developer audience in mind and try to meet their expectations.

In that spirit, we’d love to hear what you think. Dropbox currently uses about 10 different status codes (8 errors and 2 for success), but we’re planning to reduce that number in a future version of the API. Please share your thoughts here in the comments.