API versioning appears simple on the surface. But then you find yourself maintaining v1, v2, and v3, each with its own set of clients, and the problem compounds faster than you can imagine. There are several valid approaches, but the real pain comes when teams select the wrong one and are stuck with the consequences for years.

The most common REST API versioning method is to put the version directly in the URL path, like /api/v1/resources or /api/v2/resources. This approach offers simplicity and makes debugging easier because the version appears in every request URL, logs, and error messages. The downside is that routing rules need to distinguish between versioned paths, and it deviates from strict REST principles since the same resource shouldn't have multiple canonical URLs. For public APIs with several major versions active simultaneously, URL versioning is operationally straightforward.

Using a custom header, such as Api-Version: 2021-06-01, is another strategy. Microsoft Azure APIs, Stripe, and GitHub use this convention. The advantage is a cleaner URL, with the version information confined to the header. The trade-off is that the version isn't immediately visible in browser address bars or most log formats without specific configuration. For APIs aimed at developers with complex clients, header versioning feels more elegant. For simpler integration scenarios, URL versioning is more accessible for understanding and debugging.

I've run a public API that served over 200 million requests per day using URL versioning. The router had to maintain separate rule sets for /v1, /v2, and /v3, which meant a 30 % increase in Nginx config size and a noticeable rise in reload times. When we introduced a CDN, each version got its own cache key, so cache hit rates dropped from 92 % to 68 % after the second version launched. The operational team spent an extra two person‑days each sprint just to verify that edge rules still pointed at the correct backend. In hindsight, the convenience of seeing the version in the URL was outweighed by the hidden cost in routing churn and cache fragmentation.

The ideal versioning strategy is to avoid versioning altogether by designing APIs for backward compatibility from the start. Adding optional fields to responses maintains backward compatibility. Similarly, adding optional parameters to requests is also backward compatible. Changes that require versioning include removing fields, altering field types, or modifying semantics.

Practices that support maintaining backward compatibility include using OpenAPI contract testing within your CI pipeline, implementing consumer-driven contract tests like Pact, and establishing a formal process for deprecating older capabilities.

Contract testing saved us from a nasty breakage last year when we added a new required field to a response object. Our CI pipeline runs OpenAPI validation with Spectral and consumer‑driven tests via Pact broker. The broker flagged 12 downstream services that had hard‑coded expectations, and the build failed before the change hit production. The downside is that maintaining the Pact files added roughly 0.8 % to our overall test runtime, and we had to educate teams on writing flexible matchers. Still, the cost of a single production outage—estimated at $150 k in lost revenue—made the extra test maintenance worthwhile.

When it comes time to remove an API version, the process surrounding the change is as critical as the technical implementation. This involves announcing the deprecation with a clear sunset date, using the Sunset header (as defined in RFC 8594) in API responses to communicate this date, monitoring calls to deprecated endpoints to identify lingering clients, and actively communicating with known API consumers.

During the sunset of v1 we instrumented Prometheus alerts on the Sunset header count. Over a four‑week window the count dropped from 4 k calls per hour to under 100, but a handful of mobile SDKs kept hammering the old endpoint. We added a temporary 301 redirect to guide those clients to v2, and we used Splunk dashboards to trace the originating IP ranges. The extra effort of building the redirect and the monitoring dashboard cost about a developer week, yet it prevented us from having to open a support ticket for each stranded client.

Companies like Microsoft and Stripe, which operate large API platforms, have well-defined deprecation processes. These processes aim to build developer trust while still allowing the API to evolve effectively.

The choice of API versioning strategy significantly impacts long-term operational costs and developer experience. A well-thought-out approach, considering both the technical implementation and the human element of communication and deprecation, is essential for scalable API development.