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.
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.