Choose and implement an API versioning strategy that lets you evolve your API without breaking existing clients. Covers URI versioning, header versioning, content negotiation, sunset policies, and migration tooling.
API versioning strategies are the means by which a backend service evolves its interface over time while maintaining compatibility with clients that depend on prior versions. This matters when the service must introduce breaking changes—adding, removing, or modifying fields in request or response bodies, altering semantics, or changing endpoints—without disrupting existing clients. Versioning ensures that clients using old interfaces continue to work, even as new versions are released and adopted.
When clients need to explicitly target a version, embedding the version in the URL path is the most direct approach. The version is part of the endpoint path, typically at the root or after the base path.
GET /api/v1/users/123 HTTP/1.1
Host: api.example.com
Accept: application/json
The server routes the request based on the v1 segment. The version may be a simple string, such as v1, v2, or v3, or a date-based version like v2024-03-15.
A common failure occurs when the version is not normalized across the service. Clients may send v1, V1, v1.0, or even api/v1, but the server treats them as distinct versions. To avoid this, the routing logic must be case-insensitive and support optional trailing slashes.
Clients can discover the current version by making a GET /api/version request:
{
"current": "v2",
"supported": [
"v1",
"v2"
],
"latest": "v2"
}
Clients may also use Accept headers to request a version:
GET /api/users/123 HTTP/1.1
Host: api.example.com
Accept: application/vnd.example.users.v2+json
The server parses the Accept header and selects the appropriate version. This pattern is especially useful in microservices where different services use different versioning schemes.
v1 but the response includes v2 metadata. The client must verify the X-API-Version header to ensure consistency.v1 may receive v2 data unexpectedly.v1 data but the Accept header specifies v2. The client assumes it is using v2, but the server still uses v1 logic. This leads to subtle bugs when new fields are added but not used.Query parameters allow clients to specify versioning in the query string. This is especially useful for clients that cannot control the full URL path, such as mobile apps or legacy systems.
GET /api/users/123?version=v1 HTTP/1.1
Host: api.example.com
The server reads the version query parameter and selects the appropriate handler. The version key may be named differently: ver, api_version, or v.
The server must handle multiple versions with a single endpoint. For example, a GET /api/users endpoint may serve different data structures based on the version parameter.
// GET /api/users?version=v1
{
"users": [
{
"id": 123,
"name": "Alice",
"email": "[email protected]"
}
]
}
// GET /api/users?version=v2
{
"users": [
{
"id": 123,
"fullName": "Alice Johnson",
"email": "[email protected]",
"createdAt": "2024-01-01T00:00:00Z"
}
],
"pagination": {
"currentPage": 1,
"total": 42
}
}
The server may also support Accept header versioning in conjunction with query parameters.
version=v1 and Accept: application/vnd.example.users.v2+json are present, which one wins? The server must define a clear precedence: query parameter wins, or Accept header wins, or a combination.version=v1 but forget to include it in the URL. The server must validate that the parameter exists and is not empty.X-API-Version header may return v1, but the response body includes v2 fields. The client must reconcile this and may need to cache versioned responses based on both the URL and the Accept header.HTTP headers provide a clean, standardized way to specify the API version, especially for clients that send multiple requests to the same service.
GET /api/users/123 HTTP/1.1
Host: api.example.com
Accept: application/json
X-API-Version: v2
The server reads the X-API-Version header and selects the appropriate version. The header name may vary: API-Version, Accept-Version, Version, or X-Api-Version.
The server may also return the selected version in the response header:
HTTP/1.1 200 OK
Content-Type: application/json
X-API-Version: v2
Content-Length: 142
{
"id": 123,
"name": "Alice",
"email": "[email protected]"
}
Clients can use this to verify that they are interacting with the expected version.
X-API-Version: v2 on the initial request, but subsequent requests do not include it. The server may serve different versions for different requests, leading to inconsistent behavior.X-API-Version header even when the client did not provide one. This leads to a “default version” behavior where clients assume they are using a version, but the server provides the version in the response.X-API-Version: v2, but the server expects API-Version. The header name must be standardized across the service.Accept headers to determine the media type and X-API-Version to determine the version. The combination allows for fine-grained control.Media types (MIME types) allow versioning through the Accept header and the Content-Type header, using a structured format to encode version information.
GET /api/users/123 HTTP/1.1
Host: api.example.com
Accept: application/vnd.example.users.v2+json
The server matches the Accept header to a known media type and selects the appropriate version. The media type may include parameters:
Accept: application/vnd.example.users.v2+json; charset=utf-8; profile=full
The server returns the response with the same media type:
HTTP/1.1 200 OK
Content-Type: application/vnd.example.users.v2+json
Content-Length: 142
{
"id": 123,
"fullName": "Alice Johnson",
"email": "[email protected]"
}
The media type may include version-specific features. For example, v2 may introduce createdAt, while v1 does not.
application/vnd.example.users.v2+json, but the client sends application/vnd.example.users.v2+json; version=2. The server must recognize the version in the parameters.Content-Type: The server returns application/json, but the Content-Type header is application/vnd.example.users.v2+json. Clients may expect version-specific behavior but receive plain JSON.full, minimal, lite) for each version. Clients may request application/vnd.example.users.v2+json; profile=full, and the server returns a full response.Content negotiation combines multiple versioning mechanisms. The server selects the version based on a combination of Accept, X-API-Version, and version query parameters.
GET /api/users/123 HTTP/1.1
Host: api.example.com
Accept: application/vnd.example.users.v2+json
X-API-Version: v1
version: v3
The server applies a priority order to determine the version:
Accept header (highest priority)X-API-Version headerversion query parameter (lowest priority)The server uses the highest-priority version and returns it in the X-API-Version header.
v2 from Accept, but the response includes v1 data. The client must verify that the version in the X-API-Version header matches the expected version.Content-Type: The server returns application/json, but the Content-Type header is application/vnd.example.users.v2+json. The client may expect version-specific behavior but receives plain JSON.Accept: The server expects application/vnd.example.users.v2+json, but the client sends application/json. The server may fall back to a default version, but the client assumes it is using v2.A well-designed versioning strategy includes backward and forward compatibility to reduce the burden of version upgrades.
The server must support both the current version and previous versions. When a new version is introduced, the server must handle requests for old versions.
// GET /api/users/123?version=v1
{
"id": 123,
"name": "Alice",
"email": "[email protected]"
}
The server may return v1 data even when the Accept header specifies v2.
Clients may use a new version before the server supports it. The server must handle unknown fields and additional data.
// GET /api/users/123?version=v2
{
"id": 123,
"fullName": "Alice Johnson",
"email": "[email protected]",
"createdAt": "2024-01-01T00:00:00Z",
"preferences": {
"theme": "dark",
"notifications": true
},
"unknownField": "value" // not defined in v2 schema
}
The server may ignore unknown fields and return them in the response.
preferences.theme, but the server does not recognize it. The server returns preferences.theme, but the client does not use it.createdAt, but the server returns created_at. The client must map the fields correctly.A versioning strategy must include deprecation and sunset policies.
Deprecation header.HTTP/1.1 200 OK
Content-Type: application/json
X-API-Version: v1
Deprecation: true
Deprecation-Until: 2025-06-30
Deprecation-Link: <https://docs.example.com/deprecations/v1>; rel="documentation"
HTTP/1.1 410 Gone
Content-Type: application/json
X-API-Version: v1
Sunset: 2025-06-30
The client may use the Sunset header to inform users that a version is no longer supported.
Deprecation header: The server sets Deprecation: true, but the client does not recognize it. The client must check the Deprecation-Until header.Sunset header: The server returns 410 Gone, but the client does not recognize the Sunset header. The client must parse the Sunset header to determine the sunset date.Clients may discover available versions through a dedicated endpoint.
GET /api/version HTTP/1.1
Host: api.example.com
Accept: application/json
{
"current": "v2",
"supported": [
"v1",
"v2"
],
"latest": "v2",
"deprecated": [
"v1"
],
"sunsetting": [
{
"version": "v1",
"until": "2025-06-30"
}
],
"links": {
"self": "/api/version",
"documentation": "/docs/api",
"changelog": "/changelog"
}
}
Clients may use this endpoint to select the appropriate version and to inform users about deprecation and sunset.
current field: The server returns v2, but the client expects current to be v2. The client may use v1 data but not know that v2 is the current version.links object: The server returns links.self, but the client does not recognize it. The client must parse the links object to find the documentation and changelog.A single service may support multiple versions simultaneously. The server must route requests based on the version, and the service must maintain backward and forward compatibility.
// GET /api/users/123?version=v1
{
"id": 123,
"name": "Alice",
"email": "[email protected]"
}
// GET /api/users/123?version=v2
{
"id": 123,
"fullName": "Alice Johnson",
"email": "[email protected]",
"createdAt": "2024-01-01T00:00:00Z"
}
The server may also support v3 with new features, while maintaining v1 and v2.
Accept header: The server supports v3, but the client sends application/vnd.example.users.v2+json. The server returns v2 data, but the client expects v3.Content-Type: The server returns application/vnd.example.users.v3+json, but the client does not recognize it. The client must parse the Content-Type header to determine the version.Clients may route requests to different services based on the version. The client may maintain a routing table that maps versions to endpoints.
{
"v1": {
"url": "https://api.example.com/v1",
"version": "v1"
},
"v2": {
"url": "https://api.example.com/v2",
"version": "v2"
}
}
The client may use this table to select the appropriate endpoint for each request.
version field: The server returns v1, but the client expects version to be v1. The client may use v1 data but not know that v2 is available.Error responses must include version information to help clients debug version-related issues.
{
"error": "NotFound",
"message": "User not found",
"details": {
"userId": 123,
"version": "v2"
},
"links": [
{
"rel": "documentation",
"href": "https://docs.example.com/errors/NotFound"
}
]
}
The server may return 404 Not Found with the X-API-Version header to indicate the version of the error.
error field: The server returns error: "NotFound", but the client expects error_code. The client may not recognize the error.details field: The server returns version: "v2", but the client does not expect it. The client may use v1 data but not know that v2 is available.Clients may validate their versioning strategy through automated tests.
curl -H "Accept: application/vnd.example.users.v2+json" \
-H "X-API-Version: v1" \
-v \
https://api.example.com/api/users/123
The client may expect the response to include fullName and createdAt.
Accept header: The client sends application/vnd.example.users.v1+json, but the server expects application/vnd.example.users.v2+json. The server returns v2 data, but the client expects v1.X-API-Version: The client sends X-API-Version: v1, but the server expects API-Version. The server returns v1 data, but the client expects v1.A versioning strategy must include deployment and rollout policies.
X-API-Version to route requests to the canary version.X-API-Version: The server routes requests to the canary version, but the client does not set X-API-Version. The server returns canary data, but the client does not recognize it.Accept header: The server expects application/vnd.example.users.v2+json, but the client sends application/json. The server returns v2 data, but the client expects v1.A versioning strategy must include monitoring and observability.
version field: The server collects metrics, but the client does not recognize the version field. The client may not know which version is performing well.version field: The server logs requests, but the client does not recognize the version field. The client may not know which version is failing.A versioning strategy must include documentation and examples.
version field: The server provides documentation, but the client does not recognize the version field. The client may not know which version is documented.version field: The server provides examples, but the client does not recognize the version field. The client may not know which version is exampled.This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy