Why Version APIs?
APIs change. New fields are added, old ones are renamed, endpoints are restructured. Without versioning, every breaking change breaks every client. API versioning lets you evolve the API while giving existing clients time to migrate.
A *breaking change* is any change that causes a previously valid client request to fail or return a different result. Examples:
- Removing or renaming a field
- Changing a field's type (
string→integer) - Removing an endpoint
- Changing authentication requirements
*Non-breaking* (additive) changes — new optional fields, new endpoints — generally do not require a version bump.
URL Path Versioning (`/v1/`)
The most common and most visible approach:
https://api.example.com/v1/users
https://api.example.com/v2/users
Pros: immediately obvious in logs, browser history, and documentation. Easy to route at the load balancer level. Cache-friendly — v1 and v2 are distinct URLs.
Cons: technically violates REST's principle that a URL identifies a resource, not a version of a resource. Proliferates endpoints and documentation pages.
This is the pragmatic choice for public APIs. GitHub, Stripe, Twilio, and most major API providers use URL versioning.
Header Versioning (`Accept`)
Encode the version in the Accept header using a custom media type:
GET /users HTTP/1.1
Accept: application/vnd.example.v2+json
Or use a custom API-Version header:
GET /users HTTP/1.1
API-Version: 2024-01-01
Pros: keeps URLs clean; a single URL represents the resource regardless of version. Aligns with REST's content negotiation model.
Cons: invisible to browsers and most HTTP clients. Harder to test without dedicated tooling. Breaks caching unless Vary: Accept is set correctly.
Query Parameter Versioning
Pass the version as a query string parameter:
GET /users?version=2
GET /users?api-version=2024-01-01
Pros: easy to test in a browser. Works with any HTTP client without special configuration.
Cons: pollutes URLs, can conflict with pagination/filter parameters, and complicates caching.
Semantic Versioning for APIs
Many teams adapt SemVer (MAJOR.MINOR.PATCH) for APIs:
| Version Component | Meaning |
|---|---|
| `MAJOR` | Breaking changes — increment the public version |
| `MINOR` | New features, backward-compatible |
| `PATCH` | Bug fixes, no API surface change |
In practice, only MAJOR versions are exposed to clients (/v1/, /v2/). Minor and patch versions are internal.
An alternative is date-based versioning (used by Stripe and Cloudflare): 2024-01-15. Each date snapshot is a stable contract. Clients pin to a date and receive no breaking changes until they opt in to a newer date.
Deprecation Strategy
A version without a sunset plan becomes technical debt. Follow these steps:
- Announce deprecation — add a
Deprecationheader and aSunsetheader to all responses on the old version:
Deprecation: true
Sunset: Sat, 01 Jun 2025 00:00:00 GMT
Link: <https://api.example.com/v2/users>; rel="successor-version"
- Communicate via email and changelog at least 6–12 months in advance for external APIs.
- Log usage — track which clients are still using the deprecated version so you can reach out directly.
- Return 410 Gone after sunset, not 404 —
410signals intentional removal.
Recommendation
For most teams: use URL path versioning (/v1/). It is the most understood pattern, easiest to document, and simplest to route. Combine with Deprecation + Sunset headers for graceful migration.