Versioning Strategies
When a change would break existing clients, you need a versioning strategy so old and new clients can coexist. There are three common places to put the version.
Breaking vs non-breaking
Section titled “Breaking vs non-breaking”First, decide whether you even need a new version. Non-breaking (additive) changes never require one:
- Adding a new endpoint or a new optional field.
- Adding a new optional query parameter.
- Adding a new value to an output-only enum (if clients are told to tolerate unknowns).
Breaking changes do require one: removing or renaming a field, changing a type, making an optional field required, or changing the meaning of a response.
Three strategies
Section titled “Three strategies”- URI versioning —
GET /v1/articles. Most visible and easiest to route and cache; the version is right there in the URL. Downside: the same resource lives at two URIs across versions. - Header versioning — a custom or standard header like
API-Version: 2. Keeps URIs clean, but the version is invisible in logs and harder to try in a browser. - Media-type versioning — negotiate via
Accept: application/vnd.example.article+json;version=2. The most RESTful (the version is part of the representation), but the most awkward to use.
GET /v1/articles # URI versioningGET /articles # header versioningAPI-Version: 2
GET /articles # media-type versioningAccept: application/vnd.example.article+json;version=2Choosing
Section titled “Choosing”For most teams, URI versioning wins on pragmatism: it is obvious, cache-friendly, and trivial to test. Whatever you choose, version the whole API at a coarse grain (v1, v2), not each endpoint independently — per-endpoint versions explode the support matrix.
Common mistakes
Section titled “Common mistakes”Versioning one endpoint instead of the whole API Symptom:
/v1/usersbut/v2/ordersin the same API- Clients have to track a different version per endpoint
- Version the whole API together:
/v1/usersand/v1/orders→/v2/usersand/v2/orders
Deprecating without any grace period Symptom:
/v1/is shut off with no advance notice — clients break immediately- No migration guide or clear timeline
- Use
DeprecationandSunsetheaders:Sunset: Sat, 31 Dec 2024 23:59:59 GMT
💡 Real-world examples
Stripe API:
- Versions via the
Stripe-Versionheader, not the URI- Each account is locked to the version it was created on — a new Stripe release never breaks it
GitHub API:
- Versions via the date-based
X-GitHub-Api-Versionheader (e.g.2022-11-28), not a/v3/-style URI — the REST API dropped URI versioning- Supports a version for at least 24 months after a newer one ships, with
DeprecationandSunsetheaders as it nears end of support