API versioning is the practice of labeling and managing changes to an application programming interface so that existing clients keep working while the interface evolves. In the design of APIs, a version identifies a specific contract: the endpoints, operations, fields, data types, error formats and behaviors that a client can rely on.
Versioning matters because an API is a promise to code its provider does not control. Mobile apps, partner integrations and internal services may run old releases for months or years, so the provider needs a disciplined way to introduce breaking changes without breaking callers.
Breaking and non-breaking API changes
The core question of API versioning is whether a change is backward compatible. A non-breaking change lets every existing client continue unchanged; a breaking change forces at least some clients to update their code. Only breaking changes require a new major version, provided clients follow the tolerant reader principle and ignore fields they do not recognize.
| Change | Usually breaking | Reason |
|---|---|---|
| Add an optional request field | No | Old clients simply omit it |
| Add a response field | No, for tolerant clients | Unknown fields are ignored |
| Add a new endpoint | No | Existing calls are unaffected |
| Remove or rename a field | Yes | Clients reading it fail or get null |
| Make an optional field required | Yes | Old requests become invalid |
| Change a field type or format | Yes | Parsing and validation break |
| Add a value to an enum in a response | Often | Strict clients reject unknown values |
| Change default behavior or error codes | Yes | Client logic depends on them |
Subtle changes cause most incidents. Tightening validation, reordering a list that clients assumed was sorted, or changing rate limits can break integrations even though the schema looks the same. A useful rule is that observable behavior is part of the contract, not only the documented fields.
Where the version is expressed
Web APIs signal the version in one of several places in the request, and each choice has consequences for routing, caching and discoverability. The URI path approach, such as /v2/orders, is the most common for public REST APIs because it is visible and easy to route at an API gateway.
| Scheme | Example | Main trade-off |
|---|---|---|
| URI path | /v2/orders | Very visible, but a version is not a resource |
| Query parameter | /orders?version=2 | Easy to add, easy to forget or cache badly |
| Custom header | Api-Version: 2 | Clean URLs, less visible in logs and browsers |
| Media type | Accept: application/vnd.shop.v2+json | Fine-grained, but complex for clients |
| Date-based version | Api-Version: 2026-03-01 | Many small steps, requires careful tooling |
| Hostname | v2.api.example.com | Allows separate deployment, adds DNS overhead |
Header and media-type schemes keep resource URLs stable, which purists prefer, but they require caches and CDNs to vary responses by header. Date-based versioning pins each client account to the version current when it first integrated and applies a chain of small transformations to translate between versions, an approach that suits providers with frequent changes and many customers.
Version numbers and semantic versioning
Most public APIs expose only a major version to clients, because minor and patch changes are backward compatible by definition and need no client action. Internally, teams often track the full Semantic Versioning form, MAJOR.MINOR.PATCH, in the OpenAPI description and in SDK releases, so that documentation and generated code reflect every additive change.
Other API styles handle versioning differently. GraphQL usually avoids explicit versions: fields are added freely and old ones are marked with the @deprecated directive, then removed after monitoring shows no usage. gRPC with Protocol Buffers relies on field numbers that must never be reused, so adding fields is safe and breaking changes go into a new package name such as orders.v2.
| API style | Usual approach | Key rule |
|---|---|---|
| REST API | Major version in path or header | Bump major only for breaking changes |
| GraphQL | Continuous evolution of one schema | Deprecate fields before removing them |
| gRPC | Version in the protobuf package name | Never reuse or renumber field tags |
| Webhook payloads | Version per subscription or event type | Let consumers opt in to new formats |
Deprecation and the version lifecycle
A versioning policy is only credible with a defined deprecation and sunset process. A deprecated version still works but is no longer recommended; at its sunset date it stops responding or returns an error. Public providers commonly give six to twenty-four months between deprecation and removal, depending on how hard client updates are, and mobile apps usually need the longer end.
- Announce deprecation in documentation, changelogs and directly to registered developers.
- Signal it in responses with the
DeprecationandSunsetHTTP headers so clients can detect it automatically. - Measure traffic per version and per client before choosing a removal date.
- Run brownouts, short planned outages of the old version, to reveal forgotten integrations before the final shutdown.
Running many versions in parallel has a real cost: each live version must be tested, secured and monitored. Many teams therefore limit support to two concurrent major versions and implement old versions as thin adapters over the current code rather than as separate copies.
Common mistakes in API versioning
The most frequent error is versioning too often, which fragments the client base, or too late, after a breaking change has already shipped silently. Others include using one version number for unrelated resources, so a single change forces every client to migrate, and failing to version error formats and webhook payloads, which clients parse as carefully as normal responses.
API-first design reduces the need for breaking changes: when the contract is reviewed before implementation, naming and structure problems are caught while they are still cheap to fix. Designing responses with extensible objects instead of bare arrays or scalars also leaves room to add data later without a new version.
Understanding it simply
An API is like a printed form that two companies agree to exchange. If one company suddenly moves the boxes around, the other company’s software fills in the wrong places. API versioning means printing a new edition of the form and labeling it clearly, while still accepting the old edition for a while.
That way, everyone can switch to the new edition at their own pace, and nothing stops working overnight.
Applied in practice
A payments provider offers a REST API under /v1. Its charge object stores the amount as a decimal string such as “19.99”, which causes rounding bugs in several client languages. The team decides to represent amounts as integer minor units, so 19.99 becomes 1999, which is a breaking change. They publish /v2/charges with the new format, update the OpenAPI document and release new SDK versions the same day.
The /v1 endpoints keep working through an adapter that converts values. Responses from /v1 now carry Deprecation and Sunset headers with a date eighteen months away. Dashboards show which merchants still call /v1, and the team contacts the largest ones directly. Two short brownouts reveal a forgotten reporting script at one merchant. When the sunset date arrives, less than one percent of traffic remains on the old version, and its removal passes without a customer outage.