case-study

Stripe's API Versioning

Stripe pins each account to the API version current when it integrated and transforms requests and responses between versions internally, so integrations never break and the core stays modern.

case-studystripeversioningcompatibilityapi-design

The problem every long-lived API has

Two objectives in direct conflict. The API must evolve — old designs become wrong, fields become misleading, models need restructuring. And integrations must never break, because a payments integration that breaks costs the customer revenue and costs you their trust permanently.

The common answers are unsatisfying. URL versioning (/v1, /v2) forces every consumer to migrate eventually and leaves you supporting parallel implementations. Never changing anything preserves compatibility by giving up evolution.

What Stripe does

Each account is pinned to the API version that was current when it first integrated. That version is the account's default forever, unless the customer explicitly upgrades — and upgrading is opt-in, testable, and reversible.

Internally there is one current implementation. Compatibility is achieved by a chain of request and response transformations: an incoming request from an old version is transformed forwards into the current shape, processed, and the response transformed backwards into the shape that version expects. Each version change is a small, individually-tested transformation, composed in sequence.

Why it is a strong design

The core never accumulates conditionals. The alternative — if version < X scattered through business logic — is what makes long-lived APIs unmaintainable. Here the compatibility logic is isolated at the edge, in units small enough to reason about.

Change is decoupled from customer migration. You ship the improvement today; customers adopt on their own schedule, or never.

Each transformation is testable in isolation, and the full matrix can be verified mechanically.

The costs, honestly

The transformation chain grows forever and is real code that must be maintained. Some changes cannot be expressed as a transformation at all — a genuinely new concept has no backwards mapping — so this handles most evolution, not all of it. And the discipline required is significant: every change must be considered in versioning terms at design time.

The transferable principle

Put the compatibility burden on the provider, not on thousands of consumers. It is more work for one team and far less total work across the ecosystem — and for an API that is infrastructure to someone else's business, that asymmetry is the whole argument. The same reasoning applies to event schemas, where a consumer may be reading messages written months ago.