When you deprecate a public API, or an internal-but-stable one, missing a proper timeline turns the rollout into chaos. Consumers get surprised, support tickets pile up, and you end up rolling back at the last minute.
Over the last two years I’ve run three deprecation cycles. What worked, what didn’t, and the ideal shape of a six-month process.
Why deprecation is hard
“We launched v2, everyone switch over” doesn’t work. Consumers have to:
- Carve out engineering bandwidth for the migration
- Validate in a test environment
- Roll out gradually to production
- Accept downtime risk
- Juggle it against their other priorities
You can’t just kill an API with active v1 traffic. Consumers break and trust goes with them.
The deprecation cycle has to be structured.
The six-month timeline
This timeline comes from my three deprecation cycles:
T-180 days (announcement): v2 launches, v1 deprecation announced. Migration guide ready. Consumers informed.
T-120 days: a deprecation warning lands in response headers. The analytics dashboard shows v1 usage per consumer.
T-90 days: outreach to major consumers. Migration assistance on offer. Progress tracking active.
T-60 days: daily rate limit on v1 starts dropping (warning stage). Email blast.
T-30 days: v1 responses carry a Sunset header. 410 Gone is on the calendar.
T-0 (sunset): v1 returns 410 Gone. The endpoint is closed.
T+30: v1 URL route is removed entirely (now 404).
T-180: announcement
Announce at the same moment across these channels:
- API changelog page: website, RSS feed, automatic notification to subscribers
- Email: to registered developers and consumers
- Developer dashboard: a banner on login
- Blog post: details, migration path, FAQ
- Status page: an entry under upcoming deprecations
Contents of the announcement:
v1 deprecation schedule:
- Date: 2026-10-15 sunset (180 days)
- v2 available now: https://api.example.com/v2/
- Migration guide: https://docs.example.com/migrate-v1-to-v2
- What's new: [feature list]
- Breaking changes: [list with examples]
- Support: developer-support@example.comClear deadline, clear migration path, clear help channel.
T-120: warning header
Every v1 response now carries:
Deprecation: true
Sunset: Thu, 15 Oct 2026 00:00:00 GMT
Link: <https://docs.example.com/migrate-v1-to-v2>; rel="deprecation"RFC 8594 (Sunset header) is the standard. Modern API clients log this header and notify the dev team automatically.
Usage tracking
The most important data during a deprecation cycle: which consumer is still hitting v1?
Query the analytics log:
SELECT
api_key,
consumer_name,
COUNT(*) as request_count,
MAX(request_timestamp) as last_request
FROM api_log
WHERE endpoint LIKE '/v1/%'
AND request_timestamp > NOW() - INTERVAL '7 days'
GROUP BY api_key, consumer_name
ORDER BY request_count DESC;Track the top 20 consumers by hand. Offer each of them migration support.
Dashboard view: how is weekly v1 request count trending? A line going down and to the right is the healthy shape.
T-90: proactive outreach
1-on-1 communication with high-volume consumers:
- Email: “your v1 usage is high, would you like some help?”
- Slack/Discord: a technical Q&A channel
- Video call: for the complex integrations
I did one Zoom with each high-volume consumer across three deprecations. Practical questions like “how do we move this endpoint?” get answered on the spot.
Migration speed roughly doubled.
Breaking change catalog
The heart of the migration guide: before and after for every breaking change.
### Change 1: `/v1/users/{id}` response format
Before:json
{
“id”: 123,
“name”: “Ali”,
“email”: “ali@example.com”
}
After:json
{
“id”: “u_abc123”, // String ID now
“profile”: {
“name”: “Ali”,
“email”: “ali@example.com”
}
}
**Migration**:
- ID type change: integer to string
- Profile fields are now nested
- Code change example: [migration snippet]Before, after, and migration for every breaking change. The consumer can copy-paste into their own code.
T-60: rate limit reduction
With 60 days left, if v1 usage is still there, drop the rate limit. It’s an aggressive signal:
- Normal: 1000 req/min
- T-60: 500 req/min
- T-30: 100 req/min
The consumer notices, hits the limit, and migration bandwidth jumps up the priority list.
This pattern needs care so critical consumers don’t take a hit. Give major clients advance notice and accept waiver requests.
T-30: sunset header plus a last reminder
The final 30 days:
Sunset: 2026-10-15 00:00:00 GMTheader on every response- Final reminder email
- Countdown on the consumer dashboard
- “410 Gone after this date” message
By this point every consumer should have committed to migrate or freeze.
T-0: cutover day
Cutover can be gradual behind a feature flag:
- 00:00 UTC: return 410 Gone for 10% of v1 traffic
- 06:00 UTC: 50% at 410
- 12:00 UTC: 100% at 410
Batches, not a hard cut. Panicked consumers still get to call support while the damage is limited.
The 410 Gone response:
{
"type": "/errors/api-version-sunset",
"title": "API Version Sunset",
"status": 410,
"detail": "API v1 was sunset on 2026-10-15. Please migrate to v2.",
"migration_url": "https://docs.example.com/migrate-v1-to-v2"
}Migration link in the error body. It routes panic away from your support desk and toward the fix.
T+30: route cleanup
Thirty days later you can delete the v1 route from the codebase. Traffic is gone (the ones that got 410 have accepted it; the ones that would now get 404 have already migrated).
Clean routing table, smaller codebase, less mental overhead for the team.
Tone of consumer communication
Tone matters during deprecation. Partner, not enemy:
- Not “v1 is no longer supported” but “v2 is better, we’re here to help you migrate”
- Hard deadline with flexibility (a 30-day extension for a critical consumer)
- No blaming (“why haven’t you migrated yet?” becomes “is there anything you’re stuck on?”)
A deprecation tests consumer trust. Handle it badly and they’ll push back on the next change.
Exceptional extensions
Some consumers have legitimate reasons:
- “We’re in a Q4 freeze, migration in Q1”
- “Critical integration, testing takes weeks”
- “We’re in an acquisition, no engineering capacity”
In these cases I grant an extension. Usually 30 to 60 extra days. But I get a commitment: “definitely migrating in 30 days” plus a progress check-in.
No ad-hoc extensions: documented reason, documented commitment.
Internal API deprecation
For internal APIs the cycle can be shorter (3 to 6 weeks). Consumers are in the same company, communication is direct, iteration is fast.
Same pattern though:
– Announcement
– Warning header
– Usage tracking
– Outreach to high-volume consumers
– Cutover
Even three weeks needs discipline, otherwise the migration is chaotic.
Post-mortem
After the cycle, run a team retro:
- Was the timeline too long or too short?
- Which consumers had problems?
- Which migration step was unclear?
- What do we change for the next deprecation?
That retro document feeds directly into the next deprecation.
Final take
API deprecation isn’t a kill-and-walk-away process; it’s a partnership with your consumers. Six months plus clean communication plus migration help.
Short cycles (1 to 2 months) produce angry customers. Long cycles (12+ months) produce engineering debt.
Six months is the sweet spot. A disciplined timeline and transparent communication keep the deprecation healthy.