Why versioning matters
An API is a promise. Once clients depend on it, changing it carelessly breaks their apps, and broken clients are expensive for everyone. Versioning is how you ship improvements while giving clients a stable contract and a clear path to migrate.
The goal is not to avoid change. It is to make change predictable: know which changes are safe, expose versions in a consistent way, and retire old ones on a communicated schedule instead of surprising people.
Breaking versus additive changes
Most versioning pain comes from not classifying changes. Start with a clear rule.
Usually safe (additive):
- Adding a new optional field to a response.
- Adding a new endpoint.
- Adding a new optional request parameter.
- Adding a new enum value, if clients tolerate unknown values.
Breaking (needs a version):
- Removing or renaming a field.
- Changing a field’s type or format.
- Making an optional parameter required.
- Changing the meaning or default of existing behaviour.
- Changing status codes or error shapes clients depend on.
Designing clients to ignore unknown fields is the single best way to keep additive changes safe. Document the rule so nobody has to guess.
Versioning strategies
There are four common ways to let clients select a version.
URI versioning — the version is part of the path.
GET /v1/posts
GET /v2/posts
It is visible, easy to route, easy to cache and easy to test in a browser. It is the most common choice for public APIs, even though purists argue the URL should not change.
Header versioning — a custom header selects the version.
GET /posts
X-API-Version: 2
URLs stay stable, which is nice for caching by resource, but the version is invisible in logs, links and browser tests.
Media-type versioning — content negotiation selects the version.
GET /posts
Accept: application/vnd.example.v2+json
This is the most REST-aligned approach and composes with content negotiation, but it is the hardest to discover and debug.
Query parameter — ?version=2. Simple, but easy to omit and awkward for caching.
Whichever you choose, apply it consistently and document it. Mixing strategies is worse than picking a less fashionable one.
Deprecation and sunset
Removing a version should be a process, not an event.
Deprecation: true
Sunset: Wed, 31 Dec 2026 23:59:59 GMT
Link: </v2/posts>; rel="successor-version"
- Deprecation announces that a version or endpoint is going away.
- Sunset gives the exact date it stops working.
- Link points to the replacement.
Pair the headers with a migration guide, changelog entries and direct communication to heavy users. Then monitor usage: if a meaningful share of traffic is still on the old version near the sunset date, extend it rather than break those clients.
Running versions side by side
Supporting two versions means the code must serve both. Common approaches:
- Versioned handlers that map to shared services, so business logic lives in one place.
- Adapters that translate between the old representation and the new one.
- Feature flags for gradual rollout of new behaviour.
- Separate specs per version, generated and published alongside the API.
Keep the difference between versions small. Large forks of logic are hard to maintain and easy to let drift.
Communicating change
Versioning only works if clients know what is happening.
- Publish a changelog and mark breaking changes clearly.
- Keep an up-to-date OpenAPI spec per version.
- Send deprecation notices in headers and, where possible, by email.
- Provide a migration guide with before-and-after examples.
- Give a support window that matches your users’ release cycles.
Best practices
- Classify every change as additive or breaking before shipping.
- Prefer additive changes; bump the version only when you must.
- Pick one versioning strategy and use it everywhere.
- Never remove a field or endpoint without a deprecation period.
- Announce deprecation with headers and a sunset date.
- Monitor traffic per version before retiring one.
- Keep shared logic behind version-specific adapters.
Common mistakes
- Breaking clients silently by changing a response shape.
- Versioning every tiny change and creating a maintenance burden.
- Mixing versioning strategies across the same API.
- Removing a version without warning or a migration path.
- Letting old versions run forever with no plan.
- Forgetting to update documentation and specs per version.
Where to go next
Versioning is how an API survives contact with real clients. Build on clean REST design, describe each version with OpenAPI, and use HTTP headers to communicate deprecation. Then write a one-page policy for your own API: what counts as breaking, and how long versions live.