API Versioning Strategies That Don't Trap You

Our early instinct, like most teams, was URL-based versioning — /v1/orders, /v2/orders. It's simple to explain and easy for partners to understand, but it pushed us toward maintaining entire parallel route trees for changes that only affected one or two fields. A single new required field on the orders response ended up justifying a v2 that duplicated 95% of v1's behavior, and now we had two codepaths to keep in sync for every subsequent bug fix.
For a client's partner API, we moved to a header-based versioning scheme instead — partners send an API-Version header with a date string, similar to Stripe's approach, and we maintain a set of small, additive transformation layers rather than fully duplicated endpoints. A breaking change gets implemented once in the current version, with a thin adapter that reshapes the response for callers pinned to an older version date.
The adapters only handle genuinely breaking changes — renamed fields, changed types, removed fields — and we cap how many version dates we support at once, typically the last 12-18 months, with a clear deprecation notice period before older ones are sunset. This keeps the number of adapters small; at any given time we're usually maintaining two or three, not a dozen.
Additive changes — new optional fields, new endpoints — don't get a version bump at all under this scheme, since well-behaved clients ignore fields they don't recognize. We only had to reinforce this once, when a partner's integration broke because their JSON parser was strict about unexpected fields; the fix was on their end, but it's the one case where 'you can add fields without a version bump' isn't universally safe advice.
The header-based approach also let us version by feature rather than as an all-or-nothing snapshot in some cases, using feature flags scoped to specific partners during a transition period. That flexibility would have been much harder to retrofit onto URL versioning, where the version is baked into every route a partner's code references directly.


