Blog
3 min read

API Versioning: How to Change an API Without Breaking Clients

Once other apps depend on your API, changes can break them. What counts as a breaking change, versioning strategies (URL path, headers, Stripe-style dated versions), evolving without versions, deprecation with Sunset headers, and a practical approach for small teams.

While only your own front end uses your API, you can change it freely and deploy both together. Once mobile apps (which users don't update immediately), customers' integrations or partners depend on it, a change can break code you don't control. Versioning is how you evolve an API without doing that. (What is an API?)

What's breaking and what isn't

Breaking changes (need a new version or careful migration):

  • removing or renaming a field or endpoint
  • changing a field's type ("42" → 42) or meaning
  • adding a required request parameter
  • changing error codes or formats clients rely on
  • changing authentication
  • tightening validation that used to accept something

Non-breaking (usually safe):

  • adding a new endpoint
  • adding an optional request parameter
  • adding a field to a response (if clients ignore unknown fields — tell them to)
  • adding a new enum value — borderline; strict clients may break

The safest API is one that mostly evolves by adding.

Strategies

1. Version in the URL

GET /v1/invoices
GET /v2/invoices
  • ✅ Obvious, easy to route, easy to test in a browser, easy to see in logs.
  • ❌ Whole-API versions; tempting to fork everything for one change.

The most common choice, and a good default.

2. Version in a header

GET /invoices
Accept: application/vnd.acme.v2+json
# or
Api-Version: 2
  • ✅ Clean URLs; can version per resource.
  • ❌ Less visible, harder to try by hand, easy for caches to mishandle (add Vary).

3. Dated versions (the Stripe model)

Each account is pinned to the API version (a date) it started with. Clients can send a header to use a newer version. Internally, the server runs the latest code and applies transformations to responses for older versions.

  • ✅ Clients never break unexpectedly; small, frequent changes possible.
  • ❌ Significant engineering to maintain the transformation layers.

Great for large public APIs; heavy for a small team.

4. No versions — evolve carefully

Many internal and small APIs never version: add fields, never remove, deprecate gently. GraphQL APIs typically work this way. (GraphQL vs REST)

Deprecating properly

When something must go:

  1. Announce it — changelog, email to API users, docs.
  2. Signal it in responses: the Deprecation header and a Sunset header with the removal date, plus a Link to migration docs.
  3. Watch usage — log which clients still call the old version or field. (Structured logging)
  4. Contact the stragglers before the date.
  5. Remove it — and keep the old version's tests until you do.

Give generous notice: months for public APIs.

Document the contract

An OpenAPI description per version makes changes reviewable — a diff of the spec shows breaking changes before they ship, and tools can check this automatically in CI. (OpenAPI vs Swagger)

Mobile apps need extra care

Old app versions live on phones for years. Options: keep old endpoints working, have the API tell outdated apps to upgrade (a minimum-version check), and never assume everyone has the latest build. (Web app vs mobile app)

A practical approach for small teams

  • Prefix routes with /v1/ from day one — it costs nothing and leaves room.
  • Evolve within v1 by adding whenever possible.
  • Treat the response shape as a contract; test it. (How to test an API)
  • Only create v2 for a genuinely different design, and run both until v1 traffic is gone.
  • Same rule applies to webhooks you send: version the payload. (Idempotency keys)

EasySpawn runs your API with logs you can query, so you can see exactly which clients still call v1 before you retire it — and Claude Code can write the migration guide. See how it works or join the waitlist.

Related: Designing a REST API · OpenAPI vs Swagger · tRPC vs REST · API Authentication Methods

Keep reading