When developing an API, one of the first critical decisions every developer must face is that of Content Type. In this day and age most APIs return one or both of JSON or XML.
Some APIs get away with one, and that’s fine, but to improve the UX of your API, you should allow the requestor to determine what data type is best for them. If you’ve read the post on API Content Negotiation, you’ll know the best way to approach content negotiation is to follow the standards.
However, as is the case with many standards, it can be difficult to read the Header Definitions standard. It’s easy to understand the difference between “Accept: application/json;” and “Accept: application/xml;” – But are you sure your service will respond correctly if to a request specifying “Accept: application/xml;q=0.5, application/json”? It’s a perfectly valid request, and should be handled properly.
Parsing the Accept Header
While the spec can be quite daunting to read through, fortunately the actual parsing is fairly straightforward and intuitive. The key to understanding the how the Accept header specifies which content-type is actually desired boils down to three points, in order of how they should be looked at while parsing:
- Quality (q-param)
Specificity indicates that if a server can supply either of two given formats in an Accept header, it will supply the more specific one. This will take into account parameters (other than the special q-param) as well – rarely used, but valid; for example “text/html;level=1” may mean something to a server. If a server were asked to respond to “text/*, text/html, text/html;level=1”, it would respond with level 1 HTML first if possible, then any other HTML, then any textual information.
Quality, or the q-param, is a special parameter that can indicate a preferential order determined by a quality scaling parameter. In theory, the quality parameter could be used with some sort of quality determination of the available content types. If the application could only serve up a 50% quality audio/mp4 file but a 100% quality ogg file, and the Accept header specified that it wanted audio/mp4 at a 1.0 scale but ogg at 0.9 scale (“Accept: audio/mp4, audio/ogg;q=0.9”), the ogg file would be served as its scaled quality (90%) beats the inferior mp4 scaled quality (still 50%). In practice, however, the q-param is more often simply used to override the order as few services actually measure the quality of various content types.
Order, finally, is the most common scheme for determining what content-type the requestor desires. The given two accept types with the same specificity and scaled quality factor, the server should send the first type as the response.
Utilizing the Accept Header
Why should you use the Accept header? So many APIs can simply get by with a string comparison against “application/json” or “application/xml”, why is it important?
In a trivial example, an API is simply an agreement between the provider and users that when a certain type of request comes in, the API will respond a certain way. However, some otherwise valid requests – for example, “complicated” Accept headers – may be rejected, or worse, respond in a poorly defined manner if you stray outside the agreement. Determining and defining every possible accept header can become burdensome if you’re serving up multiple media types.
When you agree to follow the standards, a lot of the heavy lifting is done for you. One of the keys to a beautiful user experience is to behave in an intuitive manner rather than assume the user knows your rules. So next time you’re working on an API, remember the Accept header!
If you enjoy this article please upvote it on Hacker News and reddit.
Bruno mentions the importance of providing a reasonable default in his content negotiation post, http://apiux.com/2013/05/07/api-content-negotiation/, which I think is important. What are your thoughts on “bending” RFC spec recommendations and responding with a default representation when an accept header is present, but you cannot respond with on of the appropriate representations?
For example, if for whatever reason the request contains application/hal+json or text/html and you support only application/json and application/xml. From the RFC spec: “If an Accept header field is present, and if the server cannot send a response which is acceptable according to the combined Accept field value, then the server SHOULD send a 406 (not acceptable) response.”
Given that many APIs are used by developers that can be very new to the API world, they may not be using accept headers properly or understand them. For that reason a good strategy may be to parse and honor the header as best as you can (per most of this post), but have that fallback that returns content rather than a 406. In other words, be liberal with the SHOULD in the RFC spec if you it makes life easier for the less skilled developer, while still serving the developer that is embracing the accept header.
I definitely agree that in the absence of an Accept header, a default content type should be determined by the server and sent – after all, technically the requestor said they accept anything.
However, I’d be wary of sending a 200 response with a different content type if I specified something in the Accept header your service cannot provide. While it may help an inexperienced developer get ANY response back from your service, it doesn’t really help them submit the correct request unless you have some sort of “notes” field stating “We couldn’t supply any of your specified content types, here is our default.” I’d rather see a 406 response with an appropriate human-readable message. Teach a man to fish, and all that.
Cool post! Just wanted to mention a very useful project I use often for parsing Accept headers and matching them to a set of mime types: mimeparse (https://code.google.com/p/mimeparse/). It offers libraries for most popular languages.
Thanks for sharing! That does look like a promising library. If only more API frameworks adopted it, we’d be in much better position in terms of introducing new developers to compliant APIs. A big reason not many people properly parse the accept header is it’s so darn obscure.
Pingback: The Accept Header: A Quick Primer | nodeJS and ...