Best practices for API versioning? [closed]
Asked Answered
G

7

876

Are there any known how-tos or best practices for web service REST API versioning?

I have noticed that AWS does versioning by the URL of the endpoint. Is this the only way or are there other ways to accomplish the same goal? If there are multiple ways, what are the merits of each way?

Griswold answered 23/12, 2008 at 15:32 Comment(0)
P
682

This is a good and a tricky question. The topic of URI design is at the same time the most prominent part of a REST API and, therefore, a potentially long-term commitment towards the users of that API.

Since evolution of an application and, to a lesser extent, its API is a fact of life and that it's even similar to the evolution of a seemingly complex product like a programming language, the URI design should have less natural constraints and it should be preserved over time. The longer the application's and API's lifespan, the greater the commitment to the users of the application and API.

On the other hand, another fact of life is that it is hard to foresee all the resources and their aspects that would be consumed through the API. Luckily, it is not necessary to design the entire API which will be used until Apocalypse. It is sufficient to correctly define all the resource end-points and the addressing scheme of every resource and resource instance.

Over time you may need to add new resources and new attributes to each particular resource, but the method that API users follow to access a particular resources should not change once a resource addressing scheme becomes public and therefore final.

This method applies to HTTP verb semantics (e.g. PUT should always update/replace) and HTTP status codes that are supported in earlier API versions (they should continue to work so that API clients that have worked without human intervention should be able to continue to work like that).

Furthermore, since embedding of API version into the URI would disrupt the concept of hypermedia as the engine of application state (stated in Roy T. Fieldings PhD dissertation) by having a resource address/URI that would change over time, I would conclude that API versions should not be kept in resource URIs for a long time meaning that resource URIs that API users can depend on should be permalinks.

Sure, it is possible to embed API version in base URI but only for reasonable and restricted uses like debugging a API client that works with the the new API version. Such versioned APIs should be time-limited and available to limited groups of API users (like during closed betas) only. Otherwise, you commit yourself where you shouldn't.

A couple of thoughts regarding maintenance of API versions that have expiration date on them. All programming platforms/languages commonly used to implement web services (Java, .NET, PHP, Perl, Rails, etc.) allow easy binding of web service end-point(s) to a base URI. This way it's easy to gather and keep a collection of files/classes/methods separate across different API versions.

From the API users POV, it's also easier to work with and bind to a particular API version when it's this obvious but only for limited time, i.e. during development.

From the API maintainer's POV, it's easier to maintain different API versions in parallel by using source control systems that predominantly work on files as the smallest unit of (source code) versioning.

However, with API versions clearly visible in URI there's a caveat: one might also object this approach since API history becomes visible/aparent in the URI design and therefore is prone to changes over time which goes against the guidelines of REST. I agree!

The way to go around this reasonable objection, is to implement the latest API version under versionless API base URI. In this case, API client developers can choose to either:

  • develop against the latest one (committing themselves to maintain the application protecting it from eventual API changes that might break their badly designed API client).

  • bind to a specific version of the API (which becomes apparent) but only for a limited time

For example, if API v3.0 is the latest API version, the following two should be aliases (i.e. behave identically to all API requests):

http://shonzilla/api/customers/1234
http://shonzilla/api/v3.0/customers/1234
http://shonzilla/api/v3/customers/1234

In addition, API clients that still try to point to the old API should be informed to use the latest previous API version, if the API version they're using is obsolete or not supported anymore. So accessing any of the obsolete URIs like these:

http://shonzilla/api/v2.2/customers/1234
http://shonzilla/api/v2.0/customers/1234
http://shonzilla/api/v2/customers/1234
http://shonzilla/api/v1.1/customers/1234
http://shonzilla/api/v1/customers/1234

should return any of the 30x HTTP status codes that indicate redirection that are used in conjunction with Location HTTP header that redirects to the appropriate version of resource URI which remain to be this one:

http://shonzilla/api/customers/1234

There are at least two redirection HTTP status codes that are appropriate for API versioning scenarios:

  • 301 Moved permanently indicating that the resource with a requested URI is moved permanently to another URI (which should be a resource instance permalink that does not contain API version info). This status code can be used to indicate an obsolete/unsupported API version, informing API client that a versioned resource URI been replaced by a resource permalink.

  • 302 Found indicating that the requested resource temporarily is located at another location, while requested URI may still supported. This status code may be useful when the version-less URIs are temporarily unavailable and that a request should be repeated using the redirection address (e.g. pointing to the URI with APi version embedded) and we want to tell clients to keep using it (i.e. the permalinks).

  • other scenarios can be found in Redirection 3xx chapter of HTTP 1.1 specification

