REST API Versioning

There is a LOT of discussion about REST API versioning. I am not offering any alternatives to what is already available, simply synthesizing the various approaches taken. RESTful API version management is awkward. There is no standard approach. The most common technique of inserting a version number into the URL path throws REST purists into convulsions. The more academic, specification-friendly way makes usage less convenient and may not even work with some tooling. Before surveying the various options, lets briefly discuss why we would even care to do this in the first place.

Why Version Your API?

Like all good software, APIs evolve over time. Requirements change. Defects get fixed. New defacto standards become commonplace. Expect things to change. With that in mind, when an API does change, things tend to break. When you are the sole consumer of the APIs you produce, versioning may not be needed, especially when you upgrade client and server together. However, we’re talking about situations where you may not even be aware of all the clients using your API, even if it is only available internally to the enterprise.

API versioning is useful when:

  1. You maintain multiple versions of the API service in play. The client specifies what version to use.
  2. You maintain only the latest API service in play but have a client that can robustly handle an evolved response. Ideally non-breaking changes would alleviate the need to force client updates. Changes such as the introduction of additional data fields that can be safely ignored is a good example.  A client can test the response for the presence of additional data or alternate structure, or simply check a version field.

Many projects get into a trap where they launch without any thought about API versioning. Successive releases stress non-breaking API changes, but over time the client logic becomes more and more convoluted as it tries to figure out exactly what kind of payload it is receiving. The API also carries with it a lot of legacy cruft since removing it would cause clients to break.  Over successive releases the service, client and API devolves into a mess.

API Versioning is about future-proofing your service. Allow it to evolve without carrying forward legacy baggage. API Versioning shields your clients from change, preventing breakage.

Techniques for API Versioning

  1. URL Path
    1. Base (service) path – applies to all resources [note 1]
      https://mydomain.com/v1/myservice/myresource
      https://mydomain.com/v2/myservice/myresource
      
    2. Resource path – applies to specific resources [note 1]
      https://mydomain.com/myservice/v1/myresource
      https://mydomain.com/myservice/v2/myresource
      
  2. Subdomain – applies to all resources [note 1]. Becomes awkward for browser based clients constrained by the Same Origin Policy that need to access different versions of services hosted on the same host.
    https://v1.mydomain.com/myservice/myresource
    https://v2.mydomain.com/myservice/myresource
    
  3. API metadata. A standard endpoint such as /api or /swagger can be used to describe the entire service API to a client. This metadata can contain an API version number. Swagger provides this in info.version.  An out-of-band (OOB) API version request is something the client would do before interacting with the service so that it can configure itself to properly format the requests and handle the responses or fail gracefully if the API version is unsupported by the client.  However, this does mean that the version number applies to all resources. Since no version is supplied in any part of the service request or response there is no way to differentiate multiple versions of the same service.
    https://mydomain.com/swagger
    
  4. URL Parameters – applies to specific resources
    https://mydomain.com/myservice/myresource?version=v1
    https://mydomain.com/myservice/myresource?version=v2
    
  5. HTTP Header – this can be any custom header agreed upon by server and client to indicate version. Common choices are X-API-Version [note 2], Version [note 3] and Content-Version [note 4]. More recently the custom header Accept-Version has been popularized by frameworks such as Restify.   Be warned that intermediate proxies and caches may not carry forward custom headers or any headers for that matter.
    GET https://mydomain.com/myservice/myresource HTTP/1.1 
    
    X-API-Version: v1
    
    GET https://mydomain.com/myservice/myresource HTTP/1.1 
    
    X-API-Version: v2
    
  6. Content Negotiation – involves using the Accept [note 5] header to communicate the desired version the client requests from the service provider.  This technique is the most complex and nuanced approach to API versioning but (arguably) endeavours to uphold the HTTP specifications. However there are many different ways in which content negotiation can be implemented and not all of them would be considered standard-friendly.
      1. Use a custom MIME subtype to communicate a specific resource and version. The “x.” (experimental) tree denotes an unregistered subtype. The use of unregistered subtypes is discouraged by the spec [RFC 6838] [RFC 6648]. Furthermore, version information in the subtype name makes MIME weep.
        GET https://mydomain.com/myservice/myresource HTTP/1.1 
        
        Accept: application/x.mycompany.myservice.myresource.v1
      2. Add a registered suffix to your custom MIME type. All the same problems as above, but tooling at least knows the general data format.
        GET https://mydomain.com/myservice/myresource HTTP/1.1 
        
        Accept: application/x.mycompany.myservice.myresource.v1+json
      3. Using a registered MIME subtype. The “vnd.”(vendor) tree requires IANA registration. Likely not something you are going to do unless your software is a commercial product or insanely popular.
        GET https://mydomain.com/myservice/myresource HTTP/1.1 
        
        Accept: application/vnd.mycompany.myservice.myresource.v1+json

    The MIME type is really about communicating the desired format for the resource, whether that is JSON, XML or something else. New revisions of software should never require the introduction of a new MIME subtype (I’m looking at you GitHub).

    Accept: application/json

    Version information simply does not belong in the subtype. Custom subtypes can be used for that, but here is an alternative with MIME type parameters.

