Talk:API versioning

Add topic

Options for entry point versioning[edit]

While we generally try to evolve the responses returned by entry points in a backwards-compatible manner, there is occasionally a need to change the format returned by an entry point in a way that breaks backwards compatibility. To avoid breaking clients that might not be able to change their code in a timely manner (think apps that users might not update for a while), we should provide means for a client to communicate the client's expectations as part of the request.

The main methods for per-entry point versioning are:

  • Adding another a path element in the URL, as in /entrypoint/v1/foo.
  • Using a query string, as in /entrypoint/foo?v=1.0.0.
  • Using the Accept header, indicating the expected mime type.
  • Using other custom headers.

Here is a discussion of the relative pros / cons:

Query strings[edit]

Append a ?v=1.0.0 or ?accept=text/html; query parameter to the URL to request a specific format version. Return the latest version if no query parameter is supplied.


Vary is naturally enforced by having different URLs. Purging is complicated by the need to purge all variants separately. There are features like Varnish 4's X-Key index that can enable this. Alternatively, a VCL hack could move ?accept=... to a header & then vary on that.

Ergonomics & documentation[edit]

Swagger supports query parameters well, and even lets the user select format versions from a drop-down. Communicating a URL is simple, and it is relatively simple to remove the query string to link to the latest format version where desired.


For the desirable grouping of request metrics independent of version negotiation, the query string needs to be stripped from the URL. This is not very complicated to do, but might not be done by default.

Semantics / standards[edit]

The use of query strings is not standardized, and no widely-used format for version parameters exists.

Accept header[edit]

Return the latest format if no Accept header is supplied, or no specific content-type matches and * is accepted. Return the requested format if it is (still) supported by the API.


Browsers send Accept headers by default, indicating their preference for headers and images. This means that a naive Vary: accept would fragment caches for requests with the default header set. However, our content types all contain a version parameter using the profile=... mechanism. We can use this to derive a secondary cache key using the HTTP "Key" header, once it is implemented in Varnish. Until this is implemented, we can use a bit of VCL very similar to what we already do for cookie headers to distinguish default Accept header values from explicit requests for a specific mime type.

Purging is fairly simple in this scheme, as all format variants of a logical resource share the same URL.

Ergonomics & documentation[edit]

Generally, sending headers is slightly more complex than just copy & pasting a URL. However, CORS allows setting Accept headers by default. The swagger documentation already emits an example curl invocation with the right Accept header.

If we always return the latest version if the Accept header is omitted, example URLs will normally still point to the same resource. In documentation scenarios, linking to the latest version is typically desirable.


Having a single URL per resource makes it easy to provide aggregate access statistics independent of content version negotiation. Statistics about the relative popularity of individual versions can be collected separately (and are normally per-entry point rather than resource), and can be used to determine when to drop support for an old mime type.

Semantics / standards[edit]

The use of Accept for content negotiation has been part of HTTP from the beginning, and is widely supported. Its semantics provide a nice symmetry between request and response content-type, and generally emphasize the role of the content-type in describing the resource.

Related reading[edit]