API Versioning Strategy
Version: 1.0.0 Last Updated: 2025-12-13 Status: Production
This document outlines Petunia's API versioning strategy, lifecycle management, and migration guidance for API consumers.
Table of Contents
- Overview
- Versioning Approach
- Version Metadata
- Version Lifecycle
- Adding New API Versions
- Client Migration Guide
- Deprecation Process
- Examples
- Best Practices
Overview
Petunia uses a metadata-based versioning approach where API versions are tracked through:
- File-level JSDoc metadata (
@version,@stability) - Response headers (
X-API-Version,X-Monitoring-Version) - OpenAPI specification versioning
This approach allows us to:
- Track API maturity and stability at a granular level
- Support multiple versions simultaneously when needed
- Provide clear deprecation and sunset timelines
- Maintain backward compatibility during transitions
Versioning Approach
Current Strategy: Metadata-Based Versioning
Each API endpoint includes version metadata in its route handler:
/**
* User Management API
*
* @version 1.2.0
* @lastModified 2025-12-13
* @stability production
* @author Petunia Team
*/
Version Format
We use Semantic Versioning (SemVer) for API endpoints:
- Major version (X.0.0): Breaking changes, incompatible API changes
- Minor version (0.X.0): New features, backward-compatible additions
- Patch version (0.0.X): Bug fixes, backward-compatible fixes
Stability Levels
Each API endpoint declares its stability:
| Stability | Description | Guarantees |
|---|---|---|
| experimental | Early development, API may change frequently | No backward compatibility guarantees |
| unstable | Feature complete but may have breaking changes | Limited backward compatibility |
| stable | Production-ready, follows deprecation policy | Full backward compatibility for 12 months |
| production | Battle-tested, widely used | Full backward compatibility, 18-month deprecation window |
| deprecated | Marked for removal, use alternative | Minimum 6 months before sunset |
Version Metadata
Route Handler Metadata
Every API route should include comprehensive metadata:
/**
* [API Name] - [Brief Description]
*
* @version 2.1.0
* @lastModified 2025-12-13
* @author Petunia Team
* @stability production
*
* @description
* Detailed description of what this API does, including:
* - Primary use cases
* - Key features
* - Performance characteristics
*
* @changelog
* - 2.1.0 (2025-12-13): Added support for cursor-based pagination
* - 2.0.0 (2025-11-01): BREAKING - Changed response format to standardized ApiResponse
* - 1.5.0 (2025-09-15): Added filtering by date range
* - 1.0.0 (2025-06-01): Initial stable release
*
* @deprecated false
* @deprecationDate null
* @sunsetDate null
* @migrationPath null
*/
Response Headers
API responses include version information in headers:
const response = NextResponse.json(data);
// Version information
response.headers.set('X-API-Version', '2.1.0');
response.headers.set('X-API-Stability', 'production');
// Optional: Deprecation warnings
if (isDeprecated) {
response.headers.set('X-API-Deprecated', 'true');
response.headers.set('X-API-Sunset-Date', '2026-06-01');
response.headers.set('X-API-Migration-Path', '/docs/api/migration/v2-to-v3');
}
// Request tracking
response.headers.set('X-Request-Id', requestId);
response.headers.set('X-Response-Time', `${duration}ms`);
return response;
OpenAPI Specification
API versions are documented in /docs/openapi.yaml:
openapi: 3.0.0
info:
title: Petunia API
version: 1.0.0
description: Comprehensive API documentation for the Petunia platform
paths:
/api/users:
get:
summary: List users
x-stability: production
x-version: 2.1.0
x-deprecated: false
Version Lifecycle
Lifecycle Stages
experimental → unstable → stable → production → deprecated → sunset
↓ ↓ ↓ ↓ ↓ ↓
weeks months 6+ months 12+ months 6-18 months removed
Transition Timeline
| Transition | Minimum Duration | Requirements |
|---|---|---|
| experimental → unstable | 2 weeks | Basic functionality complete, initial testing done |
| unstable → stable | 1 month | Full feature set, integration tests, documentation |
| stable → production | 3 months | Production usage, performance validated, zero critical bugs |
| production → deprecated | N/A | New version available, migration guide published |
| deprecated → sunset | 6-18 months | All clients notified, usage < 5% of traffic |
Breaking Change Policy
Breaking changes require a new major version:
- Removing or renaming fields in responses
- Changing field types (e.g., string → number)
- Changing HTTP status codes for existing scenarios
- Requiring new required parameters
- Removing endpoints
Non-breaking changes (minor/patch version):
- Adding new optional parameters
- Adding new fields to responses
- Adding new endpoints
- Fixing bugs that align with documented behavior
- Performance improvements
Adding New API Versions
When to Create a New Version
Create a new major version when you need to:
- Make breaking changes to an existing production API
- Refactor core data models that affect response structure
- Change authentication/authorization mechanisms
- Redesign API architecture (e.g., REST to GraphQL)
Implementation Approaches
Approach 1: Parallel Routes (Recommended)
For major breaking changes, create parallel route structures:
/app/api/
├── users/ # v1 (current production)
│ ├── route.ts # @version 1.5.0
│ └── [id]/
│ └── route.ts
├── v2/
│ └── users/ # v2 (new version)
│ ├── route.ts # @version 2.0.0
│ └── [id]/
│ └── route.ts
Example:
// /app/api/v2/users/route.ts
/**
* User Management API - Version 2
*
* @version 2.0.0
* @stability stable
* @changelog
* - 2.0.0 (2025-12-01): Complete rewrite with new response format
*
* @migrationFrom /api/users (v1)
* @migrationGuide /docs/api/migration/users-v1-to-v2
*/
export async function GET(request: NextRequest) {
// New implementation with breaking changes
const users = await fetchUsers();
return NextResponse.json({
data: users,
meta: {
version: '2.0.0',
requestId: generateRequestId(),
timestamp: new Date().toISOString()
}
});
}
Approach 2: Header-Based Versioning (Future Enhancement)
For fine-grained versioning without URL changes:
// Middleware to parse Accept-Version header
export async function GET(request: NextRequest) {
const requestedVersion = request.headers.get('Accept-Version') || '1.0';
if (requestedVersion.startsWith('2.')) {
return handleV2Request(request);
} else {
return handleV1Request(request);
}
}
Client request:
curl https://app.petunia.gardenpatch.xyz/api/users \
-H "Accept-Version: 2.0" \
-H "Authorization: Bearer token"
Approach 3: In-Place Evolution (Minor Changes)
For backward-compatible changes, evolve the existing route:
/**
* @version 1.6.0 (was 1.5.0)
* @changelog
* - 1.6.0 (2025-12-13): Added optional 'include' parameter
*/
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const include = searchParams.get('include')?.split(',') || [];
// Backward compatible: new parameter is optional
const users = await fetchUsers({ include });
const response = NextResponse.json({ data: users });
response.headers.set('X-API-Version', '1.6.0');
return response;
}
Version Migration Checklist
When creating a new major version:
- Create migration guide documentation
- Update OpenAPI specification
- Add deprecation headers to old version
- Set sunset date (minimum 6 months out)
- Notify API consumers via email
- Add migration path to response headers
- Update client SDKs/libraries
- Monitor usage of old version
- Create automated migration tools if possible
- Update API documentation site
Client Migration Guide
Discovering API Versions
Check Response Headers
All API responses include version information:
curl -I https://app.petunia.gardenpatch.xyz/api/users
HTTP/1.1 200 OK
X-API-Version: 1.5.0
X-API-Stability: production
X-Request-Id: req_abc123
Check for Deprecation Warnings
Deprecated endpoints include warning headers:
HTTP/1.1 200 OK
X-API-Version: 1.5.0
X-API-Deprecated: true
X-API-Sunset-Date: 2026-06-01
X-API-Migration-Path: /docs/api/migration/users-v1-to-v2
Sunset: Sat, 01 Jun 2026 00:00:00 GMT
Migration Process
Step 1: Review Migration Guide
When you receive a deprecation notice, review the migration guide:
/docs/api/migration/[endpoint]-v[old]-to-v[new]
Example: /docs/api/migration/users-v1-to-v2.md
Step 2: Test Against New Version
Use staging/sandbox environment to test new version:
# Old version (deprecated)
curl https://app.petunia.gardenpatch.xyz/api/users
# New version (recommended)
curl https://app.petunia.gardenpatch.xyz/api/v2/users
Step 3: Update Client Code
Update your application to use the new endpoint:
// Before (v1)
const response = await fetch('/api/users');
const users = await response.json();
// After (v2)
const response = await fetch('/api/v2/users');
const { data, meta } = await response.json();
const users = data;
Step 4: Deploy and Monitor
Deploy your changes and monitor for errors:
- Check error rates in your application
- Verify response format matches expectations
- Test edge cases and error scenarios
- Monitor performance metrics
Step 5: Verify Migration
Confirm you're no longer calling deprecated endpoints:
# Check response headers
curl -I https://app.petunia.gardenpatch.xyz/api/v2/users
HTTP/1.1 200 OK
X-API-Version: 2.0.0
X-API-Stability: production
X-API-Deprecated: false
Handling Breaking Changes
Response Format Changes
v1 Format:
{
"users": [...],
"total": 100
}
v2 Format:
{
"data": [...],
"meta": {
"pagination": {
"total": 100,
"page": 1,
"limit": 20
}
}
}
Migration Strategy:
function normalizeResponse(version: string, response: any) {
if (version.startsWith('1.')) {
return {
data: response.users,
meta: { pagination: { total: response.total } }
};
}
return response;
}
Field Renames
v1:
{
"id": "user_123",
"username": "john_doe",
"created": "2025-01-01"
}
v2:
{
"id": "user_123",
"handle": "john_doe",
"createdAt": "2025-01-01T00:00:00Z"
}
Migration:
interface UserV1 {
username: string;
created: string;
}
interface UserV2 {
handle: string;
createdAt: string;
}
function migrateUser(v1User: UserV1): UserV2 {
return {
handle: v1User.username,
createdAt: new Date(v1User.created).toISOString()
};
}
Deprecation Process
Timeline
Announcement → Deprecation → Sunset → Removal
↓ ↓ ↓ ↓
3 months 6-18 months shutdown cleanup
Announcement Phase (3 months before deprecation)
Actions:
- Publish deprecation notice in documentation
- Send email to API consumers
- Add deprecation notice to API changelog
- Create migration guide
- Update SDK documentation
Communication:
## Deprecation Notice: /api/users (v1)
**Effective Date**: 2026-01-01
**Sunset Date**: 2026-06-01
**Replacement**: /api/v2/users
We are deprecating version 1 of the Users API. Please migrate to v2
by June 1, 2026. See migration guide for details.
Deprecation Phase (6-18 months)
Actions:
- Add deprecation headers to all responses
- Log deprecation warnings in server logs
- Monitor usage metrics
- Provide migration assistance
- Send periodic reminder emails
Response Headers:
response.headers.set('X-API-Deprecated', 'true');
response.headers.set('X-API-Sunset-Date', '2026-06-01');
response.headers.set('Sunset', 'Sat, 01 Jun 2026 00:00:00 GMT'); // RFC 8594
response.headers.set('X-API-Migration-Path', '/docs/api/migration/users-v1-to-v2');
response.headers.set('Link', '</api/v2/users>; rel="successor-version"');
Logging:
if (isDeprecatedEndpoint) {
logger.warn('Deprecated API endpoint accessed', {
endpoint: request.url,
version: '1.5.0',
sunsetDate: '2026-06-01',
clientId: getClientId(request),
userAgent: request.headers.get('user-agent')
});
}
Sunset Phase (removal date)
Actions:
- Stop accepting new requests to deprecated endpoint
- Return HTTP 410 Gone status
- Provide clear error message with migration path
- Monitor for stragglers
- Offer emergency migration support
Sunset Response:
export async function GET(request: NextRequest) {
return NextResponse.json(
{
error: {
code: 'API_SUNSET',
message: 'This API version has been sunset. Please migrate to v2.',
details: {
sunsetDate: '2026-06-01',
replacementEndpoint: '/api/v2/users',
migrationGuide: '/docs/api/migration/users-v1-to-v2'
}
}
},
{
status: 410, // Gone
headers: {
'X-API-Deprecated': 'true',
'X-API-Sunset': 'true',
'X-API-Migration-Path': '/docs/api/migration/users-v1-to-v2',
'Link': '</api/v2/users>; rel="successor-version"'
}
}
);
}
Removal Phase (cleanup)
Actions:
- Remove route handler files
- Clean up related code and tests
- Archive migration documentation
- Update OpenAPI specification
- Send final communication to clients
Examples
Example 1: Stable Production Endpoint
// /app/api/leads/route.ts
/**
* Lead Management API
*
* @version 3.2.1
* @lastModified 2025-12-10
* @stability production
* @author Petunia Team
*
* @changelog
* - 3.2.1 (2025-12-10): Fix pagination cursor encoding issue
* - 3.2.0 (2025-11-15): Add support for custom field filtering
* - 3.1.0 (2025-10-01): Add bulk operations endpoint
* - 3.0.0 (2025-06-01): Standardized response format (BREAKING)
*/
import { NextRequest, NextResponse } from 'next/server';
import { withAuth } from '@/lib/auth/withAuth';
import { apiSuccess, apiError } from '@/lib/utils/api-response';
export const GET = withAuth(async (request: NextRequest) => {
const startTime = Date.now();
const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
try {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '20');
const leads = await fetchLeads({ page, limit });
const response = NextResponse.json({
data: leads.data,
meta: {
requestId,
timestamp: new Date().toISOString(),
pagination: {
total: leads.total,
page,
limit,
hasMore: page * limit < leads.total
}
}
});
// Version headers
response.headers.set('X-API-Version', '3.2.1');
response.headers.set('X-API-Stability', 'production');
response.headers.set('X-Request-Id', requestId);
response.headers.set('X-Response-Time', `${Date.now() - startTime}ms`);
return response;
} catch (error) {
return apiError('Failed to fetch leads', { requestId, error });
}
});
Example 2: Deprecated Endpoint
// /app/api/contacts/route.ts
/**
* Contact Management API - DEPRECATED
*
* @version 1.5.0
* @stability deprecated
* @deprecated true
* @deprecationDate 2025-12-01
* @sunsetDate 2026-06-01
* @migrationPath /docs/api/migration/contacts-to-leads
* @successorEndpoint /api/v2/leads
*
* @changelog
* - 1.5.0 (2025-12-01): DEPRECATED - Migrate to /api/v2/leads
* - 1.4.0 (2025-09-01): Added email validation
*/
import { NextRequest, NextResponse } from 'next/server';
import { createLogger } from '@/lib/utils/logging';
const logger = createLogger('contacts-api-deprecated');
export async function GET(request: NextRequest) {
// Log deprecated API usage
logger.warn('Deprecated API accessed', {
endpoint: '/api/contacts',
version: '1.5.0',
sunsetDate: '2026-06-01',
clientIP: request.headers.get('x-forwarded-for'),
userAgent: request.headers.get('user-agent')
});
// Still process the request during deprecation period
const contacts = await fetchContacts();
const response = NextResponse.json({
data: contacts,
deprecationNotice: {
deprecated: true,
sunsetDate: '2026-06-01',
message: 'This endpoint is deprecated. Please migrate to /api/v2/leads',
migrationGuide: '/docs/api/migration/contacts-to-leads'
}
});
// Deprecation headers
response.headers.set('X-API-Version', '1.5.0');
response.headers.set('X-API-Stability', 'deprecated');
response.headers.set('X-API-Deprecated', 'true');
response.headers.set('X-API-Sunset-Date', '2026-06-01');
response.headers.set('Sunset', 'Sat, 01 Jun 2026 00:00:00 GMT');
response.headers.set('X-API-Migration-Path', '/docs/api/migration/contacts-to-leads');
response.headers.set('Link', '</api/v2/leads>; rel="successor-version"');
// Warning header (RFC 7234)
response.headers.set('Warning', '299 - "This API version is deprecated and will be sunset on 2026-06-01"');
return response;
}
Example 3: Experimental Feature
// /app/api/insights/contextual/route.ts
/**
* Contextual AI Insights - EXPERIMENTAL
*
* @version 0.2.0
* @stability experimental
* @lastModified 2025-12-13
*
* @description
* EXPERIMENTAL: This API is under active development and may change
* without notice. Do not use in production applications.
*
* @changelog
* - 0.2.0 (2025-12-13): Added sentiment analysis
* - 0.1.0 (2025-12-01): Initial experimental release
*/
export async function POST(request: NextRequest) {
const response = NextResponse.json({
data: insights,
warning: 'EXPERIMENTAL: This API may change without notice'
});
response.headers.set('X-API-Version', '0.2.0');
response.headers.set('X-API-Stability', 'experimental');
response.headers.set('X-Feature-Flag', 'contextual-insights');
return response;
}
Example 4: Version-Specific Response Formats
// /app/api/users/route.ts
/**
* User Management API - Multi-version support
*
* @version 2.0.0
* @stability production
*/
export async function GET(request: NextRequest) {
const acceptVersion = request.headers.get('Accept-Version') || '2.0';
const users = await fetchUsers();
// v1 format (deprecated)
if (acceptVersion.startsWith('1.')) {
return NextResponse.json({
users: users.map(u => ({
id: u.id,
name: u.name,
email: u.email,
created: u.createdAt
})),
total: users.length
}, {
headers: {
'X-API-Version': '1.0.0',
'X-API-Deprecated': 'true',
'X-API-Sunset-Date': '2026-06-01'
}
});
}
// v2 format (current)
return NextResponse.json({
data: users,
meta: {
version: '2.0.0',
pagination: {
total: users.length,
page: 1,
limit: 20
}
}
}, {
headers: {
'X-API-Version': '2.0.0',
'X-API-Stability': 'production'
}
});
}
Best Practices
For API Developers
- Always include version metadata in route handlers
- Use semantic versioning for version numbers
- Document breaking changes in changelog
- Provide migration guides for major version changes
- Monitor deprecated endpoint usage before sunset
- Set realistic sunset timelines (minimum 6 months)
- Communicate early and often with API consumers
- Test backward compatibility thoroughly
- Version response formats consistently
- Use stability markers to set expectations
For API Consumers
- Monitor response headers for deprecation notices
- Subscribe to API changelog notifications
- Test against new versions in sandbox before production
- Implement version negotiation in clients
- Handle deprecation warnings gracefully
- Plan migrations early (don't wait until sunset)
- Cache version information appropriately
- Log API errors with version context
- Use request IDs for debugging
- Follow migration guides carefully
Versioning Anti-Patterns
Don't:
- ❌ Make breaking changes without version bump
- ❌ Sunset APIs without migration path
- ❌ Use version numbers inconsistently
- ❌ Remove deprecated endpoints immediately
- ❌ Skip migration documentation
- ❌ Ignore deprecation warnings
- ❌ Version every minor change
- ❌ Keep too many versions active simultaneously
- ❌ Change versioning strategy mid-project
- ❌ Forget to update OpenAPI spec
Do:
- ✅ Plan breaking changes carefully
- ✅ Provide clear migration paths
- ✅ Use semantic versioning consistently
- ✅ Give adequate deprecation notice (6+ months)
- ✅ Document all changes thoroughly
- ✅ Monitor and respond to deprecation warnings
- ✅ Version only when necessary
- ✅ Maintain 1-2 active versions maximum
- ✅ Stick to versioning strategy
- ✅ Keep documentation synchronized
Future Enhancements
Planned Improvements
-
Automated Version Detection Middleware
- Centralized version header injection
- Automatic deprecation warning enforcement
- Usage analytics by version
-
GraphQL Versioning
- Schema versioning for GraphQL endpoints
- Deprecated field tracking
- Migration tooling
-
SDK Auto-Generation
- TypeScript SDK from OpenAPI spec
- Version-aware client libraries
- Automatic migration scripts
-
Version Analytics Dashboard
- Track version adoption rates
- Monitor deprecated endpoint usage
- Forecast sunset readiness
-
Content Negotiation
- Accept-Version header support
- Automatic version routing
- Response format negotiation
Related Documentation
- ARCHITECTURE.md - System architecture overview
- ARCHITECTURE_PLAN.md - Migration plan (see P3-4)
- openapi.yaml - OpenAPI specification
- GIT_FLOW.md - Version control workflow
- PRE_DEPLOYMENT_CHECKLIST.md - Deployment process
Support
For questions about API versioning:
- Email: support@gardenpatch.xyz
- Documentation: https://docs.petunia.gardenpatch.xyz/api/versioning
- Changelog: https://petunia.gardenpatch.xyz/changelog
Maintained by: Petunia Platform Team Review Cycle: Quarterly Next Review: 2026-03-13