API versioning that survives three years

API versioning is not choosing `/v1` versus `/v2` in the path. It is a multi-year contract with every client — including ones that do not exist yet. Version what breaks. Sunset what telemetry proves unused. Expand everything else.

Engineering5 min read
API designVersioningBackward compatibilityRESTPlatform engineering
Share

Year one: clean REST API, /v1 prefix, swagger docs. Year two: mobile app, three integration partners, internal admin tools. Year three: a breaking field rename ships in a Friday deploy. Partner webhooks silently fail. Mobile clients on old builds crash on parse. Nobody knows which consumers still call the deprecated endpoint because logging did not capture client identity. API versioning strategy that survives three years is not syntax — it is policy: what may change without notice, what requires a new version, how long old versions live, and how sunset is measured — not guessed.

APIs are contracts longer than most codebases. External clients update on their schedules, not yours. Mobile apps sit in app stores for months. Enterprise integrations require quarters of notice. Internal services multiply unnoticed. Versioning is how teams change production APIs without pretending every consumer deploys simultaneously.

Version what breaks; expand what does not

Change typeStrategyExample
Add optional fieldNo version bumpNew JSON field, ignored by old clients
Add optional endpointNo version bumpNew route, old routes unchanged
Rename fieldBreaking — version or aliasuser_namedisplay_name with dual-write period
Remove fieldBreaking — version + sunsetDeprecation header, telemetry, then removal
Change semanticsBreaking — versionstatus: "active" now excludes trial
Change authBreaking — versionOAuth scope changes, key format changes
Performance onlyUsually not versionedFaster response, same shape

Additive changes are the default. Breaking changes require version increment OR compatibility layer with documented sunset.

/v2 in the URL is not a versioning strategy. It is a label. Policy is the strategy.

The strangler fig legacy migration pattern applies to APIs — route traffic to new implementations behind a facade while old version decays with measured traffic, not arbitrary dates.

Versioning styles and tradeoffs

URL path (/v1/users). Visible, easy to route at gateway. Risk: clients hardcode path; resource URLs proliferate.

Header (Accept: application/vnd.company.v2+json). Clean URLs. Risk: harder to test in browser; clients forget header.

Query param (?api-version=2024-01-01). Date-based clarity. Risk: caching mistakes; params stripped by proxies.

Content negotiation. Standards-compliant. Risk: complexity; inconsistent client support.

Pick one primary style per API surface. Mixing styles across endpoints confuses consumers and observability. Document the choice where integrators land first — developer portal, OpenAPI spec banner, error messages on deprecated calls.

Date-based versions (2024-06-01) communicate sunset timelines better than opaque integers (v3). Integers are fine internally if changelog maps integer → date → deprecation policy.

Deprecation is a product with telemetry

Sunset without usage data is vandalism against integrators.

Deprecation headers on every old-version response:

Deprecation: true
Sunset: Sat, 01 Mar 2027 00:00:00 GMT
Link: <https://docs.example.com/migration/v1-to-v2>; rel="successor-version"

Per-client/version metrics:

  • Requests per API version per day
  • Unique client IDs or API keys per version
  • Error rates on deprecated fields
  • Oldest active client version

Sunset policy written upfront:

  • Minimum notice period (90 days consumer, 180 days enterprise — adjust to contracts)
  • Active outreach when client ID still on deprecated version at T-60 days
  • Hard sunset only when traffic below threshold OR contractual notice complete

Never remove a version that telemetry shows active paying integrations without named owner sign-off.

Compatibility patterns that delay version forks

Before creating /v2, exhaust compatibility:

Field aliasing. Return both user_name and display_name during transition. Write accepts both. Remove alias after sunset.

Default values for new required fields. Server supplies sensible default when old clients omit field — document in changelog.

Response shaping per version. Gateway or handler layer maps internal model to v1 or v2 shape — contains mapping debt but avoids double implementation.

Feature flags for behavioral changes. Same endpoint, flag controls new semantics — useful for internal clients; external APIs prefer explicit versions.

Mapping layers accumulate. Track mapping debt — when alias maintenance exceeds new version cost, fork version and sunset v1 on schedule.

Documentation and changelog as versioning infrastructure

Integrators read changelogs, not commit history.

  • Breaking change log — date, affected endpoints, migration steps, sunset date.
  • OpenAPI per version — separate specs or versioned single spec with clear tags.
  • Migration guides — code samples for top client languages, not prose alone.
  • Webhook versioning — separate topic or payload version field; partners miss URL versioning.

OAuth scopes and authorization design intersects versioning — scope changes are breaking for clients holding old tokens. Version auth requirements explicitly in changelog.

How should teams design API versioning for the long term?

These policies prevent version sprawl and surprise breakages.

How many active versions should run simultaneously?

Typically two — current and previous. Three creates triple maintenance burden. Exception: enterprise contracts mandating extended support — charge for it or document as revenue-linked cost.

When is a new major version mandatory?

When compatibility layer cost exceeds fork, when semantic change cannot be aliased, or when security requires breaking old auth. Not when rename would look cleaner — cleanliness is not breaking change criteria.

How do mobile clients affect sunset timelines?

Mobile lags server by months — app store review, user update rates. Sunset policy must exceed mobile tail; server-driven feature flags cannot fix old binary parse errors. Maintain old response shapes until analytics show negligible old app version traffic.

A common argument runs the other way

The opposing view holds that strict versioning slows innovation — that teams should ship breaking changes fast and expect clients to keep up.

Clients that pay invoices do not keep up on your sprint cadence. Breaking APIs without versioning transfers engineering speed to customer support and churn. Additive-first policy with rare versioned breaks is faster net — fewer fire drills, fewer emergency partner calls.

GraphQL and BFF patterns shift versioning location but do not eliminate contract discipline — schema deprecation rules apply equally.

Key takeaways

  • Version breaking changes only — additive fields and endpoints need no version bump.
  • Pick one versioning style; document sunset policy before v1 ships.
  • Deprecation headers, telemetry per client/version, and minimum notice periods are mandatory.
  • Field aliasing and response shaping delay forks — track mapping debt.
  • Mobile and enterprise clients extend sunset timelines — measure, do not guess.
  • Changelog and migration guides are versioning infrastructure, not afterthoughts.

Conclusion

API versioning that survives three years is measured in policy and telemetry, not path prefixes. Teams that log client version usage, deprecate with notice, and expand before breaking maintain trust with integrators they will never meet. Teams that break fields on Friday deploys maintain a backlog of angry partners.

The audit: list active API versions, request volume per version, oldest client still on each. If any version lacks sunset date and traffic metric, that is this quarter's platform work.

Related articles

Command Palette

Search for a command to run...