API & Automation·7 min read·

Versioning an API Without Breaking Your Clients

Versioning is a trust exercise: increment deliberately, overlap old and new versions, and retire v1 only when real traffic says it is time.

NB

Netbay Infrastructure Team

Netbay Engineering

On this page

APIs change because products change. New fields, new lifecycle states, new billing models — all mean the contract is a moving document. The problem is not change; it is breaking compatibility silently and leaving clients with code that no longer means what it appears to mean. This post is a versioning playbook: when to version, where to put the version, and how to retire old versions of an API without burning your clients.

Where the version lives

The most common question is where to put the version, and the options trade visibility against neatness.

  • URL prefix (such as /api/v1/plans): the version is explicit in every request, which makes routing, caching, and debugging trivial. URLs are self-describing and the version survives copy-paste. This is the right default for a public API.
  • Accept header (Accept: application/vnd.api+json; version=2): keeps the URL clean but hides the version from logs, caches, and clients that ignore custom headers. Proxies and shared caches start serving the wrong version and nobody notices until support does.
  • Query parameter (?v=2): the least visible choice. Query strings get dropped, rewritten, and mangled by all manner of intermediaries, and users share links without the parameter. Avoid versioning this way.

Commit to the URL prefix. It is the most boring, most reliable place a public contract can declare itself, and boring is what compatibility runs on.

Non-breaking changes are free — spend them first

A large share of API work is additive, and additive is compatible. Adding a new endpoint, a new optional field, or a new response header does not break well-behaved clients as long as consumers ignore unknown fields. The discipline is real though:

  • Never reorder, rename, retype, or remove an emitted field. Every JSON field you have ever sent is a promise.
  • Adding an enum value can break clients that switch on it exhaustively. Add it, announce it, and document the new values.
  • Relaxing a constraint is safe. Tightening one is a breaking change even when the response shape is identical.
  • New required request parameters are breaking. New parameters, when present, gate optional behavior.

If your team treats additive work as versionless, most feature requests never force a bump, and the version number becomes a real signal instead of a churn counter. Version the whole surface, too: a version that means "this endpoint, but not that one" is a version nobody trusts, because every consumer has to re-check which promises still hold for their corner of the API.

Breaking changes: bump, overlap, sunset

When a true break is unavoidable — a semantic rewrite, a renamed resource, a reworked model — run the three-phase rotation.

  1. Release the new version under /api/v2 while v1 stays fully live. Routing, docs, and analytics all coexist.
  2. Announce deprecation loudly: a Deprecation header on every v1 response, a banner in the docs, and a migration guide. Deprecation headers let client tooling detect the change automatically.
  3. Hold the overlap long enough for every consumer you can see — six months is a reasonable floor for a small API, longer for enterprise users. Measure actual v1 traffic rather than guessing.
  4. Only when v1 traffic is near zero, wire v1 to a 410 Gone with a link to the migration guide. A hard 410 with an explanation is friendlier than a 404 with silence.
bash
# Your client still works during the overlap, but the server flags it
curl -s -i https://api.netbayhosts.in/api/v1/plans   -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET" | head -n 20
yaml
HTTP/1.1 410 Gone
Deprecation: true
Link: <https://docs.example.in/api/migration-v1-to-v2>; rel="migration"
Content-Type: application/json

{"error":{"code":"api_version_retired","message":"v1 retired on Jan 1, 2027. Migrate to /api/v2.","details":[]}}
bump → overlap → sunset /api/v1 only live, unchanged v1 + v2 parallel Deprecation header v2 only v1 → 410 Gone t0: v1 frozen overlap ≥ 6 months, watch traffic v1 traffic ≈ 0 retire on a schedule, never on a whim or a surprise

Takeaway

Versioning is a trust exercise: increment deliberately, never change a response in place, and retire old versions through a visible, timed window. The version number in your URL is only useful if you refuse to abuse it — clients reward you with smooth upgrades instead of support tickets when migration stays boring.

You can watch a real, well-run versioned contract at Netbay: the V1 API documented at netbayhosts.in ships stable plan, account, and purchase endpoints with key and secret auth — exactly the kind of surface whose version number you can believe.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance