Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Funny thing: I've been thinking a bit about API versioning quite a bit lately, and the best solution I've come up with is the ONE thing not at all covered in this: put an `api-version` header into the request. I've seen both of the schemes recommended here, and I like neither very much. So what's wrong with my (header) solution?


Neither of the schemes mentioned here are good as they change the URI for a resource, which breaks all sorts of things. Could you imagine if every website wanting to switch from HTML 4 to HTML 5 had to update their URIs from https://www.example.com/HTMLv4/contact.html to https://www.example.com/HTMLv5/contact.html? It would be chaos.

For instance, if client application A talks to the service using version 1.0 of the API and client application B talks to the service using version 2.0 of the API, then those client applications can't interoperate because they are seeing two different sets of resources.

Your solution isn't far off the approach recommended by everybody who rejects the different URI approach. You don't need an API version header. When your representation format for a resource changes, provide a `version` parameter for the media type. For example: `Content-Type: application/vnd.example.foo+json;version=2`.

This is exactly how HTTP and the `Content-Type` header are supposed to work – if your representation for a resource changes, you don't change the URI, you change the media type.


The "?api-version" approach in the doc is there for exactly the reason you call out. Azure uses this approach.

By omitting "/v1.0" from the path it makes the actual URL's far more durable, as they're not version dependent. There are pros and cons to this, as there is with everything. In Azure's case it's great, as you can then use URLs as resource identifiers and put ACL's on them. If they were versioned via the path, this wouldn't work.

Other services, such as XBox, and (very old) Azure, put the API version in a custom header (x-ms-version, for example). This proved to be extremely problematic, and every team that does this had stories of all the problems it caused and then spends time removing it.

I've never seen a detailed proposal for putting API version in the Content-Type header, and will go look.

The content-type header does seem to have many of the same drawbacks as putting the version in a header (any header). For example, people could not make queries from a browser just by entering a URL as there is no way to specify a header. Nor could someone bookmark an API GET request, which is also quite handy.

Ease of use is huge, and I am in the camp that a version in the URL (path or parameter) is much easier in every way than a header. Every with curl, it's easier (I can never remember how to specify headers w/o the man page).


One slight downside of a custom header to specify a version is that OPTIONS calls don't include the value of the custom header, so your pre-flight gets to say yes or no without knowing what version is being called. Putting API version in the URL or query string fixes this.

As for bookmarking a GET request, this is /almost/ doable even following the MSFT guidelines since it says that service implementors MUST also provide a query-string alternative to required custom headers (section 7.8), and that service implementors MUST support explicit versioning. The only fly in this ointment is that the versioning part of the spec only offers two places of specifying the version - URL and query string, and seems to leaves no room for other options.

Personally, I think the Accept header flavor with custom MIME types is the most flexible for minor (backwards compatible) version - see GitHub's API for an example - but it certainly isn't the most simple to work with, neither in client libraries, Curl/WGet command-line use or API consumer tools (almost none let you fiddle with Accept headers). Since API ease of use is such a big factor for adoption, passing versions in the URL or the query string is most likely an OK lowest common denominator for APIs that seek the widest possible reach.


Interestingly enough, putting it in Content-Type is also invalid unless the mime type allows it. (Which, of course, is trivial if you're defining your own.)

Specifically, RFC 2616 specifies that clients SHOULD only specify parameters which are valid for the mime type [0]. This can get more restrictive; for instance, JSON-API (application/vnd.api+json) states that any parameters MUST result in 415 UNSUPPORTED MEDIA TYPE [1].

[0] https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3....

[1] http://jsonapi.org/format/


Troy Hunt has an article where he shows three different ways to declare a version, including the header method.

https://www.troyhunt.com/your-api-versioning-is-wrong-which-...


The API version header has a few problems.

1. Older proxies used to remove/strip headers they don't understand. 2. Frameworks and libraries don't always give access to n non-standard headers, meaning they just can't used. 3. It's harder for humans to look at a request and see what's going on.


Proxies stripping headers isn't a problem if you use HTTPS or HTTP 2. Additionally, the proper place for this is the Content-Type header, which is a standard header any proxy would understand.

I can't say I've come across a framework or a library that makes it impossible to access a non-standard header, and if there are any, that would be a pretty glaring bug. Nevertheless, the proper place for this information is in the Content-Type header, which is a standard header.

In what way is it hard for a human to look at a request to see what's going on? The information is right there.


Why do you think HTTPS would fix this?

For the TLS case, there are enough MITM proxies, both in the Enterprise and elsewhere, to make this a real concern. There are also API Aggrigators which are effectively MITM and need to be taught to "play-well" with custom headers.

Certainly in the consumer case HTTPS would keep a majority of consumer facing ISPs from header-stripping, but there is still a pretty big hole.


An API version in an URI is fine. But better would be to use a new domain, if the API brakes backward compatibility entirely.

If the API does not break the clients entirely, just have new resources for incompatible formats:

  /user
  /user2
For the first resource, you can just add a "X-Deprecated" header with a link to the deprecation information. If you already know the date when the resource is abandoned, you can also add a header that has a date of removal in it. If removed, you can either have /user serve the new format or have it serve a HTTP 303 to the new format.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: