Designing API deprecations with real off-ramps
What we changed about API deprecations so they behaved more like managed migrations and less like surprise deadlines.
Deprecating an API is easy to announce and hard to complete.
Our early attempts followed a familiar pattern:
- send a "this API will be deprecated" message
- set a date
- hope clients move in time
The real world looked like:
- some clients never saw the announcement
- some started migrations but didn’t finish
- internal callers forgot to update scheduled jobs and scripts
When the date arrived, we had a choice:
- delay the deprecation
- or break clients we still cared about
We decided to treat API deprecations more like managed migrations and less like hopeful deadlines.
Constraints
- We supported both internal and external callers.
- Not all callers had the same ability to migrate on our timeline.
- We wanted to reduce long-term surface area without repeatedly extending dates.
Constraints
- We supported both internal and external callers.
- Not all callers had the same ability to migrate on our timeline.
- We wanted to reduce long-term surface area without repeatedly extending dates.
What we changed
1. Start with usage and ownership
Before announcing a deprecation, we:
- inventoried which clients were actually using the API
- identified internal owners for each major client
This turned "we hope people move" into "we know who needs to move and who can help."
2. Provide a concrete migration path
We avoided deprecations that said "stop using this API" without offering:
- a replacement API or pattern
- guidance on behavioral differences
- examples and tooling for common use cases
For internal clients, this sometimes meant:
- writing small adapters
- offering temporary dual-write or dual-read paths
3. Decompose the deprecation into stages
Instead of a single date, we used stages:
- announce: communicate intent and rationale
- deprecated but supported: new features land only on the replacement; we add warnings and metrics
- limited: old API remains but is throttled or limited for specific behaviors
- removed: old API returns clear, documented errors
Each stage had clear signals in logs, metrics, and docs.
4. Add observability for stragglers
We instrumented the old API to show:
- which clients were still using it
- what kinds of calls they were making
This made it easier to:
- reach out to stragglers
- understand whether they had valid constraints
5. Align deprecation with reliability and cost goals
We framed deprecations not just as "cleaning up," but as:
- reducing maintenance overhead
- lowering risk for incidents
- simplifying observability
This helped stakeholders prioritize deprecation work alongside features.
Results / Measurements
After adopting this approach, we saw:
- fewer last-minute extensions for old APIs
- smoother cutovers with fewer surprises
- better alignment between deprecation schedules and actual client readiness
We still encountered slow migrations.
The difference was that we could:
- see who was blocked
- adjust plans based on real constraints
Takeaways
- Effective API deprecations look like managed migrations, not surprise shutdowns.
- Usage data and client ownership matter more than announcement dates.
- Staging deprecations and instrumenting old APIs make it easier to land changes safely.