API Versioning Strategies

URI path (/v1) vs header (Accept, custom) vs query param, when to bump, announcing deprecation with Deprecation/Sunset headers, backward compatibility, and media-type versioning.

API versioning is the strategy for evolving an API without breaking the clients already using it. Once published, an API becomes a contract: renaming a field or reshaping a response can shatter many integrations at once. The goal of versioning is to distinguish changes you can make safely from changes that require a new version, and to transition the latter without breaking anyone.

Where to carry the version: three approaches

There are three families, differing in where the version rides on the request.

GET /v1/users/u_92311 HTTP/1.1
Host: api.example.com
Accept: application/vnd.example.v2+json
X-API-Version: 2

Media-type versioning

A refined form of the header approach is media-type (content-negotiation) versioning. The client asks for a representation version via a vendor media type in Accept (application/vnd.example.v2+json), and the server responds through content negotiation. Because it keeps the URI a pure resource identifier, it's theoretically the most 'RESTful' — but in practice its learning curve and thin tooling support keep it far less common than path versioning.

When to bump the version

The core principle: bump the version only for a breaking change. Define clearly, as a team, what counts as breaking and what doesn't.

Announcing deprecation: Deprecation and Sunset

When retiring an old version, don't cut it off without warning — announce it with standard headers. The Deprecation header marks a response (or endpoint) as deprecated, and the Sunset header states the date/time when responses will actually stop. Pointing at migration docs with a Link header is even friendlier.

HTTP/1.1 200 OK
Deprecation: true
Sunset: Wed, 31 Dec 2026 23:59:59 GMT
Link: <https://api.example.com/docs/migrating-to-v2>; rel="deprecation"
Content-Type: application/json

Practical guidance — For most teams, URI path versioning (/v1, /v2) is the most practical: it's visible and easy to cache, route, document, and tool. But don't version-happy. Splitting into /v2, /v3 too often explodes the cost of maintaining several versions at once. The healthy pattern is to keep backward compatibility as long as possible so one major version lives a long time, opening a new version only for a truly unavoidable overhaul. And whenever you do ship a new version, always pair it with a clear retirement timeline (Deprecation/Sunset) and a migration guide for the old one.