Peper answered 29/12, 2008 at 20:24 Comment(14)
Using a version number in the URL should not be considered a bad practice when the underlying implementation changes. "When the interface to a service changes in a non-backwards-compatible way, in reality an entirely new service has been created...From the client's perspective, a service is no more than an interface and some non-functional qualities...if the interface to a service changes in a non-backwards-compatible way, it no longer represents an instance of the original service, but is rather a completely new service." ibm.com/developerworks/webservices/library/ws-versionOrlosky
Do you have any thoughts on adding a header with the version number so it can be checked by clients or developers ?Dinesh
Theoretically, that's a good idea unless the requests are either passing through some proxy servers or the API server is behind a CDN (like Akamai) as those could filter out some HTTP headers. Using a standard header ("smuggling" the API version, e.g. Server: SomeAPI/2.1) instead of a custom one (e.g. X-API-Version: 2.1) is a good idea. At any rate, you'll need to verify this with your provider and ask for some sort of long-term commitment before forcing your clients to use the header.Peper
See also the use of an Accept header to indicate the version that the client expects: blog.steveklabnik.com/2011/07/03/…Carpospore
For the last part: I would say that an API that is obsolete and not supported anymore should return 410 Gone, as a redirect might indicate that the new location is compatible when it isn't. If the API is merely obsolete but still exists, a Warning HTTP Header on the Response might be an option.Milled
I strongly disagree that putting a version number in the URI would disrupt HATEOAS. In fact, if you have properly implemented HATEOAS then, by definition, adding versioning to your URI will not matter...the clients are wholly unaffected.Lamaism
How do you deal with clients that are already using the stable URL like shonzilla/api/customers/1234 and you want to upgrade to a new version? how can you force them add the V2 (the old one) to the URL?Featherveined
There are scenarios where API versioning on the URL is a MUST. If your API is to be accessed from a JavaScript client executing in a browser then it's more than probable that the most of browsers of your user base do not support CORS and will be using JSONP to access your API.JSONP only supports GET method and you can't manipulate headers. So if you are offering this flavor your API versioning strategy should focus on the URL.Following
HATEOAS doesn't specify anything about versioning resources. I think semantically, yes, versioning in the Accept header makes more sense, but in a practical sense, I've always found it easier to deal with API's (browsing, using curl etc.) that had versions in the URI. So, semantics be damned. 3 Good examples of popular REST API's (IMHO) developer.github.com/v3 twilio.com/docs/api/rest stripe.com/docs/apiJoel
Replacing the API versioning with a date in unix time or otherwise is also helpful.Chibcha
Please don't put redirect on a REST API - it's very painful to handle them as a browser will automatically oblige them even if they results in cross domain errors.Trustbuster
Instead of shonzilla/api/customers/1234 i would use shonzilla/api/latest/customers/1234 Just to make it clear that it's probably going to break sometimes with their code. People are more influenced to use shonzilla/api/3/customers/1234 . The rest, i agree withZinkenite
In my opinion, to be restful versions should be considered at the level of resources, late and not early in the url. The concept of API is just the context that holds the resources. You can have 2 versions of one resource but only one of others, also different versions of a resource are in practical terms two different resources as they can have different shapes, you need to know that you are working with customer v2 and not v1. In the url level adding the version as an extension of the resource helps you with that: shonzilla/api/customers.v2/1234Austroasiatic
If a new version creates a new uri then it will force the clients to update their code as well. How about if no version mentioned then return the default first version? For clients who want to spend time and effort on a new version for their clients they can specify the new version either thru the Accept header or by specifying a new uri. This wont break the existing clients who havn't coded their clients to a particular uri version.Underarm
A
273

The URL should NOT contain the versions. The version has nothing to do with "idea" of the resource you are requesting. You should try to think of the URL as being a path to the concept you would like - not how you want the item returned. The version dictates the representation of the object, not the concept of the object. As other posters have said, you should be specifying the format (including version) in the request header.

If you look at the full HTTP request for the URLs which have versions, it looks like this:

(BAD WAY TO DO IT):

http://company.com/api/v3.0/customer/123
====>
GET v3.0/customer/123 HTTP/1.1
Accept: application/xml

<====
HTTP/1.1 200 OK
Content-Type: application/xml
<customer version="3.0">
  <name>Neil Armstrong</name>
</customer>

The header contains the line which contains the representation you are asking for ("Accept: application/xml"). That is where the version should go. Everyone seems to gloss over the fact that you may want the same thing in different formats and that the client should be able ask for what it wants. In the above example, the client is asking for ANY XML representation of the resource - not really the true representation of what it wants. The server could, in theory, return something completely unrelated to the request as long as it was XML and it would have to be parsed to realize it is wrong.

A better way is:

(GOOD WAY TO DO IT)

http://company.com/api/customer/123
===>
GET /customer/123 HTTP/1.1
Accept: application/vnd.company.myapp.customer-v3+xml

