Fundamentals
API versioning
The best version is the one you never ship. Make additive changes, make clients tolerant of new fields, and version only when you must break the contract.
Once another team or a paying customer depends on your API, the response format is a contract. You can change the code behind it at any time. Changing the contract is slow. Mobile apps stay installed for years, and integrations get written once and left alone. API versioning is how you change a contract when you have to, without breaking the callers who depend on the old one.
Concept
A breaking change is any change that makes a client that worked yesterday fail today. A tolerant reader is a client that ignores fields it doesn't recognize and handles unknown enum values with a default.
The big idea
Think of the API as a shop's price list. You can add a new item to the list without upsetting anyone. You can't rename an item or change its price without telling every customer. Versioning gives you a second price list for the customers who can't switch yet.
The useful rule is to design for additive change. Then you rarely need a second list.
Walking through it
Usually safe (non-breaking):
- Adding a new endpoint.
- Adding an optional request field or a new response field.
- Adding a new enum value, if clients were told to handle unknown values.
Breaking:
- Removing or renaming a field, endpoint or enum value.
- Changing a field's meaning or units, such as cents to dollars or seconds to milliseconds.
- Making an optional field required, or tightening validation.
- Changing default behavior, error codes or pagination.
When you must break the contract, pick a scheme:
| Scheme | Example | Trade-off |
|---|---|---|
| URL path | /v1/files, /v2/files | Obvious and easy to route and cache. Coarse-grained |
| Header | Accept: application/vnd.example.v2+json | Clean URLs. Hard to test in a browser and easy to forget |
| Date-based, pinned per account | Stripe-Version: 2024-06-20 | Fine-grained. Needs transformation layers on the server |
Stripe's date-based approach works like this. The server runs the latest code. For each older pinned version, it applies a chain of small transformations to the response. Stripe can ship many small breaking changes without forcing any customer to migrate at once.
For deprecation, announce early and track which clients still use the old version. Send a Sunset header with the retirement date, email the remaining callers, and only then turn the old version off.
Common mistakes
- Changing meaning without changing the name. Switching
amountfrom cents to dollars breaks every caller, even though the field still exists. - Clients that crash on new enum values. If a client fails when it sees an unknown status, adding a status is a breaking change for that client.
- Creating
/v2casually. An internal API's old version lives until its last caller moves. Every extra version is a long-term cost.
Check yourself
- Is adding an optional response field a breaking change? No. It's additive, as long as clients ignore fields they don't know.
- Why can Stripe pin each account to a date-based version? Each account keeps the behavior it integrated against, while the server only runs one current codebase and transforms the output.
Key takeaways
- Design for additive change so you rarely need a new version.
- Clients must be tolerant readers: ignore unknown fields and handle unknown enum values.
- Common breaking changes are renames, removals, meaning changes and tighter validation.
- Pick one versioning scheme and announce deprecations with a
Sunsetheader.
Further learning
The Stripe blog post on API versioning explains how the date-based approach works in practice.
Next, read about structured errors, which tell clients how to react when a call fails.
Related
API design
An API is a contract that outlives the code behind it. Design the styles, methods, pagination, errors and async flows so changes stay additive.
API styles
REST, gRPC and GraphQL can answer the same request. Pick one by asking who calls the API and what they need from it.
Developer and API products
Treating APIs, SDKs and docs as products: developer experience and adoption.