Due to the nature of our products they are very heavily API-based in both producing and consuming data. In the process of working on them I’ve not only gotten the chance to deal with consuming a very large amount of external APIs, of very varying quality and enjoyability, but I’ve also had the chance to write a lot.
One of the important design decisions I made when building RocketPants was related to versioning – I believe it’s important in practical terms to version an API (within reason) but I strongly disagree with using custom content types to do the versioning for most things.
But why?
My argument for this boils down to a couple of small, key factors:
1. Discoverability
This predominately applies to public facing APIs but I believe it’s still important for general APIs as well. Discoverability of an API in this specific case refers to the ability to use a browser just like any other API client. AKA, I should be able to take a URL, type it into my browser bar, and see the body of the response. With the help of a good browser plugin or two (e.g. JSONView in Chrome), I may even get a structured and highlighted view of the data.
Doing headers is typically an extra step in most command line clients / http libraries (albeit not a hard one) but in most browsers it’s either quite a task (e.g. requiring an extra plugin) or just not possible.
I believe Echonest is a fantastic example of this – their API docs include links to example requests that work as in your browser, making it super easy to get started and to play around with the data, despite being versioned.
2. The Difference In Changes
My second issue refers to changes in side effects – specifically, in non-GET requests. Whilst the Accept-based approach still works as expected with HTTP caching and the like (since they’re used for normal content negotiation), but when different controllers implement different versions of the API call (e.g. creating something in version two has a different result than version one), your API will not function as expected.
Hence, the Accept based approach works for just different representations of the data, but when you change the logic, it starts to cause trouble – using a different endpoint completely (as signified by a different path) means it’s functionally a different resource and hence you expect those actions to change.
An aside: v1 versus 1.
This is a small one – I consciously chose to use /1/ in the generated paths for RocketPants over /v1/ for a number of reasons after talking to other developers. These primarily were based on the fact that A) it’s easier to scan (although only marginally), and B_ .to_i in ruby (and similar number conversions) work as expected with no surprises – the v adds no extra information but potentially offers confusion.
Conclusion
Ultimately, I believe you should choose the solution that feels right to you. My personal experience consuming and building APIs has led me to prefer this approach (and to be honest, if it was an ideal world, you’d probably eschew versions in favour of backwards compatible APIs with a decent deprecation time period), but for most cases I believe it’s more pragmatic to use versioning in the URL than it is to use header / Accept based versioning for the reasons above.
