API Versioning Strategies#
APIs change. Fields get added, endpoints get restructured, response formats evolve. The question is not whether to version your API, but how. A versioning strategy determines how clients discover and select API versions, how you communicate changes, and how you eventually retire old versions.
Breaking vs Non-Breaking Changes#
Before picking a versioning strategy, you need a clear definition of what constitutes a breaking change. This determines when you need a new version versus when you can evolve the existing one.
Non-Breaking Changes (Safe to Ship Without a New Version)#
- Adding a new field to a response body. Existing clients ignore fields they do not recognize.
- Adding a new optional query parameter or request field.
- Adding a new endpoint.
- Adding a new enum value to a response field (only if clients handle unknown values gracefully).
- Relaxing a validation constraint (accepting a wider range of input).
- Adding new HTTP methods to an existing resource.
Breaking Changes (Require a New Version)#
- Removing a field from a response body.
- Renaming a field.
- Changing a field’s data type (string to integer, object to array).
- Removing an endpoint.
- Adding a required field to a request body.
- Changing the URL structure of existing endpoints.
- Changing the meaning or behavior of an existing field.
- Tightening a validation constraint (rejecting previously accepted input).
- Changing error response formats.
The general rule: anything that can cause an existing, working client to break is a breaking change. When in doubt, it is breaking.
URL Path Versioning#
The version is part of the URL path. This is the most common approach.
GET /api/v1/orders/123
GET /api/v2/orders/123Advantages#
- Immediately visible in every request, log line, and documentation link.
- Easy to route at the infrastructure level. API gateways, load balancers, and ingress controllers can route
/api/v1/*to one service and/api/v2/*to another. - Simple for clients – change the base URL and you are on the new version.
- Cacheable by default. Different URLs are different cache entries.
Disadvantages#
- The URL is supposed to identify a resource, not the representation format.
/api/v1/orders/123and/api/v2/orders/123are the same order – they are the same resource. - Proliferates URL space. Every version doubles the endpoint surface.
- Clients must update URLs to migrate, even when the endpoint behavior is unchanged between versions.
Implementation#
At the infrastructure level, route by path prefix:
# Kubernetes Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
spec:
rules:
- host: api.example.com
http:
paths:
- path: /api/v1
pathType: Prefix
backend:
service:
name: api-v1
port:
number: 80
- path: /api/v2
pathType: Prefix
backend:
service:
name: api-v2
port:
number: 80Or within a single service, route internally:
# Flask example
app = Flask(__name__)
v1 = Blueprint('v1', __name__, url_prefix='/api/v1')
v2 = Blueprint('v2', __name__, url_prefix='/api/v2')
@v1.route('/orders/<order_id>')
def get_order_v1(order_id):
order = fetch_order(order_id)
return format_order_v1(order)
@v2.route('/orders/<order_id>')
def get_order_v2(order_id):
order = fetch_order(order_id)
return format_order_v2(order)
app.register_blueprint(v1)
app.register_blueprint(v2)When to Use#
URL path versioning works best when you have a public API consumed by external clients, when you want maximum visibility into which version is being used, and when your infrastructure routing is path-based. This is the right default choice for most teams.
Header Versioning#
The version is specified in a custom HTTP header.
GET /api/orders/123
Accept-Version: v2Or using a vendor-specific Accept header:
GET /api/orders/123
Accept: application/vnd.example.v2+jsonAdvantages#
- Clean URLs. The resource identifier does not change between versions.
- Aligns with REST principles – the URL identifies the resource, the header selects the representation.
- Allows a default version when no header is present (usually the latest stable version).
Disadvantages#
- Not visible in URLs. You cannot bookmark, share, or copy-paste a versioned API call by URL alone.
- Harder to test in a browser. You need curl or a tool that sets custom headers.
- Caching proxies may not vary on custom headers by default. You must configure
Vary: Accept-Versionand ensure your CDN respects it. - API gateways need header-based routing rules, which are more complex than path-based routing.
Implementation#
# Nginx header-based routing
map $http_accept_version $api_backend {
default api-v1;
"v1" api-v1;
"v2" api-v2;
}
server {
location /api/ {
proxy_pass http://$api_backend;
}
}When to Use#
Header versioning works for internal APIs where you control all clients, or when you have strong REST design principles and want clean resource URLs. Avoid it for public APIs where discoverability matters.
Query Parameter Versioning#
The version is a query parameter.
GET /api/orders/123?version=2Advantages#
- Easy to test – just change the query string in a browser.
- Compatible with every HTTP client without special configuration.
- Can default to the latest version when the parameter is omitted.
Disadvantages#
- Clutters the query string. The version is mixed in with actual query parameters.
- Caching is tied to the full URL including query string, which is correct behavior but may surprise teams that strip query params.
- Feels ad-hoc. Most mature APIs avoid this approach.
- Makes it harder to distinguish versioning from filtering or pagination parameters.
When to Use#
Query parameter versioning is a quick-and-dirty approach for internal tools or prototypes. Avoid it for production APIs that will be consumed by external teams.
Content Negotiation#
Uses the standard HTTP Accept header with media type parameters.
GET /api/orders/123
Accept: application/json; version=2This is a variation of header versioning using standard HTTP content negotiation rather than custom headers.
Advantages#
- Uses standard HTTP mechanisms. No custom headers.
- The server can return the best available version based on what the client accepts.
- Multiple versions can be expressed:
Accept: application/vnd.example.v2+json, application/vnd.example.v1+json;q=0.9.
Disadvantages#
- Complex for clients to implement correctly.
- Not all frameworks handle custom media types well out of the box.
- Documentation tooling (Swagger/OpenAPI) has limited support for content-negotiated versioning.
When to Use#
Content negotiation suits sophisticated API platforms where clients are expected to handle HTTP headers correctly and where you want to follow strict HTTP semantics.
Deprecation Workflow#
Regardless of your versioning strategy, you need a process for retiring old versions. A deprecation workflow communicates intent, gives clients time to migrate, and establishes clear deadlines.
Step 1: Announce Deprecation#
Add deprecation headers to responses from the old version:
HTTP/1.1 200 OK
Deprecation: Sat, 01 Mar 2026 00:00:00 GMT
Sunset: Sat, 01 Jun 2026 00:00:00 GMT
Link: <https://api.example.com/api/v2/orders>; rel="successor-version"Deprecation: The date the version was deprecated (RFC 8594).Sunset: The date the version will be removed (RFC 8594).Linkwithrel="successor-version": Points to the replacement.
Step 2: Monitor Usage#
Track which clients are still using the deprecated version. Log the User-Agent, API key, or client identifier with each request to the deprecated endpoints.
-- Query API request logs to find deprecated version usage
SELECT client_id, COUNT(*) as request_count, MAX(timestamp) as last_seen
FROM api_requests
WHERE api_version = 'v1'
AND timestamp > NOW() - INTERVAL '30 days'
GROUP BY client_id
ORDER BY request_count DESC;Step 3: Communicate Directly#
Contact high-usage clients directly. Automated headers are necessary but not sufficient. Send emails, post changelog entries, and update documentation with migration guides.
Step 4: Return Warnings, Then Errors#
Before the sunset date, consider returning warning responses. After the sunset date, return 410 Gone:
HTTP/1.1 410 Gone
Content-Type: application/json
{
"error": "api_version_sunset",
"message": "API v1 was retired on 2026-06-01. Please migrate to v2.",
"documentation": "https://docs.example.com/migration/v1-to-v2",
"successor": "https://api.example.com/api/v2"
}Version Sunset Policy#
Establish a policy before launching your first API version. A policy answers:
- How many versions are supported simultaneously? Two is common (current + previous). Three at most for large public APIs.
- What is the minimum deprecation notice period? Six months is reasonable for external APIs. Three months for internal APIs.
- What is the minimum time between deprecation and sunset? This is the migration window. Match it to your longest client release cycle.
- Who approves exceptions? Some clients cannot migrate on schedule. Have a process for granting extensions.
Document this policy publicly. Clients need to plan around it.
Practical Recommendations#
For most teams, use URL path versioning. It is simple, visible, well-understood, and supported by every infrastructure tool. The REST purity arguments against it do not outweigh its practical benefits.
Version at the API boundary, not internally. Internal service-to-service communication should use schema evolution (protobuf, Avro) rather than version numbers. Versioning is for external contracts.
Start at v1, not v0. Version zero signals instability. If your API is not stable enough for v1, it is not ready for external consumers.
Do not version for non-breaking changes. Add fields, add endpoints, add optional parameters – all without a version bump. Only increment the version for breaking changes.
Automate detection of breaking changes. Use OpenAPI spec diffing tools in CI to catch accidental breaking changes before they ship:
# openapi-diff detects breaking changes between spec versions
openapi-diff spec-v1.yaml spec-v2.yaml --fail-on-incompatibleDesign for backward compatibility from the start. The best version is the one you never have to create. Use nullable fields, enveloped responses, and extensible enums to give yourself room to evolve without breaking clients.