Versioning Schemes — URL, Header, Date-Pinned, and the Scheme Behind the Scheme

Where does the version live — in the path, a header, a date? The debate consumes design reviews and misses the real decision underneath: what a version even is (the whole API? a resource? a behavior?), who chooses it (the request? the account?), and how many will you honestly support? This post surveys the schemes with their true trade-offs, including the account-pinned date scheme that the most evolution-serious API companies converged on — and why.

Post 1 defined the contract; this post is about creating room to change it — the mechanism that lets v-next fix what v-now got wrong without breaking anyone who hasn’t asked for the fix.

The surface schemes

The observation that reframes the debate: these schemes mostly differ in where the number goes, while the costs live in how many numbers exist and how long each lives — which the surface choice barely affects.

The scheme behind the scheme: account-pinned dates

The pattern the most change-intensive API providers converged on restructures the question. Instead of a few big versions, every breaking change creates a new dated version (2026-08-14); instead of each request choosing, each account is pinned to the version current when it integrated; requests may override per-call (for testing the future), and accounts upgrade deliberately, one dated step visible in a changelog at a time. What this buys: breaking changes decouple from each other — shipping a fix today doesn’t wait for a v2 omnibus, and clients adopt changes incrementally instead of facing a migration cliff; integrations freeze in the era they were built — code written in 2024 keeps 2024 semantics until its owners choose otherwise, which is post 1’s contract honored by default; and the provider carries the burden — which is the honest accounting, because the provider wrote the breaking change and is the only party who can make it cheap. The cost is real and concentrated: dozens of live dated versions mean the implementation must serve all of them — mechanically impossible without post 3’s compatibility-transform architecture, which is why this scheme and that architecture are effectively one decision. Account pinning also changes defaults: the dangerous “no version specified → latest” policy (every new deploy breaks someone) becomes “no version → your account’s pin,” which is both safer and stickier.

Choosing, honestly

The decision tree is shorter than the debate: an internal or few-consumer API — skip ceremony, coordinate deploys, use additive evolution (post 6) and version nothing until forced. A public API changing rarely — URL versioning is fine if you accept that each version is a near-permanent commitment (budget for a decade, not a deprecation quarter — post 5 covers why sunsets slip). A public API that intends to evolve continuously — the dated-pin model is the one that scales with change rate, priced at building the transform layer. And in every scheme, two invariants hold: the version policy is documented as part of the API (what counts as breaking, what may appear without notice — post 1’s tolerant-reader demand in writing), and version metadata is observable (every response echoes the version that served it; your telemetry can say who is on what — the raw material post 5’s deprecation machinery requires).

The takeaway: the visible schemes — path, header, per-resource — mostly relocate the version number, while the real decisions are granularity, default policy, and lifetime. The convergent answer for continuously-evolving APIs is dated versions pinned per account: every breaking change ships independently as a date, integrations keep the semantics of their era, upgrades are deliberate single steps, and the provider pays the (real) cost in a compatibility-transform layer — the subject of post 3. Whatever the scheme: write the policy into the API’s contract and make the serving version observable everywhere.

Key takeaways

Further reading

Sources & References

the general discipline of naming change