4. Use MIME type parameters, specifically “profile” to further describe the resource and “version” to communicate the revision of that description.

Accept: application/json; profile="http://mydomain.com/myservice/myresource-schema.json"; version=2

Technically the presence of parameters is defined in the subtype specification. The JSON subtype does not specify any parameters, though RFC 6906 does amend that.  In the case of a JSON MIME subtype, the “profile” could reference a JSON schema. This reference does not even need to be resolvable by the client and could simply act as a hint.

XML content conveniently allows meta-definition (DTD or schema) in the XML payload itself, but JSON has no accepted standard to provide this same capability. Hence the “profile” parameter is certainly more relevant to JSON content than XML.

Content negotiation is tricky. A request without an Accept header implies that the client accepts everything (*/*) [RFC 2616] [RFC 7231]. In this case the service provider needs to decide if it responds with its initial version for that resource or the latest version (not to mention what format (json, xml, etc) if multiple are available). To avoid breaking any clients the service can provide the initial version when in doubt, but that necessitates  the initial revision be permanently available. Otherwise clients may break if a new revision isn’t backwardly compatible. Version determination when the client provides no version in its request is a concern for any technique that relies on headers and URL parameters.

What is commonly overlooked is the fact that using only the Accept header in content negotiation solves only half the problem, that is negotiating the resource format (and version) to be returned to the client. The Accept header plays no role in scenarios where the client POSTs or PUTs a resource to the service provider.  This is where the Content-Type header comes in. It references the same MIME types as the Accept header except that it applies to a payload transferred from client to server [note 6].

Content-Negotiation

7. Binding API Version to API Key

TBD

Conclusion

Surveying the REST API landscape, all indications are that the URL Path is the most common technique adopted for API versioning. While not syntactically correct with regards to REST semantics, it is easy to understand and easy to communicate. Links to resource versions can be easily shared. It has the added advantage that version requests are always explicit. Deciding what granularity of version control you want depends upon your application. It may not make sense to have two different versions of different resources co-reside.

Using a custom version header is an accepted practice that avoids polluting the resource URLs and also avoids the nuanced complexity of getting content negotiation right. It also side steps attempting to properly interpret the true meaning of Version and Content-Version. Be sure to handle scenarios where no version is specified either because the client didn’t bother or some network intermediary threw it away. Look first to using Accept-Version.

Attempt content negotiation if you’re up to the challenge or will be socially ostracized if you don’t conform to hallowed Internet RFCs. There are several ways to get this wrong and allowing clients to independently specify request and response resource versions (via Accept and Content-Type headers) introduces a lot of additional test scenarios.  However, the end result would be a very flexible API that most adopters probably won’t appreciate.


[note 1]

“myservice” is optional. Base path could be ‘/’.

[note 2]

Use of “X-” for non-standard labels is discouraged (see RFC 6648). For headers not expected to be broadly adopted simply namespace with token of your choosing. For example:

MyCompany-API-Version: v1″

[note 3]

While interpretation of the specification is in favour of using the “Version” header field to represent the version of the resource, it may have also been interpreted in other incompatible ways since it’s been on the books for so long (since 1992). As a result many implementations that do choose to communicate version information in the header avoid the use of this field and introduce a custom field instead.

[note 4]

Use of “Content-Version” header (RFC 2068) is subject to interpretive overlap with “Version”. As such it too has been avoided with regards to using it to explicitly denote API versions.

[note 5]

The Accept header being an important standard header has little risk of being stripped by intermediaries between requester and service provider (unlike more obscure headers or custom ones).

[note 6]

Of course the Content-Type is also present on responses from server to client. In this context it denotes the media type of the response payload.


References

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s