Guide

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:

SchemeExampleTrade-off
URL path/v1/files, /v2/filesObvious and easy to route and cache. Coarse-grained
HeaderAccept: application/vnd.example.v2+jsonClean URLs. Hard to test in a browser and easy to forget
Date-based, pinned per accountStripe-Version: 2024-06-20Fine-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 amount from 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 /v2 casually. An internal API's old version lives until its last caller moves. Every extra version is a long-term cost.

Check yourself

  1. Is adding an optional response field a breaking change? No. It's additive, as long as clients ignore fields they don't know.
  2. 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 Sunset header.

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.