Learn how to version APIs properly, handle breaking changes gracefully, and evolve your API without breaking existing clients.
APIs are contracts between your server and your clients. When you release an API, developers integrate it into their applications. They write code that depends on your endpoints, request formats, and response structures. If you change the API in a way that breaks their code, you break their applications.
This is the fundamental problem API versioning solves: How do you improve and evolve your API without breaking existing clients?
There are two types of changes:
Non-breaking changes (safe):
These changes are backwards compatible. Existing clients continue to work exactly as before. You can deploy these changes without versioning.
Breaking changes (dangerous):
"id": "123" → "id": 123)These changes will break existing clients. A mobile app using your v1 API will crash if you suddenly rename /users to /accounts or change user.name to user.fullName.
Real-world example: In 2012, Instagram changed their API to require access tokens for all requests. Apps that were working suddenly stopped. Developers were furious. Instagram learned to version their API properly.
The solution is versioning. When you need to make breaking changes, you create a new version of the API while keeping the old version running. Existing clients continue using v1, new clients can adopt v2, and everyone is happy. Eventually, after giving developers months of notice, you can deprecate and shut down v1.
There are three main strategies for versioning APIs, each with trade-offs:
1. URL Path Versioning (Most Popular)
Include the version in the URL path:
https://api.example.com/v1/usershttps://api.example.com/v2/usershttps://api.example.com/v3/usersPros:
Cons:
This is the most common approach. Used by Stripe, Twitter, GitHub, and most major APIs.
2. Query Parameter Versioning
Include the version as a query parameter:
https://api.example.com/users?version=1https://api.example.com/users?v=2Pros:
Cons:
Used by Google Maps API and some older APIs.
3. Header Versioning (RESTful Purist)
Specify the version in an HTTP header:
Accept: application/vnd.example.v1+jsonAPI-Version: 2Pros:
Cons:
Used by GitHub API (Accept: application/vnd.github.v3+json) and Azure.
Which to choose?
For most APIs, URL path versioning is the best choice. It is simple, explicit, and universally supported. Reserve header versioning for situations where you need fine-grained content negotiation (e.g., supporting both JSON and XML formats). Avoid query parameter versioning unless you have a specific reason.
Semantic Versioning (semver) is a versioning scheme that uses three numbers: MAJOR.MINOR.PATCH (e.g., 2.4.1). While it was designed for software libraries, the principles apply to APIs.
Here is how it maps:
MAJOR version — Increment when you make breaking changes
MINOR version — Increment when you add backwards-compatible functionality
PATCH version — Increment for bug fixes that don't change the API contract
Most public APIs only expose the MAJOR version in the URL (/v2/) and handle minor/patch updates transparently. Clients get bug fixes and new features automatically without needing to change their code.
Example from Stripe:
2023-10-16, 2024-01-01, etc.This date-based versioning is a variation of semantic versioning. Each date is like a major version. Stripe can evolve the API rapidly while giving developers complete control over when they upgrade.
Key principle: Your versioning strategy should give clients stability (existing code keeps working) and control (they choose when to upgrade). Never force breaking changes on clients without warning and a migration path.
Deprecation is the process of phasing out old API versions while giving developers time to migrate. Done well, it is smooth. Done poorly, it creates chaos and angry developers.
Best practices for deprecation:
1. Give plenty of notice Announce deprecation at least 6-12 months before shutdown. Major APIs (Google, Stripe) often give 1-2 years. Send emails, update documentation, add warnings in the developer portal.
2. Use the Sunset header
Include a Sunset HTTP header in responses to indicate when the endpoint will stop working:
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
This is an official HTTP standard (RFC 8594). Clients can parse this header and warn developers.
3. Provide migration guides Do not just say "v1 is deprecated, use v2." Show developers exactly what changed, how to migrate, and provide code examples. Compare old vs new side-by-side.
4. Add deprecation warnings in responses Include a custom header or field in the JSON response:
{
"data": { ... },
"warnings": [
{
"type": "deprecation",
"message": "This endpoint is deprecated and will be removed on 2025-12-31. Migrate to /v2/users.",
"migration_guide": "https://docs.example.com/migrate-to-v2"
}
]
}5. Monitor usage Track how many clients are still using deprecated endpoints. Before shutting down v1, ensure usage is near zero. Reach out to heavy users personally.
Backwards compatibility strategies:
Remember: every breaking change costs your users time and money. Treat deprecation seriously. The best API is one that evolves without breaking existing code.
<h3 style="font-family:sans-serif; margin-bottom:15px;">API Versioning in the Wild</h3>
<div style="font-family:sans-serif; line-height:1.6;">
<div style="background:#f8fafc; padding:15px; border-left:4px solid #3b82f6; margin-bottom:15px;">
<h4 style="margin:0 0 8px 0; color:#1e293b;">Stripe (Date-based)</h4>
<code style="background:#e2e8f0; padding:4px 8px; border-radius:4px; display:block; margin-bottom:8px;">
curl https://api.stripe.com/v1/charges \<br>
-H "Stripe-Version: 2024-01-01"
</code>
<p style="margin:0; font-size:14px; color:#475569;">
Each date is a version. Clients pin to a date, Stripe can make breaking changes without affecting them.
</p>
</div>
<div style="background:#f8fafc; padding:15px; border-left:4px solid #10b981; margin-bottom:15px;">
<h4 style="margin:0 0 8px 0; color:#1e293b;">GitHub (Header + Path)</h4>
<code style="background:#e2e8f0; padding:4px 8px; border-radius:4px; display:block; margin-bottom:8px;">
curl https://api.github.com/repos/user/repo \<br>
-H "Accept: application/vnd.github.v3+json"
</code>
<p style="margin:0; font-size:14px; color:#475569;">
Uses v3 in the Accept header. Elegant but requires custom header support.
</p>
</div>
<div style="background:#f8fafc; padding:15px; border-left:4px solid #f59e0b;">
<h4 style="margin:0 0 8px 0; color:#1e293b;">Twitter (URL Path)</h4>
<code style="background:#e2e8f0; padding:4px 8px; border-radius:4px; display:block; margin-bottom:8px;">
GET https://api.twitter.com/2/tweets/1234567890
</code>
<p style="margin:0; font-size:14px; color:#475569;">
Simple URL path versioning. v1 was shut down in 2013, v2 launched in 2020.
</p>
</div>
</div>Which of the following is a breaking change that requires a new API version?