Loading learning content...
Creating new API versions is only half the versioning story. The other half—often neglected—is deprecation: the disciplined process of retiring old versions. Without a clear deprecation strategy, you accumulate version debt: a growing number of supported versions that drain engineering resources, complicate testing, and fragment your consumer ecosystem.
Deprecation is where API versioning meets human psychology. Done poorly, it feels like abandonment—consumers lose trust and become reluctant to invest in your platform. Done well, it demonstrates maturity—consumers see a partner who evolves thoughtfully while respecting their investment.
This page provides the complete framework for deprecating API versions without damaging the relationships that make your API valuable.
By the end of this page, you will understand deprecation lifecycle management, know how to communicate deprecation effectively, implement technical deprecation mechanisms, and manage the human side of sunsetting API versions that people depend upon.
Effective deprecation follows a structured lifecycle with clear phases, each serving a specific purpose in the transition:
Phase 1: Planning (Internal)
Before any public announcement, determine the deprecation timeline, migration path, and resource allocation. Key decisions:
Phase 2: Announcement
Public notice that a version will be deprecated. This is a promise—once made, changing it damages trust significantly.
Phase 3: Active Deprecation
The deprecated version continues working but with increasing friction (warnings, reduced features, lower limits). Consumers are actively encouraged to migrate.
Phase 4: End of Life (Sunset)
The deprecated version stops working. Requests return errors directing to the new version.
Phase 5: Removal
Code supporting the old version is removed entirely. Documentation may be archived.
| Phase | Duration | Consumer Impact | API Provider Actions |
|---|---|---|---|
| Planning | 1-3 months | None (internal) | Develop migration guides, train support, prepare tooling |
| Announcement | Day 0 | Notification only | Publish timeline, documentation, inform key stakeholders |
| Active Deprecation | 6-18 months | Warnings, reduced support | Deprecation headers, usage monitoring, migration support |
| End of Life | Day N | Version stops working | Return sunset errors, redirect to docs, final notifications |
| Removal | 1-3 months after sunset | None (already migrated) | Remove code, archive documentation, close related issues |
Once you announce a sunset date, changing it (especially shortening it) destroys trust. Extending deadlines is generally acceptable if necessary, but shortening them—even with 'good reasons'—signals unreliability. Plan conservatively and stick to your commitments.
Deprecation communication must be proactive, multi-channel, and progressively more urgent as the sunset date approaches. Consumers have varying attention spans—some read every email, others ignore everything until their integration breaks.
Communication Channels:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
// Deprecation Communication Schedule interface DeprecationCommunicationPlan { sunsetDate: Date; communications: ScheduledCommunication[];} interface ScheduledCommunication { timing: string; // When to send channels: string[]; // Where to communicate urgency: 'info' | 'warning' | 'critical'; content: string; // Key message} const communicationPlan: DeprecationCommunicationPlan = { sunsetDate: new Date('2024-12-01'), communications: [ { timing: '6 months before', channels: ['email', 'blog', 'docs', 'dashboard'], urgency: 'info', content: `API v1 will be deprecated on Dec 1, 2024. Migration guide: [link]. New v2 features: [link]. Start planning your migration today.`, }, { timing: '3 months before', channels: ['email', 'dashboard', 'api-headers'], urgency: 'warning', content: `Reminder: API v1 sunset in 90 days. 80% of consumers have migrated. Migration support: [link].`, }, { timing: '1 month before', channels: ['email', 'dashboard', 'api-headers', 'status-page'], urgency: 'warning', content: `ACTION REQUIRED: API v1 ends in 30 days. If you're still on v1, migrate now or contact support.`, }, { timing: '2 weeks before', channels: ['email', 'direct-outreach', 'dashboard', 'api-headers'], urgency: 'critical', content: `URGENT: API v1 ends in 14 days. Your integration will stop working. Migrate immediately.`, }, { timing: '1 week before', channels: ['email', 'sms', 'direct-outreach', 'all'], urgency: 'critical', content: `FINAL NOTICE: API v1 ends in 7 days. After Dec 1, requests will return 410 Gone.`, }, { timing: '1 day before', channels: ['email', 'sms', 'direct-outreach'], urgency: 'critical', content: `TOMORROW: API v1 shuts down. Last chance to migrate. Support: [emergency-contact].`, }, { timing: 'sunset day', channels: ['email', 'blog', 'status-page'], urgency: 'info', content: `API v1 has been retired. Thank you for migrating. If you need help, contact support.`, }, ],};Frame migration positively. Highlight new features, better performance, improved developer experience. The goal is for consumers to want to migrate, not feel forced. Successful deprecation ends with consumers grateful for the push to upgrade, not resentful about losing something that worked.
Beyond communication, technical mechanisms signal deprecation programmatically, enabling automated tools and SDKs to detect and warn about deprecated API usage.
HTTP Deprecation Headers (RFC 8594)
Standard headers communicate deprecation status in every API response:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
// HTTP Deprecation Headers Implementation import { Request, Response, NextFunction } from 'express'; interface VersionInfo { version: string; deprecated: boolean; deprecatedSince?: Date; sunsetDate?: Date; successorVersion?: string; migrationGuide?: string;} function deprecationHeadersMiddleware( req: Request, res: Response, next: NextFunction) { const versionInfo = getVersionInfo(req.apiVersion); // Always include the version being used res.setHeader('X-API-Version', versionInfo.version); if (versionInfo.deprecated) { // RFC 8594: Deprecation header // Value is timestamp when deprecation was announced res.setHeader('Deprecation', versionInfo.deprecatedSince.toISOString()); // RFC 8594: Sunset header // Value is timestamp when API will stop working res.setHeader('Sunset', versionInfo.sunsetDate.toISOString()); // Link headers for machine-readable deprecation info const links = [ // Link to deprecation documentation `<${versionInfo.migrationGuide}>; rel="deprecation"; type="text/html"`, // Link to successor version `</${versionInfo.successorVersion}>; rel="successor-version"`, // Link to latest version '</v3>; rel="latest-version"', ]; res.setHeader('Link', links.join(', ')); // Custom warning header with human-readable message res.setHeader('X-API-Warn', `API ${versionInfo.version} is deprecated and will be removed on ` + `${versionInfo.sunsetDate.toDateString()}. ` + `Please migrate to ${versionInfo.successorVersion}.` ); } next();} // Example response headers for deprecated API://// HTTP/1.1 200 OK// X-API-Version: v1// Deprecation: Mon, 01 Jul 2024 00:00:00 GMT// Sunset: Sun, 01 Dec 2024 00:00:00 GMT// Link: </docs/migrate-v1-v2>; rel="deprecation"; type="text/html",// </v2>; rel="successor-version",// </v3>; rel="latest-version"// X-API-Warn: API v1 is deprecated and will be removed on Sun Dec 01 2024.// Please migrate to v2.SDK Integration
SDKs can parse deprecation headers and warn developers:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
// SDK Deprecation Warning Integration class APIClient { private warnedAboutDeprecation = false; private async request<T>(endpoint: string, options?: RequestOptions): Promise<T> { const response = await this.httpClient.request(`${this.baseUrl}${endpoint}`, { ...options, headers: { ...this.defaultHeaders, ...options?.headers, }, }); // Check for deprecation headers this.handleDeprecationHeaders(response.headers); return response.json(); } private handleDeprecationHeaders(headers: Headers): void { const deprecation = headers.get('Deprecation'); const sunset = headers.get('Sunset'); if (deprecation && !this.warnedAboutDeprecation) { const sunsetDate = sunset ? new Date(sunset) : null; const daysRemaining = sunsetDate ? Math.ceil((sunsetDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null; console.warn(`⚠️ DEPRECATION WARNING ⚠️`); console.warn(`The API version you're using is deprecated.`); if (sunsetDate) { console.warn(`Sunset date: ${sunsetDate.toDateString()}`); if (daysRemaining && daysRemaining > 0) { console.warn(`Days remaining: ${daysRemaining}`); } else { console.error(`⛔ SUNSET DATE HAS PASSED - Migrate immediately!`); } } const migrationLink = this.parseLinkHeader(headers.get('Link'), 'deprecation'); if (migrationLink) { console.warn(`Migration guide: ${migrationLink}`); } // Only warn once per client instance this.warnedAboutDeprecation = true; // Optionally, emit event for monitoring this.emit('deprecation', { deprecation, sunset }); } } private parseLinkHeader(header: string | null, rel: string): string | null { if (!header) return null; const links = header.split(',').map(link => { const match = link.match(/<([^>]+)>;\s*rel="([^"]+)"/); return match ? { url: match[1], rel: match[2] } : null; }); return links.find(l => l?.rel === rel)?.url ?? null; }} // Developer sees in console:// ⚠️ DEPRECATION WARNING ⚠️// The API version you're using is deprecated.// Sunset date: Sun Dec 01 2024// Days remaining: 87// Migration guide: https://docs.example.com/migrate-v1-v2Some APIs increase friction as sunset approaches: slower response times, reduced rate limits, or disabled non-essential features. This encourages migration while maintaining core functionality. Use sparingly—it feels punitive and can damage relationships. Only apply to consumers who haven't engaged with migration outreach.
Successful deprecation requires investment in migration support. The easier you make migration, the faster consumers move, and the sooner you can retire the old version.
Migration Documentation
Comprehensive documentation is the foundation of migration support:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
# Migration Guide: v1 to v2 ## OverviewBrief summary of why v2 exists and benefits of migrating. ## Timeline- **Now**: v2 available, v1 deprecated- **Dec 1, 2024**: v1 sunset- **Jan 1, 2025**: v1 code removed ## Breaking Changes Summary| Change | v1 | v2 | Migration ||--------|----|----|-----------|| User name field | `name` | `firstName`, `lastName` | Split on space or request user update || Price format | cents (int) | dollars (decimal) | Divide by 100, update displays || Auth header | X-API-Key | Authorization: Bearer | Update header name | ## Step-by-Step Migration ### Step 1: Update Authentication```diff- headers: { 'X-API-Key': apiKey }+ headers: { 'Authorization': `Bearer ${apiKey}` }``` ### Step 2: Update User Model```diff- interface User { name: string; }+ interface User { firstName: string; lastName: string; }``` ### Step 3: Update Price Handling```diff- displayPrice(product.price / 100) // Convert cents to dollars+ displayPrice(product.price) // Already in dollars``` ## Testing Your Migration1. Set `API-Version: 2` header2. Run your test suite3. Compare outputs between v1 and v2 ## Common Issues ### Error: "Unknown field 'name'"**Cause**: v2 removed the combined `name` field**Solution**: Use `firstName` and `lastName` instead ### Error: "Invalid authentication" **Cause**: v2 requires Bearer token format**Solution**: Change header from `X-API-Key` to `Authorization: Bearer` ## Getting Help- Slack: #api-migration-support- Email: api-support@example.com- Office Hours: Thursdays 2-4pm PTMigration Tooling
Automated tools significantly reduce migration burden:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
// Migration Support Tools // 1. COMPATIBILITY MODE / SHIM LAYER// Accept old format, return old format, but run through new code function createOrderCompat(request: CreateOrderV1Request): OrderV1Response { // Transform v1 request to v2 format const v2Request: CreateOrderV2Request = { customerId: request.customer_id, // snake_case → camelCase items: request.line_items.map(item => ({ productId: item.product_id, quantity: item.qty, })), paymentMethod: request.payment?.method ?? 'card', }; // Call v2 implementation const v2Response = createOrderV2(v2Request); // Transform v2 response to v1 format return { order_id: v2Response.orderId, total_cents: Math.round(v2Response.total * 100), // dollars → cents status: v2Response.status.toLowerCase(), };} // 2. RESPONSE COMPARISON TOOL// Help consumers verify migration correctness app.get('/migration/compare/:endpoint', async (req, res) => { const endpoint = req.params.endpoint; const testPayload = req.body; // Call both versions const [v1Response, v2Response] = await Promise.all([ callV1(endpoint, testPayload), callV2(endpoint, testPayload), ]); // Calculate differences const diff = calculateDiff(v1Response, v2Response); res.json({ v1Response, v2Response, differences: diff.changes, breakingChanges: diff.breaking, expectedDifferences: getExpectedDifferences(endpoint), migrationStatus: diff.breaking.length === 0 ? 'COMPATIBLE' : 'ACTION_REQUIRED', });}); // 3. MIGRATION PROGRESS DASHBOARD// Track overall migration progress async function getMigrationStats(): Promise<MigrationStats> { const last30Days = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); const stats = await db.query(` SELECT api_version, COUNT(DISTINCT client_id) as unique_clients, COUNT(*) as total_requests FROM api_requests WHERE timestamp > $1 GROUP BY api_version `, [last30Days]); const v1Clients = stats.find(s => s.api_version === 'v1')?.unique_clients ?? 0; const v2Clients = stats.find(s => s.api_version === 'v2')?.unique_clients ?? 0; return { totalClients: v1Clients + v2Clients, migratedClients: v2Clients, percentMigrated: (v2Clients / (v1Clients + v2Clients)) * 100, v1RequestVolume: stats.find(s => s.api_version === 'v1')?.total_requests ?? 0, trend: await calculateMigrationTrend(), };}When the sunset date arrives, the deprecated version must stop working. But "stop working" can be implemented with varying degrees of grace:
Option 1: Hard Shutdown
Return 410 Gone with a helpful error body. Simple, clear, but potentially disruptive.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
// Sunset Implementation Options // Option 1: HARD SHUTDOWN - Return 410 Gonefunction hardShutdownMiddleware(req: Request, res: Response, next: NextFunction) { if (isVersionSunset(req.apiVersion)) { return res.status(410).json({ error: 'API Version Retired', code: 'VERSION_SUNSET', message: `API ${req.apiVersion} was retired on ${getSunsetDate(req.apiVersion)}.`, action: { required: 'Migrate to a supported version', currentVersion: req.apiVersion, supportedVersions: getSupportedVersions(), latestVersion: getLatestVersion(), migrationGuide: getMigrationGuideUrl(req.apiVersion), }, support: { documentation: 'https://docs.example.com/api/versions', email: 'api-support@example.com', }, }); } next();} // Option 2: GRADUAL SHUTDOWN - Increasing error ratefunction gradualShutdownMiddleware(req: Request, res: Response, next: NextFunction) { if (isVersionSunset(req.apiVersion)) { const daysSinceSunset = getDaysSinceSunset(req.apiVersion); // Week 1: 10% of requests fail // Week 2: 50% fail // Week 3: 90% fail // Week 4+: 100% fail const errorRate = Math.min(1, 0.1 * Math.pow(2, daysSinceSunset / 7)); if (Math.random() < errorRate) { return res.status(410).json({ error: 'API Version Retired', message: `API ${req.apiVersion} is being phased out. ` + `${Math.round((1 - errorRate) * 100)}% success rate remaining.`, migrateNow: true, urgency: 'critical', }); } } next();} // Option 3: REDIRECT TO DOCSfunction redirectMiddleware(req: Request, res: Response, next: NextFunction) { if (isVersionSunset(req.apiVersion)) { // 301 redirect to migration documentation const migrationUrl = `https://docs.example.com/migrate/${req.apiVersion}`; // For API calls, return JSON with redirect info if (req.accepts('json')) { return res.status(410).json({ error: 'API Version Retired', redirect: migrationUrl, }); } // For browser requests, actually redirect return res.redirect(301, migrationUrl); } next();} // Option 4: READ-ONLY MODE// Allow reads during grace period, block writesfunction readOnlyModeMiddleware(req: Request, res: Response, next: NextFunction) { if (isVersionSunset(req.apiVersion)) { const isReadOnly = ['GET', 'HEAD', 'OPTIONS'].includes(req.method); if (isReadOnly) { res.setHeader('X-API-Warn', 'Read-only mode. Writes disabled. Migrate to continue.'); return next(); } return res.status(410).json({ error: 'Write Operations Disabled', message: 'This API version is in read-only mode. Migrate to perform writes.', }); } next();}| Strategy | Consumer Impact | Best For | Risk |
|---|---|---|---|
| Hard Shutdown | Immediate failure | Clear deadlines, prepared consumers | Stranded consumers if migration incomplete |
| Gradual Shutdown | Increasing failures | Large consumer base, gentle transition | Extended uncertainty, complex monitoring |
| Redirect | Clear guidance | Browser-accessible APIs | May confuse programmatic clients |
| Read-Only Mode | Partial functionality | Data access priority | Consumers may delay full migration |
Consider a short (1-2 week) grace period after the announced sunset where the API still works but returns aggressive warnings. This catches consumers who missed the deadline while maintaining pressure to complete migration. Announce this grace period as a possibility, not a promise.
Despite best efforts, some consumers won't migrate by the deadline. Handling holdouts requires balancing firm boundaries with business pragmatism.
Why Consumers Don't Migrate
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
// Holdout Identification and Handling interface HoldoutConsumer { clientId: string; contactEmail: string; organizationName: string; apiVersion: string; lastRequestDate: Date; requestVolume: 'high' | 'medium' | 'low'; tier: 'enterprise' | 'business' | 'free'; engagementHistory: EngagementEvent[];} async function identifyHoldouts(targetVersion: string, sunsetDate: Date): Promise<HoldoutConsumer[]> { // Find consumers still on deprecated version as sunset approaches const consumers = await db.query(` SELECT c.client_id, c.contact_email, c.organization_name, c.tier, MAX(r.timestamp) as last_request, COUNT(*) as request_count FROM api_clients c JOIN api_requests r ON c.client_id = r.client_id WHERE r.api_version = $1 AND r.timestamp > NOW() - INTERVAL '30 days' GROUP BY c.client_id, c.contact_email, c.organization_name, c.tier `, [targetVersion]); return consumers.map(c => ({ clientId: c.client_id, contactEmail: c.contact_email, organizationName: c.organization_name, apiVersion: targetVersion, lastRequestDate: c.last_request, requestVolume: categorizeVolume(c.request_count), tier: c.tier, engagementHistory: await getEngagementHistory(c.client_id), }));} // Prioritized outreach for holdoutsasync function conductHoldoutOutreach(holdouts: HoldoutConsumer[]) { // Sort by importance: tier, volume, recency const prioritized = holdouts.sort((a, b) => { const tierOrder = { enterprise: 0, business: 1, free: 2 }; const volumeOrder = { high: 0, medium: 1, low: 2 }; if (tierOrder[a.tier] !== tierOrder[b.tier]) { return tierOrder[a.tier] - tierOrder[b.tier]; } return volumeOrder[a.requestVolume] - volumeOrder[b.requestVolume]; }); for (const consumer of prioritized) { if (consumer.tier === 'enterprise') { // Personal outreach from account management await assignAccountManager(consumer); await scheduleCall(consumer); } else if (consumer.requestVolume === 'high') { // Direct email from engineering await sendPersonalizedEmail(consumer); } else { // Standard automated escalation await sendEscalationEmail(consumer); } }} // Exception process for must-keep consumersinterface MigrationException { consumerId: string; reason: string; approvedBy: string; extendedSunset: Date; conditions: string[]; // What consumer must do} async function grantException( consumerId: string, reason: string, extensionDays: number): Promise<MigrationException> { // Exceptions must be executive-approved, documented, conditional const exception: MigrationException = { consumerId, reason, approvedBy: await getVPApproval(consumerId, reason), extendedSunset: new Date(Date.now() + extensionDays * 24 * 60 * 60 * 1000), conditions: [ 'Complete migration plan submitted within 2 weeks', 'Weekly migration progress updates', 'No new features on deprecated API', ], }; await recordException(exception); return exception;}Granting exceptions creates precedent. Other consumers learn they can ignore deadlines. Limit exceptions to genuinely exceptional cases (contractual obligations, major revenue at risk). Document extensively and require concrete migration commitments in return. Every exception weakens your deprecation credibility.
A published deprecation policy sets expectations before consumers integrate. It transforms deprecation from an unexpected event into an understood contract term.
Key Policy Elements:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
# API Deprecation Policy Last Updated: January 2024 ## Overview Example company is committed to API stability while continuously improving our platform. This policy describes how we deprecate API versions and what you can expect during transitions. ## Version Support Commitments ### Minimum Support Period- **Major versions (v1, v2)**: Minimum 24 months from release- **After deprecation announcement**: Minimum 12 months before sunset ### Support Levels| Version Status | Support Level | SLA ||----------------|---------------|-----|| Current | Full support, new features | 99.9% uptime || Deprecated | Security fixes only | 99.9% uptime || Sunset | No support | None | ## Deprecation Timeline When we deprecate an API version: 1. **Announcement (Month 0)** - Blog post, email to all registered developers - Documentation updated with deprecation notice - New version available for migration 2. **Active Deprecation (Months 1-11)** - Deprecation headers on all responses - Quarterly migration reminders - Migration support available 3. **Final Notice Period (Month 11-12)** - Weekly email reminders - Direct outreach to active consumers - Escalated dashboard warnings 4. **Sunset (Month 12)** - Version returns 410 Gone - Redirect to migration documentation ## What We Will NOT Do - ❌ Shorten announced timelines- ❌ Sunset without 12 months notice- ❌ Remove deprecation documentation- ❌ Ignore migration support requests ## What We Expect From You - ✅ Subscribe to developer mailing list- ✅ Monitor deprecation headers- ✅ Plan migrations within support periods- ✅ Test integrations before sunset dates ## Exceptions Expedited deprecation (less than 12 months) may occur only for:- Critical security vulnerabilities- Legal or compliance requirements- Third-party dependency end-of-life In such cases, we will:- Provide maximum feasible notice- Offer migration assistance- Consider case-by-case extensions ## Contact Questions about deprecation: api-support@example.comDeprecation schedule: https://docs.example.com/api/deprecationA clear, generous deprecation policy is a selling point. Enterprise evaluators specifically ask about versioning and deprecation. 'We guarantee 24 months of support after deprecation announcement' wins deals. Your policy should be prominently featured in sales materials and developer documentation.
Effective deprecation requires visibility into migration progress. Monitor these metrics throughout the deprecation lifecycle:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
// Deprecation Monitoring Dashboard interface DeprecationDashboard { // MIGRATION PROGRESS migrationProgress: { totalConsumers: number; migratedConsumers: number; percentMigrated: number; migrationTrend: 'accelerating' | 'steady' | 'stalling'; }; // TRAFFIC ANALYSIS trafficByVersion: { version: string; requestCount: number; percentOfTotal: number; uniqueConsumers: number; }[]; // HOLDOUT DETAILS holdoutAnalysis: { consumersByTier: Record<string, number>; consumersByVolume: Record<string, number>; lastContactAttempt: Record<string, Date>; engagementStatus: Record<string, 'responsive' | 'unresponsive' | 'unknown'>; }; // TIMELINE timelineStatus: { currentPhase: 'active' | 'deprecated' | 'final-notice' | 'sunset'; daysRemaining: number; projectedMigrationComplete: Date | null; atRiskConsumers: number; // Won't finish at current pace };} // Automated alerts based on metricsconst alertRules = [ { name: 'Migration Stalled', condition: (d: DeprecationDashboard) => d.migrationProgress.migrationTrend === 'stalling' && d.timelineStatus.daysRemaining < 60, severity: 'critical', action: 'Escalate to leadership, increase outreach', }, { name: 'High-Value Holdouts', condition: (d: DeprecationDashboard) => d.holdoutAnalysis.consumersByTier['enterprise'] > 0 && d.timelineStatus.daysRemaining < 30, severity: 'critical', action: 'Assign account managers, offer direct support', }, { name: 'Unresponsive Consumers', condition: (d: DeprecationDashboard) => d.holdoutAnalysis.engagementStatus['unresponsive'] > 10, severity: 'warning', action: 'Try alternative contact methods, escalate outreach', }, { name: 'On Track', condition: (d: DeprecationDashboard) => d.migrationProgress.percentMigrated > 90 && d.timelineStatus.projectedMigrationComplete !== null && d.timelineStatus.projectedMigrationComplete < d.timelineStatus.sunsetDate, severity: 'info', action: 'Continue current approach, prepare sunset', },]; // Weekly deprecation status reportasync function generateWeeklyReport(): Promise<DeprecationReport> { const dashboard = await getDeprecationDashboard(); return { period: { start: weekAgo, end: today }, summary: `Migration at ${dashboard.migrationProgress.percentMigrated}% ` + `(${dashboard.migrationProgress.migratedConsumers}/` + `${dashboard.migrationProgress.totalConsumers})`, progressThisWeek: dashboard.migrationProgress.percentMigrated - lastWeek.percentMigrated, highlights: [ `${newMigrations} consumers migrated this week`, `${dashboard.timelineStatus.daysRemaining} days until sunset`, `${dashboard.timelineStatus.atRiskConsumers} consumers at risk`, ], actionItems: await generateActionItems(dashboard), charts: { migrationOverTime: await getMigrationTrendChart(), versionDistribution: await getVersionPieChart(), consumerHeatmap: await getConsumerActivityHeatmap(), }, };}| Metric | What It Tells You | Alert Threshold |
|---|---|---|
| % Migrated | Overall progress | <50% at 50% of timeline |
| Migration Velocity | Weekly migration rate | Declining for 2+ weeks |
| Holdout Count | Consumers remaining | 10% at 30 days out |
| Enterprise Holdouts | High-value risk | Any at 60 days out |
| Outreach Response Rate | Communication effectiveness | <30% response |
| Support Ticket Volume | Migration friction | Increasing trend |
Effective deprecation is the capstone of API versioning discipline. Let's consolidate the key insights:
Module Complete:
You've now mastered API versioning end-to-end: why it matters, URL and header strategies, breaking vs non-breaking changes, and deprecation management. These skills enable you to build APIs that evolve continuously while maintaining the trust and reliability that consumers depend upon.
Key Principles to Remember:
Congratulations! You've completed the API Versioning module. You now have the knowledge to design versioning strategies for APIs at any scale, from startups to global platforms. Apply these principles to build APIs that developers love to use and trust to depend upon.