Versioning APIs using request headers
Enable versioning on a per-request basis.
Verion headers allow API consumers to specify the desired API version for each request.
Using headers to define version numbers is more RESTful since multiple versions of the same resource are addressed by the same URI. By distinguishing between the concepts of the resource, which as a fixed identity, and the resource's representation, links to resources are preserved over time and can be used as permalinks.
An example of a request might look like:
curl https://api.example.com/items/123 \ -H 'ExampleAPI-Version: 2012-10-27'
Option 1: Use a custom header
There is no standard HTTP header field that defines the version of a resource's representation, meaning APIs need to define their own. The table below descibes some common options.
Header name | Notes |
---|---|
Accept-Version | Aligns with existing content-negotiation headers, such as Accept-Language and Accept-Encoding . |
ExampleAPI-Version | Clarifies that the header is specific to the API vendor. |
It is no longer best practice to prefix custom headers with X-
.
Option 2: Use HTTP content negotiation
HTTP content negotiation allows the client (user agent) to describe what kinds of representations of the it can handle. This mechanism is typically used to request specific content types such as text/html
, but can easily be extended to describe the version of the representation. The server responds with the requested representation if it is able to, or otherwise responds with an appropriate error.
Content negotiation primarily uses the Accept
request header. This can be extended to include the version number like so:
Accept: application/vnd.example.v1+json
Or, alternatively using Accept extension parameters:
Accept: application/vnd.example+json;version=1.0
If the request is successful, the server's response will contain the Content-Type
header, which describes the specific representation that was negotiated:
HTTP/1.1 200 OK Content-Type: application/vnd.example.v1+json
In some cases, the server may respond with a different version to that which was requested. For example, if there is a more up-to-date version that is backwards compatible to the requested version.
When using the Accept
header as part of content-negotiation, the appropriate status code for unsupported versions is 406 Not Acceptable
.
Pros
- Follows REST principles that multiple versions of a resource are still identified by a single URI.
- Content negotiation allows the server to respond with a more up-to-date version than requested, if still backwards compatible.
- The
Accept
header allows for multiple representation versions to be included in the same OpenAPI definition.
Cons
- Developers may easily omit the header if it is optional, potentially leading to surprising behaviour when a new default version is released.
- The API is more difficult to consume, particularly if extending the
Accept
header. - The server implementation may be more complex as versioning cannot be implemented at the gateway or router layer.
Usage
To set the API version on a specific request, send an ExampleAPI-Version
header:
curl https://api.example.com/records \ -H 'ExampleCo-Version: 2.1.7'
Requests without the ExampleAPI-Version
header will default to use the latest version.
If you specify an API version that is no longer supported, you will receive a 400
error.
Implementation notes
Resource URIs that can responds with different representation versions of the resource should always include the Vary
header in responses. This instructs any caches that differently versioned responses should be cached separately.
The Vary
header is formatted as a comma-separated list of header names that influenced the response. For example, if using a custom header:
HTTP/1.1 200 OK Content-Type: application/vnd.example.v1+json Vary: Accept, Accept-Encoding, ExampleAPI-Version { "data": [ {}, {} ] }
OpenAPI reference
This OpenAPI document descibes an API that is versioned using a custom request header, with the version number formatted using Semantic Versioning.
{ "openapi": "3.1.0", "info": { "title": "Header Versioning API", "version": "2.1.7" }, "paths": { "/items/{item}": { "parameters": [ { "$ref": "#/components/parameters/ExampleAPIVersion" } ], "get": { "summary": "The item", "parameters": [ { "$ref": "#/components/parameters/ExampleAPIVersion" } ] } } }, "components": { "parameters": { "ItemID": { "name": "item", "in": "path", "description": "The unique ID of the item.", "required": true, "schema": { "type": "string" } }, "ExampleAPIVersion": { "name": "ExampleAPI-Version", "in": "header", "description": "Set the API version on a specific request. If not specified, the default value is the latest API version, currently `2.1.7`.", "required": false, "schema": { "type": "string", "pattern": "^([0-9]+).([0-9]+).([0-9]+)$", "default": "2.1.7" } } } } }