<===
HTTP/1.1 200 OK
Content-Type: application/vnd.company.myapp-v3+xml
<customer>
  <name>Neil Armstrong</name>
</customer>

Further, lets say the clients think the XML is too verbose and now they want JSON instead. In the other examples you would have to have a new URL for the same customer, so you would end up with:

(BAD)
http://company.com/api/JSONv3.0/customers/123
  or
http://company.com/api/v3.0/customers/123?format="JSON"

(or something similar). When in fact, every HTTP requests contains the format you are looking for:

(GOOD WAY TO DO IT)
===>
GET /customer/123 HTTP/1.1
Accept: application/vnd.company.myapp.customer-v3+json

<===
HTTP/1.1 200 OK
Content-Type: application/vnd.company.myapp-v3+json

{"customer":
  {"name":"Neil Armstrong"}
}

Using this method, you have much more freedom in design and are actually adhering to the original idea of REST. You can change versions without disrupting clients, or incrementally change clients as the APIs are changed. If you choose to stop supporting a representation, you can respond to the requests with HTTP status code or custom codes. The client can also verify the response is in the correct format, and validate the XML.

There are many other advantages and I discuss some of them here on my blog: http://thereisnorightway.blogspot.com/2011/02/versioning-and-types-in-resthttp-api.html

One last example to show how putting the version in the URL is bad. Lets say you want some piece of information inside the object, and you have versioned your various objects (customers are v3.0, orders are v2.0, and shipto object is v4.2). Here is the nasty URL you must supply in the client:

