Skip to content

API Versioning

Versioning protocol — instantiates Layer-Appropriate Capability Placement

Exposes a host capability as explicitly versioned interfaces that coexist, so consumers migrate on their own schedule and a change to the host never becomes a forced, simultaneous break for everyone downstream.

API Versioning is the discipline of exposing a shared or host capability as labeled, coexisting versionsv1, v2, a version header, a dated contract — so that evolving the capability does not force every consumer to change at the same instant. Its defining move is decoupling the host's clock from the consumer's: without versioning, any breaking change to a delegated interface makes everyone downstream rebuild in lockstep, and that pain is exactly what pushes teams to fork a local copy instead of depending on the shared one. Versioning removes the reason to fork. It is not translation between two fixed sides (that is an adapter) and not a temporary migration crutch (that is a shim) — it is the standing promise that old and new contracts run side by side long enough for consumers to move deliberately.

Example

A platform team owns a public REST API that dozens of internal and external clients depend on for pricing. They need to change the response: price was a bare number, and it must become an object with amount and currency. Ripping the field out would break every caller at once — the kind of forced break that makes downstream teams cache their own copy of pricing rather than trust the shared service. Instead they version. v1 keeps returning the bare price; v2 returns the new object. Both are live, selected by a URL prefix (/v1/, /v2/) and governed by a stated deprecation window — say, v1 supported for ≈12 months after v2 ships.

Clients migrate when they can. New integrations start on v2; legacy callers move over the year; usage dashboards show when v1 traffic has fallen far enough to retire it. The breaking change still happened — but it happened as a scheduled migration each consumer controls, not a synchronized outage. That is what keeps consumers depending on the one shared capability instead of quietly building their own.

How it works

What distinguishes versioning from just "shipping a new endpoint" is the explicit contract about coexistence and change:

  • Versions are labeled and addressable. A consumer names the version it wants (route prefix, header, media type), so the same capability serves multiple contracts at once.
  • A compatibility rule governs change. Non-breaking additions can land in place; anything that would break a caller requires a new version — often codified against a scheme like semantic versioning.
  • Old and new coexist for a stated window. Deprecation is announced with a timeline, not sprung; the overlap period is the whole point.
  • Retirement is evidence-driven. A version is removed when its traffic has drained, measured, rather than on a whim — so no consumer is cut off mid-migration.

Tuning parameters

  • Version granularity — version the whole API, or individual resources and fields. Coarse versioning is simple to reason about but forces broad migrations; fine granularity localizes change at the cost of a combinatorial surface to support.
  • Deprecation-window length — how long a retired version stays alive. Long windows respect slow consumers but multiply the versions you must maintain; short windows cut maintenance but re-create the forced-break pressure versioning exists to relieve.
  • Change-classification strictness — how conservatively a change counts as "breaking." Strict rules protect consumers but spawn versions for small changes; loose rules keep versions few but risk silent breakage.
  • Selection mechanism — URL path, header, or negotiated media type. Path versions are visible and cache-friendly; header/negotiated versions are cleaner but easier for a caller to get implicitly wrong.

When it helps, and when it misleads

Its strength is that it lets a shared capability evolve without punishing the consumers who depend on it — which is precisely what keeps them depending on it rather than forking a local copy. It converts a breaking change from a coordinated outage into a set of independent, consumer-paced migrations, and it makes deprecation an announced, measurable process instead of a surprise.

Its failure mode is version sprawl: every version kept alive is a contract you must maintain, test, and secure, and a team that never retires anything slowly drowns in its own backward compatibility. The mirror failure is the phantom deprecation window that is announced but never enforced, so v1 lives forever and v2 was pointless. The classic misuse is versioning as a substitute for design — bumping the version to avoid the harder work of a stable, well-shaped contract, until the API is a museum of every past mistake.[1] The discipline that keeps it honest is to make additions backward-compatible so most changes need no new version at all, to pair every new version with a real retirement plan for the old one, and to drive retirement by measured usage, not hope.

How it implements the components

Versioning realizes the evolvable-contract slice of the archetype — it lets the delegated interface change over time without breaking dependents:

  • versioned_layer_contract — it is this component: the delegated capability expressed as explicitly labeled contracts that coexist, each a stable target a consumer can pin to.
  • portability_and_compatibility_contract — the deprecation window and compatibility rules constitute the promise that a consumer can keep working across change and migrate on its own schedule, so dependence on the shared layer stays portable across the host's evolution.

It defines and evolves the contract but does not translate between two mismatched sides — that is Adapter Layer — nor verify conformance to a version, which is Interface Contract Test. The temporary bridge that carries old callers during a specific migration is Compatibility Bridge or Shim, not a standing version.

References

[1] Semantic versioning encodes the rule this mechanism runs on — that a major bump signals a breaking change and therefore a new coexisting contract, while minor and patch changes must stay backward-compatible. The scheme is a communication convention, not a guarantee: it only protects consumers if change is classified honestly.