Should I use URL versioning (/v1/users) or header versioning (Accept: application/vnd.app.v1+json)? We tried both. Here is what worked.
Option 1: URL Path (/v1/users)
Pros: Simple, visible, cacheable
Cons: Need to route to same handler
GET /v1/users
GET /v2/users
Option 2: Header (Accept)
Pros: Cleaner URLs, same endpoint for different versions
Cons: Harder to test, not visible in browser
GET /users
Accept: application/vnd.app.v1+json
Option 3: Query Parameter (?version=1)
Not recommended - it breaks HTTP caching and looks messy.
What We Use
URL path versioning for major versions, query for minor tweaks:
GET /api/v1/users?fields=id,name -- v1 with optional fields
GET /api/v2/users -- breaking changes get new version
GET /api/v2/users/123/vip -- resource-specific versioning
Deprecation Strategy
When we release v2, we don’t kill v1 immediately:
- Ship v2, keep v1 working
- Add deprecation headers:
Deprecation: true - Announce timeline (6 months)
- Return warnings in responses
- Turn off v1 after timeline
One More Tip
Use OpenAPI/Swagger to document all versions. Make it easy for consumers to migrate.