(Another reason why version in the URL sucks)
http://company.com/api/v3.0/customer/123/v2.0/orders/4321/
Alena answered 19/7, 2011 at 16:8 Comment(15)
Handling independent data contract version and service contract versions in Accept header seems a messy as much as it is messy in the URL. Are there any other options ? Also if I have multiple endpoints (soap, rest), should this also be indicated in Accepts and let the routing service at the server end decide the direction to the correct endpoint OR is it acceptable to have the endpoint coded in the URL ?Tails
company.com/api/v3.0/customer/123/v2.0/orders/4321 drove your explanation home. Good example!Gamopetalous
I think this depends on the use-case. Working on internal company systems, Webservice versioning is required so that other internal systems (clients of the webservice) are given time to upgrade to the newest version. So the old version can be supported for a set time period (say only 1 month) and then turned off. In this case there doesn't seem to be an issue for me in having the version in the URL.Kira
I can't agree with this, at least to the point of your last reason. This seems to be saying the different parts of the URI have different versions. But that's not the point of an API version. The point is to have ONE version for the ENTIRE resource. If you change versions, it's a different API resource. That's why it doesn't make sense to see company.com/api/v3.0/customer/123/v2.0/orders/4321 but rather company.com/api/v3.0/customer/123/orders/4321 You're not versioning any given part of the resource, you're versioning the resource as a whole.Sacrifice
in 'Accept: application/vnd.company.myapp.customer-v3+json' what does 'vnd' stand for? Everyone seems to be using it, but I cant find a description of it anywhere.Ketron
I figured it out ... its vmd == Vendor MIME TypeKetron
Semmantically using version number in header seems better. But its far much more practical using the URL: less error prone, best debuged, readily seen by developers, easily modifiable in rest testing clients.Following
What fool4jesus said. Versioning the URI isn't versioning the response object (which you could version independently if you chose - using headers). Versioning the URI is versioning the whole URI structure.Ceratodus
I think the BAD/GOOD over simplifies the question. API stands for "Application programming interface" and versioning interfaces seems to be a very good idea. APIs are not really just about serving resources. What needs to be seperated is that some people are talking about interfaces and other people are talking about resources. If you look at the google maps api closely in the network tab, you will see that they include the api version number in the url. For example: maps.google.com/maps/api/jsv2 during authentication. The jsv2 is the api number.Aletaaletha
I agree, with three corrections: 1. You must version the entire URI (so your last example doesn't make sense). 2. You should use x- instead of vnd. because the latter must be registered with IANA. 3. You should separate the content-type and version by using Content-Type: x-com.company.myapp+json; Version=5.Bookstack
@Gili: Actually, you should no longer use -x as it's deprecated by RFC6648.Wreckfish
Why not version the host name then?Revitalize
Another reason that I can think of to do it in the url is so you can use more REST frameworks Routing functions off the "shelf" whereas if you use Headers for Routing you are going to have to override default routing implementations with header routing on top of url routing. With gets back to others points. Api's are semantic, and if the meaning changes it is now a different resource and should be routed as such.Benefit
So combining responses from Gili and @JonathanW there's no valid user defined mime type unless you register it via IANA? This looks like a classic case of high level enterprise thinking being generalised to low level solutions inappropriately.Ommiad
The accepted answer addresses this issue but gets the part this answer misses; for backwards compatibility you must maintain aliases for the old versions, nothing in the points made here logically precludes that practice. Best of both worlds.Beestings
S
98

We found it practical and useful to put the version in the URL. It makes it easy to tell what you're using at a glance. We do alias /foo to /foo/(latest versions) for ease of use, shorter / cleaner URLs, etc, as the accepted answer suggests.

Keeping backwards compatibility forever is often cost-prohibitive and/or very difficult. We prefer to give advanced notice of deprecation, redirects like suggested here, docs, and other mechanisms.

Spoilt answered 4/1, 2011 at 20:57 Comment(1)
The accepted answer may be the correct and most pure. However, for the developer and day to day user of API's this is surely the easiest to use and set up. The most pragmatic approach. As indicated by other Google and Amazon also use this approach.Kalisz
H
46

I agree that versioning the resource representation better follows the REST approach...but, one big problem with custom MIME types (or MIME types that append a version parameter) is the poor support to write to Accept and Content-Type headers in HTML and JavaScript.

For example, it is not possible IMO to POST with the following headers in HTML5 forms, in order to create a resource:

Accept: application/vnd.company.myapp-v3+json
Content-Type: application/vnd.company.myapp-v3+json 

This is because the HTML5 enctype attribute is an enumeration, therefore anything other than the usual application/x-www-formurlencoded, multipart/form-data and text/plain are invalid.

...nor am I sure it is supported across all browsers in HTML4 (which has a more lax encytpe attribute, but would be a browser implementation issue as to whether the MIME type was forwarded)

Because of this I now feel the most appropriate way to version is via the URI, but I accept that it is not the 'correct' way.

Hagler answered 13/10, 2011 at 10:51 Comment(6)
Assuming the route where the versioning was defined in the headers, one could say that HTML forms that use native form submission would always use the latest version of the API since they would not be passing the specific version they want to adhere to. However, XHR requests do indeed allow you to change the accepts and read the content-type headers. So basic forms are really the only problem.Atonement
I'm not sure I agree that URI is most appropriate, but the fact that Content-Type doesn't work with forms is very important indeed.Tolan
@Kyle, i saw a blog somewhere said that if you don't specific a version in the request header, it is best to return with the first api version not the latest one for the best compatiable.Koloski
That actually makes a lot of sense to me now that I think about.Atonement
@KyleHayes don't forget iframes, video/embed and other "src/href" type tags.Cuman
Content-Type doesn't work with forms, but there's always javascript and multipart/form-data to let you upload whatever you want. (by turning your form into whatever format you want (even potentially something binary) and then wrapping it in multipart/form-data like a file upload, in case that wasn't clear)Genniegennifer
T
21

Put your version in the URI. One version of an API will not always support the types from another, so the argument that resources are merely migrated from one version to another is just plain wrong. It's not the same as switching format from XML to JSON. The types may not exist, or they may have changed semantically.

Versions are part of the resource address. You're routing from one API to another. It's not RESTful to hide addressing in the header.

Tranship answered 5/6, 2012 at 15:9 Comment(0)
G
13

There are a few places you can do versioning in a REST API:

  1. As noted, in the URI. This can be tractable and even esthetically pleasing if redirects and the like are used well.

  2. In the Accepts: header, so the version is in the filetype. Like 'mp3' vs 'mp4'. This will also work, though IMO it works a bit less nicely than...

  3. In the resource itself. Many file formats have their version numbers embedded in them, typically in the header; this allows newer software to 'just work' by understanding all existing versions of the filetype while older software can punt if an unsupported (newer) version is specified. In the context of a REST API, it means that your URIs never have to change, just your response to the particular version of data you were handed.

I can see reasons to use all three approaches:

  1. if you like doing 'clean sweep' new APIs, or for major version changes where you want such an approach.
  2. if you want the client to know before it does a PUT/POST whether it's going to work or not.
  3. if it's okay if the client has to do its PUT/POST to find out if it's going to work.
Genniegennifer answered 24/10, 2011 at 16:39 Comment(0)
T
8

Versioning your REST API is analogous to the versioning of any other API. Minor changes can be done in place, major changes might require a whole new API. The easiest for you is to start from scratch every time, which is when putting the version in the URL makes most sense. If you want to make life easier for the client you try to maintain backwards compatibility, which you can do with deprecation (permanent redirect), resources in several versions etc. This is more fiddly and requires more effort. But it's also what REST encourages in "Cool URIs don't change".

In the end it's just like any other API design. Weigh effort against client convenience. Consider adopting semantic versioning for your API, which makes it clear for your clients how backwards compatible your new version is.

Terbecki answered 6/3, 2014 at 7:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.