Skip to content
← Blog

API Versioning: How to Ship Breaking Changes Without Breaking Your Customers

A practical framework for versioning APIs, deprecating old behavior, and shipping breaking changes without waking up to a support queue full of angry integrators.

Every API eventually needs a breaking change. A field that should never have been nullable. An endpoint that returns the wrong shape for a resource that has outgrown its original design. A pagination scheme that falls over past ten thousand records. The question is never whether you will need to break something — it is how much damage the break does when it happens.

Most teams get this wrong in one of two ways. They either freeze the API in place, accumulating design debt because nobody wants to be the one who breaks a customer’s integration, or they ship changes without warning and discover the hard way how many workflows depended on the exact behavior they just changed.

Version the Contract, Not the Code

The first mistake is treating versioning as a code-organization problem. It is not. A version number is a promise to your consumers about what will and will not change underneath them. The code that implements v1 and v2 can share 95% of its logic — that is an implementation detail. What matters is that a consumer who integrated against v1 six months ago gets the exact same behavior today that they got then, byte for byte.

This means every breaking change gets a new version, full stop. Not a “minor bump,” not a “this shouldn’t affect anyone” exception. If a field’s type changes, if a status code changes, if the meaning of an existing parameter changes, that is a new version. The moment you start making judgment calls about which breaking changes are “small enough” to skip versioning, you have created a category of undocumented risk that someone downstream will hit at the worst possible time.

Additive Changes Are Not Breaking Changes

The flip side is just as important: teams that version too aggressively end up with a sprawl of API versions that are expensive to maintain and confusing to integrate against. Adding a new optional field, adding a new endpoint, or adding a new enum value that consumers are expected to handle gracefully (via a default case) are not breaking changes and do not need a new version.

The dividing line is simple: would a well-written client that ignores fields it doesn’t recognize keep working correctly after this change? If yes, it’s additive. If no, it’s breaking. Write that rule down and hold your team to it — it removes almost all of the ambiguity in day-to-day decisions.

Deprecation Needs a Timeline, Not a Warning

Announcing a deprecation with no end date is functionally the same as not deprecating at all. “This endpoint is deprecated and will be removed in a future version” tells an integrator nothing they can act on, so most of them do nothing until it actually breaks.

A deprecation notice needs three things: the exact date or version the old behavior stops working, a working example of the replacement, and a mechanical way to detect who is still using the old path. That last part matters more than teams expect — a Deprecation and Sunset response header, combined with server-side logging of which API keys are still hitting the deprecated route, turns “we hope nobody is affected” into “we know exactly who is affected and can reach out directly before we flip the switch.”

Support Old Versions Longer Than Feels Comfortable

The instinct after shipping v2 is to want v1 gone as quickly as possible — it is more code to maintain, more surface area for bugs, more cognitive load for the team. Resist that instinct. The cost of maintaining an old version for an extra quarter is a known, bounded engineering cost. The cost of forcing a client’s finance system or e-commerce checkout to migrate on your timeline instead of theirs is customer trust, and that does not come back cheaply.

A reasonable default for a B2B API: a minimum twelve-month support window after a version is marked deprecated, with direct outreach to any account still using it at the ninety-day and thirty-day marks. For a high-traffic consumer API this can be shorter; for anything embedded in another company’s critical infrastructure, it should be longer, not shorter.

Build the Version Into the Request From Day One

The most common regret we hear from teams retrofitting versioning onto an existing API is that they didn’t put a version identifier in the request from the start. Whether it’s a URL path segment (/v1/orders), a header (Api-Version: 2026-07-16), or a content-type parameter, pick one before you ship your first public endpoint. Adding versioning after the fact means every existing consumer is implicitly on an unversioned “v0” that you now have to support indefinitely, because you have no way to tell who would break if you changed it.

Date-based versioning (Stripe’s approach, where the version is a date string tied to a changelog) scales better than sequential numbers once you have more than two or three versions in flight, because it lets you ship narrow, well-documented changes without the psychological weight of a major version bump. But the specific scheme matters less than having one, consistently applied, from the first release.


PNK WORKS builds and maintains APIs that agencies, SaaS products, and internal teams can integrate against with confidence. Start a project.

Ready to work together?

Start a Project →