API versioning is one of those topics that sounds simple until you have a production API with real users. Then you discover that every versioning strategy has tradeoffs, and the choice you made two years ago is now causing production incidents.
The Three Strategies
URL path versioning puts the version in the URL: /api/v1/users. It’s the simplest to understand and debug. Clients know exactly which version they’re calling just by looking at the URL. The downside: you eventually have to maintain multiple versions of your entire URL tree, and URL-based versioning makes it hard to evolve individual endpoints independently.
Header-based versioning uses a custom header like Accept-Version: v2. It keeps URLs clean but introduces complexity. Caching proxies might not vary on your custom header, leading to version-mismatch bugs. Debugging requires looking at headers, not just URLs, which makes curl commands longer and log analysis harder.
Content negotiation uses the Accept header with custom media types. It’s the most RESTful approach and allows per-resource versioning. It’s also the most complex to implement and debug. Most teams that try it eventually simplify to one of the other two approaches.
What Actually Works in Practice
The pattern that has emerged as the pragmatic winner in 2026: URL path versioning for major versions (v1, v2, v3) with a clear deprecation policy. Major versions change rarely — once every two to three years. Each major version gets its own set of route files or Django app, with shared business logic living in a common service layer.
For minor, non-breaking changes, don’t version at all. Add optional fields to responses. Accept additional optional fields in requests. Use feature flags for behavioral changes that clients can opt into with a query parameter during a transition period.
The Deprecation Policy
The deprecation policy matters more than the versioning strategy. Set a clear timeline: new major version announced, old version deprecated with a six-month sunset period, old version removed. Communicate through API response headers (Deprecation and Sunset) and email to registered API consumers. Track usage of deprecated endpoints and reach out to active users individually.
Framework-Specific Implementation
FastAPI makes URL versioning straightforward with APIRouter prefixes. Django REST Framework supports URL versioning out of the box with its versioning classes. Flask can use blueprints with URL prefixes. The implementation is rarely the hard part — the hard part is the organizational discipline to deprecate and remove old versions on schedule.
Versioning is a communication problem more than a technical one. Choose the simplest strategy that works for your clients, document your deprecation policy clearly, and stick to it.
Discussion
Leave a comment
No comments yet
Be the first to start the conversation.