Content Negotiation

A key feature of hypermedia is the ability for the client to request the response in a particular format and for the server to respond to that – and several other – formats as necessary. For example, a client could request the response come back formatted as XML, typically by specifying that in the “Accepts” header by using the standard XML MIME-type string “text/xml”. If the server doesn’t support XML as a response format, it should send back an HTTP error code “406 Not Acceptable”.

Simply specifying XML as a return format, however, doesn’t really do much to help the client figure out what the XML contains. The XML spec overcomes this by adding a DTD element in the response body that links to a document that helps the client interpret the response. Other response types like JSON do not currently have a well- specified way to do this and rely, instead, on either the Accepts and Content-Type headers or in using a file extension to allow the client developer to specify the preferred response format.

Using the content headers allows for more fine-grained control over the negotiation process, allowing the client to specify not just the high-level format – i.e. JSON or XML – but the actual profile that governs the layout, including the version.

This specification may follow an existing or emerging standard, such as can be found on Schema.org, or may be specified by the API producer. This allows for a great deal of flexibility for the API producer to define responses that best match the structure and needs of their API.

For example, an e-commerce API provider may choose to create their own profile for a product in their catalog which best mirrors how a product is represented elsewhere in their application. This profile will likely be used where a product is referenced in a system, so the application developers should standardize internally on a specific profile, providing it a custom MIME type:

application/json;vnd.example.products.json+v1

Note the use both of the representational format (“json”) and the version (“v1”). This form of content negotiation allows versioning at the individual resource level, which can help keep the codebase that drives the application clean and consistent while allowing for increased flexibility to iterate. If the client application sets the Accept header to “*/*” or leaves it out completely, the API may return its preferred representational format, which, in the above case, will likely be the most recent version of the custom Product representation.

5 thoughts on “Content Negotiation”

  1. So I’ve seen other formats for `Accept:` and `Content-Type:` headers that look more like `vnd.example.products+json;v=1`. From the W3C spec, I can see that this is using an `accept-param` for the version, but that only looks like it’s valid for `Accept:`. And it looks like the `+json` is conventional only?

    Am I over-analyzing, or are there semantic differences here?

  2. So I’ve seen other formats for Accept: and Content-Type: headers that look more like vnd.example.products+json;v=1. From the W3C spec, I can see that this is using an accept-param for the version, but that only looks like it’s valid for Accept:. And it looks like the +json (or your +v1) is conventional only?

    Am I over-analyzing, or are there useful semantic differences here?

    1. Hmm. Just found this in RFC-6838, describing use of the ‘+’ suffix, and this describing +json and others. The former would seem to indicate that using unregistered suffixes may not be a good idea. If standards matter, anyway. 🙂

  3. Agree. Versioning should refer to responses, not endpoint structure. A change to an endpoint is a re-design of the business model all together (either because the business actually changed or the first iteration of the URIs were not correct and are being re-deployed). If a URI is aligned with the business model and goals of the API, then that URI of a resource should not change….however, the representation of a resource certainly can change as the business introduces new attributes, translations, etc.. Having the version identified as an attribute of the expected content format is spot on.

  4. Have you considered using profile links instead of vendor derived media types?

    https://tools.ietf.org/html/rfc6906

    Cons to vendor-derived media types:
    (assuming your derived media types aren’t already late-stage RFC).

    Media type extensions have potential for namespace collisions. This is really important in large companies with acquisition scenarios, with the assumption that all tech trends towards services.

    Encoding the expected application domain type into every request creates strong coupling between the specific client version and the application semantics. It’s especially a problem with mobile and native clients where they don’t get an upgraded client simply by hitting the refresh button on their browser.

    The same cons for encoding application domain types in the accept header also apply to encoding any kind of resource or API versions into the request. Passing the client version number (if you’re one of a limited set of official clients) is probably fine, and even increases flexibility for breaking changes, but that doesn’t make sense in the accept header.

    Putting fixed versioning into the request means that the server is less likely to be able to determine the intention meant behind the version id. There’s no such thing as `>=v2.5` or `~v3` in the scheme you’ve described, and those are pretty important constructs once you start having a myriad of clients that you don’t control. See package managers (e.g. pip) for examples of this in action.

Leave a Reply

Your email address will not be published. Required fields are marked *