
A mobile app released six months ago is still calling your API when your team needs to rename a field, change pagination, or split one endpoint into two. That is the real reason to learn how to version a REST API: not to add `/v1` everywhere by habit, but to evolve a product without turning existing clients into production incidents.
API versioning is a compatibility policy as much as a routing decision. A useful strategy tells consumers what can change safely, what requires a new contract, how long the old contract will remain available, and how they will learn that migration is necessary.
Start With Compatibility, Not Version Numbers
Before choosing a versioning format, separate changes into two categories: additive changes and breaking changes. Additive changes usually preserve the current contract. Adding an optional response property, a new endpoint, or a new optional request parameter is typically safe for clients that ignore unfamiliar data.
Breaking changes alter what a correctly written client expects. Examples include removing or renaming fields, changing a field’s data type, making an optional input required, changing authentication behavior, or modifying pagination semantics. A response property changing from a string to an object may look small in a pull request, but it can break generated SDKs, typed front ends, data pipelines, and integrations outside your organization.
The best versioning strategy is often to avoid unnecessary versions. Design contracts that can grow: use optional fields, stable resource identifiers, explicit defaults, and extensible representations. Do not expose internal database models as API responses unless you are prepared to preserve their shape indefinitely.
Compatibility also depends on the consumer. A public API with unknown customers needs a more conservative policy than an internal service consumed by a few teams that deploy together. Even internal APIs can gain long-lived clients through scheduled jobs, partner integrations, and mobile applications, so document assumptions rather than relying on organizational proximity.
How to Version a REST API: Choose a Delivery Style
There is no single correct place for an API version. The right choice depends on discoverability, caching, tooling, client expectations, and the lifespan of your API. The three common approaches are URL paths, request headers, and media types.
URL path versioning
Path versioning places the major version directly in the route, such as `/api/v1/orders` and `/api/v2/orders`. For most teams, this is the most practical default. It is easy to read in logs, browser requests, monitoring dashboards, API gateways, and support tickets. It also works cleanly with routing rules and generated documentation.
Its trade-off is philosophical rather than operational. Some REST purists argue that a resource’s identity should not change because its representation changes. In practice, clearly visible versioning often provides more value than theoretical purity, especially when several client teams need to diagnose behavior quickly.
Use path versions for major contract generations, not every release. `/v1.0.1` in a URL creates noise and pushes consumers toward depending on implementation-level release numbers.
Header versioning
Header versioning keeps routes stable, for example `GET /orders` with an `API-Version: 2026-08-01` header. This can make URLs cleaner and support date-based versions, which are useful when you want consumers to opt into a contract snapshot.
The downside is visibility. A copied URL no longer fully describes the request, and developers may forget the header during manual testing. Caches, gateway policies, and API documentation must also vary behavior correctly by header. If you use this approach, make required headers highly visible in examples and return clear errors when they are missing or unsupported.
Media type versioning
Media type versioning uses the `Accept` header, such as `Accept: application/vnd.example.orders.v2+json`. It follows HTTP content negotiation closely and can be useful when multiple representations of the same resource must coexist.
It is also more demanding to implement and explain. Many teams gain little from its added complexity, particularly when clients, gateways, and API testing tools already need straightforward workflows. Choose it when representation negotiation is a meaningful part of your API design, not simply because it sounds more RESTful.
Query parameter versioning, such as `/orders?version=2`, is generally a weaker option for public APIs. Parameters are easy to omit, can complicate caching, and blur the line between resource filtering and contract selection. It can be acceptable for short-lived internal migrations, but it should rarely be the long-term standard.
Use Major Versions and a Clear Change Policy
For REST APIs, major-version thinking is usually more useful than exposing full semantic version numbers. Treat v1 and v2 as separate public contracts. Deliver compatible additions within v1, then introduce v2 only when you need to make a breaking change.
Write this policy down and apply it consistently. For example, your team might allow new optional fields and endpoints in v1, while reserving v2 for removed fields, changed types, altered error formats, or changed authorization requirements. The policy should also answer whether field ordering matters, whether unknown enum values may appear, and how clients should handle null values.
Enums deserve particular attention. Adding a new status value can break clients that assume a fixed set of values and fail on an unknown case. Treat enum expansions as potentially breaking unless your client guidance and generated SDKs explicitly support forward-compatible handling.
A version number does not eliminate the need for disciplined behavior within a version. If `/v1/orders` is silently changed in a way that breaks clients, the presence of `/v2` elsewhere provides no protection.
Build v2 as a Contract, Not a Forked Codebase
When a breaking change is justified, avoid cloning the entire service and maintaining two independent implementations. That approach creates duplicated business rules, inconsistent security patches, and expensive operational drift.
Instead, keep core domain logic behind a stable internal boundary. Let v1 and v2 adapters translate their request and response models to that shared logic. A v1 adapter may continue returning `customer_name`, while v2 returns a structured `customer` object. Both can call the same order service, validation layer, and authorization checks.
This pattern has limits. If v2 represents a fundamentally different business workflow, forcing both versions through identical internal models can create confusing abstractions. In that case, separate implementations may be warranted. The decision should follow domain behavior, not an automatic preference for reuse.
Version your API specification alongside code. Whether your team uses OpenAPI or another contract format, keep an explicit spec for each supported major version. It gives client developers a dependable source of truth and enables automated checks for accidental contract changes.
Deprecate With Dates, Signals, and Migration Help
Publishing v2 is only half the job. Consumers need a controlled path away from v1. Announce the new version early, explain the business and technical reason for the change, provide migration examples, and state a retirement date.
Deprecation should be observable in normal client workflows. You can add deprecation and sunset response headers, include a non-sensitive migration notice in documentation, and track requests by API version. Do not rely on one announcement email for an API that has external customers or multiple product teams.
A credible deprecation plan includes four operational commitments:
- A published support window for the old version.
- A clear list of behavior and schema differences.
- Migration guidance, including changed errors and edge cases.
- A final shutdown process with reminders before enforcement.
Support windows vary. A private service used by one actively maintained application may need only a short overlap. A public API used in mobile clients or embedded systems may need many months. Security vulnerabilities can shorten that timeline, but teams should still communicate the risk and offer the safest feasible migration route.
Test Contracts and Watch Real Client Traffic
Unit tests are not enough for versioned APIs. Add contract tests that verify response schemas, required fields, status codes, validation errors, pagination links, and authentication behavior for every supported version. Consumer-driven contract testing can be especially valuable when several internal teams integrate independently.
Run compatibility checks in continuous integration before publishing a new specification. A removed property, narrowed enum, or changed required field should fail the build unless the change is intentionally tied to a new major version.
After release, instrument your gateway and application telemetry by version, endpoint, client identifier where appropriate, status code, and error class. This data answers the question that matters during deprecation: who is still using v1, and which calls will fail if it disappears? It also reveals whether v2 adoption is stalled because of a missing feature, confusing documentation, or an unexpected client error.
Keep Versioning Boring and Predictable
A versioned API is easier to operate when every layer agrees on the contract. Route definitions, authentication middleware, rate limits, API documentation, SDK generation, logging, and error formats should all recognize the version explicitly. A v2 endpoint that accidentally returns v1 errors creates needless confusion during migration.
Choose the simplest approach your consumers can understand, establish a narrow definition of breaking change, and give old clients a real transition period. Good API versioning does not make change painless, but it turns change into a managed engineering process instead of a surprise waiting in someone else’s deployment queue.



