How Do You Version an API Without Breaking Your Customers?
How Do You Version an API Without Breaking Your Customers?
Pin every customer to the version they integrated against, make additive changes freely, and never change the meaning of an existing field. Breaking changes go into a new version with a long support window. The versioning scheme matters far less than the discipline behind it.
We build a lot of the integration layer between B2B products and their websites, and the pattern is consistent. The teams that suffer are not the ones who picked the wrong scheme. They are the ones who shipped a change they thought was safe and found out it was not.
Here is how the companies that do this well actually run it, and what we would copy.
What Counts as a Breaking Change?
Anything a reasonable client could be relying on that stops working the same way. GitHub publishes its list and it is a useful baseline: "removing an entire operation", "removing or renaming a parameter", "adding a new required parameter", and "changing the type of a parameter or response field."
The one teams argue about is adding a field. Adding an optional response field is usually safe, but only if your clients parse leniently. If a customer validates responses against a strict schema, a new field is a failure on their side and a support ticket on yours.
There is a fourth category most lists miss, which is changing behaviour without changing shape. A field that used to be populated and is now sometimes null is a breaking change even though the type did not move. So is a rate limit you tightened. Shape is easy to test. Meaning is not.
Should You Use Semantic Versioning or Dates?
Semantic versioning for libraries, dates for hosted APIs. The semver specification is about a package whose consumer chooses when to upgrade. Its rules are "MAJOR version when you make incompatible API changes", "MINOR version when you add functionality in a backward compatible manner", and "PATCH version when you make backward compatible bug fixes."
That works because the consumer holds a copy. A hosted API is different: you are running the code, and the customer's integration keeps calling whatever you deployed this morning. Most large API providers therefore version by date, which reads as a point in time rather than a compatibility promise.
GitHub states plainly that "the API version name is based on the date when the API version was released", and Stripe's current version at the time of writing is 2026-08-26.dahlia. Both are dates. Neither is trying to encode compatibility in the number, because the pinning does that job instead.
How Does Pinning Actually Work?
Every account gets a default version, and every request can override it. Stripe documents that requests "use your Stripe account's default API version" unless you override it "by setting the Stripe-Version header." GitHub uses an X-GitHub-Api-Version header, and states that "requests without the X-GitHub-Api-Version header will default to use the 2022-11-28 version."
The default matters more than the override. A customer who integrated two years ago and never set a header should still be working today. If your default is "latest", you have no versioning at all, you just have a changelog and some optimism.
The per request override is what makes upgrades testable. A customer can send one request at the new version, compare the response, and decide. Stripe's own guidance is to "use API versioning to test a new API version before committing to an upgrade", which is only possible because the override exists.
What About Webhooks?
Webhooks need their own pin, and this is the detail most teams get wrong. Stripe documents that "webhook events also use your account's default API version unless you set an API version during endpoint creation." The endpoint carries its own version, separately from your API calls.
The reason is that a webhook is a message you send to code you do not control, on a schedule you choose. If it changes shape when you bump an account default, you have broken a listener that was working perfectly, and the customer finds out from a failed job rather than from a failed request.
Two practical consequences follow. Pin new webhook endpoints explicitly rather than inheriting a default, and treat the payload shape as a separate contract from the API response shape even when they look identical today. We went into the receiving side of this in our piece on verifying webhooks.
How Often Should You Release a New Version?
Rarely for breaking versions, continuously for everything else. Stripe's model is instructive here. Since the 2024-09-30.acacia release it has released "new API versions monthly with no breaking changes", and issues "a new major release" only "twice a year". It tells customers that "you can safely upgrade to any monthly release without updating your code."
That split does two things at once. It gives the vendor a steady cadence for additive improvements, and it gives customers a small number of dates a year where they actually have to think. Two breaking moments a year is a planning problem. Twelve is a treadmill.
Naming the major releases, as Stripe does with codenames like Basil, is a small touch that helps more than it should. A named release is something a customer can search, discuss internally, and put on a roadmap. A date alone is harder to talk about.
How Long Should You Support an Old Version?
Longer than feels efficient. GitHub commits that "when a new REST API version is released, the previous API version will be supported for at least 24 more months." Two years is the number that makes an API safe to build a business on.
Shorter windows do not save you as much as they seem to. The cost of an old version is mostly the branching in your code plus the test surface, and both are bounded. The cost of a short window is that your API becomes something customers route around rather than build on.
Say the number publicly. A support window that exists in someone's head is not a commitment a procurement team can rely on, and it is the sort of question that shows up in security and vendor reviews. We covered that dynamic in our piece on security review in procurement.
How Do You Warn People Before Something Goes Away?
In the response itself, not only in an email. GitHub includes a Deprecation header in API responses when a version is "approaching closing down", carrying "the date when the API version will be closing down." That reaches the integration rather than an inbox somebody left three jobs ago.
Email is still worth sending, but it is the weaker channel. The person who built the integration has often moved on, the address on the account is frequently a shared mailbox, and marketing filters are unkind to anything that looks like an announcement.
The strongest version of this pairs a header with usage data. If you can see which accounts are still calling the old version and how often, you can contact the ones that matter individually rather than broadcasting to everyone. That is a small piece of instrumentation with a very high return.
What Should You Do Before You Have Any Customers?
Declare the public surface and put a version in the URL or a header from day one. The semver specification makes this the precondition for everything else: "software using Semantic Versioning MUST declare a public API. This API could be declared in the code itself or exist strictly in documentation."
Adding versioning later is possible and unpleasant. Every integration written before you added it assumes an unversioned contract, so your first version boundary is retroactive and somebody has to decide what the old behaviour officially was. Starting with a version costs one header.
The related rule is worth internalising too. Semver states that "once a versioned package has been released, the contents of that version MUST NOT be modified." Quietly fixing a released version is how you turn a bug report into a mystery.
What Does This Look Like for a Small Team?
Smaller than you think. One dated version, a header to override it, a default that never silently moves, a documented support window, and a changelog that separates additive changes from breaking ones. That is the whole system, and it fits in an afternoon of design work.
What costs real effort is the test discipline. You need a way to run your suite against every supported version, because a support commitment you cannot verify is a support commitment you will break. Build that before the second version exists, not after.
Resist the urge to version everything. Most changes should be additive and version free. If you find yourself cutting a new version every quarter, the problem is usually the API design rather than the versioning policy.
Where Do Most Teams Go Wrong?
They treat versioning as a technical decision when it is mostly a communication one. The header is trivial. The hard parts are deciding what you will promise, writing it down where customers can find it, and then keeping that promise when it becomes inconvenient.
The second common mistake is versioning the wrong boundary. Versioning the whole API when only one resource changes forces everybody to move for something that affected a handful of accounts. Per resource or per endpoint versioning is messier to document and often kinder to customers.
If you are designing an API that your website, your partners, and your customers all depend on, and you want a second pair of eyes before the contract hardens, we are happy to look at it. Find us at phoenix.studio.
Want a site that performs like this?
Tell us about your project. We will come back with a clear next step, no pressure.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Have a project like this?
Tell us where you want to go. We'll tell you how we'd get you there.