While preparing to create the next version of the API, I've been reading articles about versioning. Most of them have "best practices" in their titles which is odd, unless by best
they actually mean worst
These articles argue that APIs should be easy for developers to explore in a web browser, so API versions should be specified in the url, like https://api.site.com/v23/foo rather than being negotiated in the HTTP headers. How else could you explore the API through a web browser? But , wait , we have browser extensions for this. Also no end user needs to make direct old-school bookmarks of an API resource.
The actual problem is historical. Developers had to resort to JSONP to make cross-origin requests in old browsers, and you can't specify headers with JSONP. Nowadays we can watch this lamentable JSONP hack glide away into history while we use CORS. If you really have to support IE <10 you can actually encode headers over JSONP as a query parameter if the client and server cooperate. (I'm developing a Rack middleware and Angular interceptor to do this called jsonp-beastmode.)
Versioning in the URL completely ignores how the web already works. When I want /foo in Spanish I don't request /es/foo , I send an Accept-Language header. In fact many representational details are negotiable, like the charset, the encoding, and the mime type of a resource. We don't put them in the URL because their position is irrelevant. It gets absurd: /es/utf8/application-json/foo . Ultimately we are just requesting different representations of a single resource.
So what's the right way? I propose using the Accept header with type application/vnd.you.com+ format; version= n where format is e.g. json