API versioning is the operating system for change at an integration boundary. It answers a practical question: how can a server improve while current clients continue to interpret its responses correctly? The answer is not a number in a URL by itself. A version needs a selection rule, a compatibility promise, a consumer inventory, a migration path, an observation plan, and a retirement decision. Google API versioning treats versioning as an API design concern, while RFC 9110 reminds us that HTTP behavior carries meaning independent of the chosen version label. This guide focuses on the lifecycle between those two facts.
Define the compatibility boundary
List every consumer before deciding how to version: browser clients, mobile releases, internal services, partner systems, reporting jobs, SDKs, and human-operated scripts. Record who owns each consumer, how quickly it can change, and which operations it uses. Then define what must remain stable: resource names, required fields, state meanings, status codes, authorization behavior, ordering, and error shape. A server team may consider an altered default a small refactor; a finance integration may see it as a different invoice. The boundary is the set of observable promises that a consumer has a reasonable basis to rely on.

| Versioning decision | Question | Owner and evidence |
|---|---|---|
| Selector | How does a caller request a behavior set? | API owner, gateway rule, and request examples |
| Compatibility | Which changes remain safe within a version? | Consumer impact matrix and contract tests |
| Lifecycle | How long is a version supported? | Published policy, dates, and support owner |
| Retirement | What evidence permits removal? | Usage inventory, migration proof, and sign-off |
Classify changes by consumer effect
Treat change classification as a review step, not a semantic argument at the end of a pull request. Adding an optional response property can be compatible for tolerant clients. Removing a property, making an optional request field required, changing an enum meaning, tightening authorization, altering a default filter, or changing a 404 to a 200 empty result can be breaking even if the schema diff looks modest. A migration can also be required when a field keeps its type but its meaning changes. Write the old and new examples side by side and ask what each existing consumer would do with them.
Separate representation change from business change
Sometimes the right answer is a new representation; sometimes it is a new resource or operation. If an order moves from one total calculation to another, hiding the difference behind the same field name can preserve syntax while breaking trust. Give material semantics a name, document the calculation or state transition, and let consumers migrate deliberately. Keep the version boundary narrow: do not fork every endpoint because one response needs a new field, but do not force unrelated consumers through a risky compatibility shim either.
Choose a version strategy operators can see
Path, header, and media-type versioning can all be workable. The important choice is whether callers can select the intended behavior without ambiguity and whether telemetry can separate versions during incidents. A path selector is easy to expose in logs and documentation. A header can keep resource URLs stable but is easier to omit in manual or intermediary requests. A media type can express representation intent but needs disciplined tooling. Choose one default, document precedence when multiple selectors appear, and test proxies, caches, generated clients, and support tools before rollout.
| Strategy | Strength | Risk to control |
|---|---|---|
| Path segment | Visible in routes, logs, and simple examples | URL proliferation and duplicated gateway rules |
| Request header | Keeps resource path stable | Omitted or stripped selector creates ambiguity |
| Media type | Makes representation negotiation explicit | Harder for casual clients and tooling to inspect |
| No explicit fork | Small surface and fewer parallel implementations | A silent breaking change can surprise every consumer |
Release with a compatibility window
For a breaking change, publish the new behavior while the old behavior still works. Keep the machine-readable contract and examples aligned in the OpenAPI Specification, update SDKs or adapters, migrate one low-risk consumer, then compare results. Keep the old path observable and avoid a hidden translation layer that masks errors forever. During the window, report request counts by version, caller, operation, and outcome. A deprecation notice should identify the replacement, the deadline, and the support route. RFC 9457 can help make migration failures structured and actionable rather than a generic server message.
Measure adoption and semantic correctness
Traffic share is necessary but not sufficient. A client can move to a new version while silently dropping a field, misreading a state, or retrying an operation unsafely. Pair adoption with contract-test results, validation failures, authorization denials, latency, retry volume, and user-visible corrections. Include the selected version and contract revision in traces or structured logs. Keep a small sample of request and response shapes with sensitive data removed so support can compare actual behavior with the documented promise. Review signal quality after each migration; if the team cannot tell which version caused an outcome, the lifecycle is not observable enough to retire safely.
Retire versions as a governed decision
A retirement plan starts with evidence, not a calendar alone. Confirm the last observed use for every known consumer, ask owners to remove or upgrade dormant jobs, and identify clients whose traffic is hidden behind shared credentials. Publish a final migration guide with before-and-after requests, changed errors, and test data. Set a date for rejecting new use before the final removal if that helps expose stragglers. When the old implementation is removed, keep the failure response clear and safe; do not redirect an old request into a new semantic contract without the caller's knowledge.
Use a partner migration as the proof exercise
Imagine a partner creates orders with a field whose historical default is now unsafe. Trace the old request, the new required input, authentication, validation, fulfillment state, retries, and support path. The migration pack should show which version the partner selected, how an invalid request is returned, how a duplicate is recognized, and what the partner sees while work is pending. Review the exercise with someone who did not build the endpoint. The adjacent event-driven systems guide is useful when the change emits messages; compare its representation with the REST API contracts guide and the growing-team versioning field guide before setting the support window.
Communicate the migration as an operational change
A deprecation message should explain the replacement in the language of the caller's task, not only identify a route. Include the old and new examples, changed authorization or error behavior, monitoring date, support contact, and the last date for a safe test. Ask the consumer to acknowledge the migration or provide evidence from its own environment. Keep a record of unanswered notices and use it to adjust the cohort or escalation path. Clear communication lowers the chance that an old client will continue quietly until removal turns a known compatibility problem into an outage.
Build a change pack before opening migration
A version change pack gives each consumer owner the same facts: the old request and response, the new request and response, semantic differences, security differences, test fixture, selected version, migration deadline, support route, and rollback or adapter behavior. Add a usage snapshot by operation rather than a single traffic total. For a mobile client, include the first release that can adopt the new contract; for a partner, include notice and contact expectations. The pack should also say what the server will do when a client sends an unknown or retired version, because a clear failure is safer than a silent translation.
Score migration risk by consumer
Use a simple matrix to prioritize help. Score each consumer for business consequence, release control, data sensitivity, migration effort, and confidence in observed usage. A low-volume partner with high financial consequence may need more attention than a high-volume internal test client. Use the score to choose the migration cohort, not to turn compatibility into a mathematical promise. Revisit it when a client changes owners or a security requirement changes.
| Consumer factor | Low-risk signal | High-risk signal |
|---|---|---|
| Release control | Provider can deploy a coordinated update | Consumer release is slow or external |
| Business effect | Read-only convenience data | Money, access, or regulatory workflow |
| Observed use | Named caller and complete telemetry | Shared credential or unknown job |
| Migration effort | Additive field or SDK update | State, authorization, or retry change |
During the window, review the matrix with support and security, not only the API team. A deprecation date that looks reasonable in code can be unsafe if the consumer has a long approval cycle or if the old version is the only path that records a required audit fact. The outcome of the review should be a cohort order, an exception owner, and an explicit condition for shortening or extending the window.
API versioning takeaways
- Inventory consumers and define the observable promise before choosing a version label.
- Classify changes by what a supported client can fail to do or misunderstand.
- Select a version mechanism that clients can request reliably and operators can observe.
- Run migrations through a compatibility window with examples, ownership, and support.
- Retire only after usage, correctness, and recovery evidence make the removal decision safe.
API versioning FAQ
Should every release create a new API version?
No. Version boundaries should protect consumers from incompatible observable behavior. Routine bug fixes, additive optional fields, and internal implementation changes can often remain within a version when the documented semantics stay intact.
Are headers better than URL versions?
Neither is universally better. Headers keep resource paths stable but require reliable propagation and visibility. URL versions are easy to inspect but can multiply routes. Pick the mechanism that fits your clients, gateways, caches, documentation, and incident tooling.
What should happen when an old client refuses to migrate?
Name the owner, understand the business dependency, offer a bounded adapter or support window if justified, and document the risk. Do not keep a version indefinitely without an explicit decision about its cost, security posture, and retirement condition.
Conclusion: treat versions as promises
API versioning works when it gives a consumer time and information to move safely. Define the boundary, classify semantic impact, expose the selected behavior, observe real callers, and make retirement an owned decision. The result is a service that can evolve without turning compatibility into a last-minute negotiation or an invisible production risk.