Strategy 1: URL path versioning
GET /api/v1/users
GET /api/v2/users
Pros: Obvious, easy to understand, easy to route. Clients know exactly which version they are using. Load balancers can route different versions to different servers.
Cons: URL pollution. Every resource gets duplicated. Internal links and documentation get messy.
Best for: Public APIs with long-lived consumers. If your API serves mobile apps that update infrequently, URL versioning gives them a stable target.
Strategy 2: Header versioning
GET /api/users
Accept-Version: 2
Pros: Clean URLs. The version is metadata about the request, not part of the resource identifier.
Cons: Harder to test (you need to set headers, not just change the URL). Some proxies and CDNs do not forward custom headers reliably.
Best for: Internal APIs where you control all clients and can enforce header discipline.
Strategy 3: Content negotiation
GET /api/users
Accept: application/vnd.myapp.v2+json
Pros: RESTful purists love this. The media type describes the representation, which is what HTTP content negotiation was designed for.
Cons: Practically nobody does this well. Client libraries make it awkward. Documentation tools do not support it cleanly.
Best for: Honestly, I have never seen this work well in practice.
My recommendation
For most teams: URL path versioning. It is boring, obvious, and every tool in the ecosystem supports it.
When to version
Not every change needs a new version. Breaking changes do. Non-breaking changes do not.
Breaking: Removing a field, changing a field type, changing authentication, changing error format.
Non-breaking: Adding a field, adding an endpoint, adding an optional parameter.
The migration plan
- Ship v2 alongside v1
- Add deprecation headers to v1 responses:
Deprecation: true - Monitor v1 traffic — do not remove it until traffic is near zero
- Give clients at least 6 months of overlap
- Remove v1 and redirect to v2 docs
The real answer
Design your v1 to be extensible. Use envelopes ({ data, meta }), make fields optional where possible, and use feature flags instead of version bumps for minor changes. The best API version is the one you never need to ship.
