Skip to content

Versioning Strategies

When a change would break existing clients, you need a versioning strategy so old and new clients can coexist. There are three common places to put the version.

First, decide whether you even need a new version. Non-breaking (additive) changes never require one:

  • Adding a new endpoint or a new optional field.
  • Adding a new optional query parameter.
  • Adding a new value to an output-only enum (if clients are told to tolerate unknowns).

Breaking changes do require one: removing or renaming a field, changing a type, making an optional field required, or changing the meaning of a response.

  • URI versioning — GET /v1/articles. Most visible and easiest to route and cache; the version is right there in the URL. Downside: the same resource lives at two URIs across versions.
  • Header versioning — a custom or standard header like API-Version: 2. Keeps URIs clean, but the version is invisible in logs and harder to try in a browser.
  • Media-type versioning — negotiate via Accept: application/vnd.example.article+json;version=2. The most RESTful (the version is part of the representation), but the most awkward to use.
GET /v1/articles # URI versioning
GET /articles # header versioning
API-Version: 2
GET /articles # media-type versioning
Accept: application/vnd.example.article+json;version=2

For most teams, URI versioning wins on pragmatism: it is obvious, cache-friendly, and trivial to test. Whatever you choose, version the whole API at a coarse grain (v1, v2), not each endpoint independently — per-endpoint versions explode the support matrix.

Versioning one endpoint instead of the whole API Symptom:

  • /v1/users but /v2/orders in the same API
  • Clients have to track a different version per endpoint
  • Version the whole API together: /v1/users and /v1/orders → /v2/users and /v2/orders

Deprecating without any grace period Symptom:

  • /v1/ is shut off with no advance notice — clients break immediately
  • No migration guide or clear timeline
  • Use Deprecation and Sunset headers: Sunset: Sat, 31 Dec 2024 23:59:59 GMT

💡 Real-world examples

Stripe API:

  • Versions via the Stripe-Version header, not the URI
  • Each account is locked to the version it was created on — a new Stripe release never breaks it

GitHub API:

  • Versions via the date-based X-GitHub-Api-Version header (e.g. 2022-11-28), not a /v3/-style URI — the REST API dropped URI versioning
  • Supports a version for at least 24 months after a newer one ships, with Deprecation and Sunset headers as it nears end of support

Sources: Stripe API versioning, GitHub REST API versions

Which is a breaking change that warrants a new version?
Which versioning strategy puts the version directly in the path?
At what granularity should you version?