ADR-0026: URL-Based API Versioning
Status
Accepted - 2025-01-26
Context
TVL Platform APIs will evolve over time, requiring backward compatibility for existing clients.
Decision
URL-based versioning (/api/v1/, /api/v2/) for API endpoints.
Rationale
- Explicit: Version visible in URL
- Cacheable: Different URLs = different cache entries
- Simple: Easy to route and test
- Industry Standard: Used by Stripe, Twilio, GitHub
Alternatives Considered
Alternative 1: Header Versioning
Rejected - Harder to test (can't test in browser), breaks caching
Alternative 2: Query Parameter
Rejected - Breaks caching, non-standard
Alternative 3: Content Negotiation
Rejected - Complex, requires custom Accept headers
URL Structure
/api/v1/bookings           # Version 1
/api/v2/bookings           # Version 2 (breaking changes)
/api/v1/properties/:id     # Version 1, specific resource
Versioning Strategy
When to Create New Version
Create new version (v2) when:
- ✅ Removing fields from response
- ✅ Changing field types (string → number)
- ✅ Changing required fields
- ✅ Changing validation rules (stricter)
Backward compatible (stay in v1) when:
- ✅ Adding new optional fields
- ✅ Adding new endpoints
- ✅ Relaxing validation rules
Deprecation Policy
- Support N-1 versions for 12 months
- Announce deprecation 6 months in advance
- Sunset header indicates deprecation
HTTP/1.1 200 OK
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: true
Link: </api/v2/bookings>; rel="alternate"
Implementation
// src/routes/v1/bookings.ts
import { FastifyPluginAsync } from 'fastify';
const v1Routes: FastifyPluginAsync = async (app) => {
  app.get('/bookings', async (req, reply) => {
    // Version 1 implementation
  });
};
// src/routes/v2/bookings.ts
const v2Routes: FastifyPluginAsync = async (app) => {
  app.get('/bookings', async (req, reply) => {
    // Version 2 implementation (breaking changes)
  });
};
// src/server.ts
app.register(v1Routes, { prefix: '/api/v1' });
app.register(v2Routes, { prefix: '/api/v2' });
Version Lifecycle
v1 (Active) → v2 Released → v1 Deprecated (12 mo) → v1 Sunset