API Versioning Strategy
Architecture Diagram
The Three Approaches
There are three common ways to version an API, and each comes with real trade-offs.
URL path versioning (/v1/users, /v2/users) is the simplest to understand. Clients know exactly which version they hit. Caching works naturally because the URL is different. The downside: when you release v2, every client has to change every URL. GitHub uses this approach and it works well for public APIs where discoverability matters.
Header-based versioning is what Stripe does. You pass Stripe-Version: 2023-10-16 and get behavior pinned to that date. The URL stays the same. New accounts automatically get the latest version. Existing accounts stay pinned until they explicitly upgrade. This is elegant but harder to test casually with a browser.
Query parameter versioning (/users?version=2) sits somewhere in between. It is easy to use in a browser but pollutes the URL and can conflict with other query parameters. Most teams avoid this approach for production APIs.
Breaking vs Non-Breaking Changes
The hardest part of API versioning is deciding what counts as breaking. Adding a new optional field to a response is not breaking. Adding a new required field to a request body is. Changing a field from string to integer is. Removing a field is.
Stripe publishes a detailed changelog where every change is tagged as breaking or non-breaking. This level of discipline takes effort but prevents surprise outages for consumers.
Deprecation and Sunset Policies
A version without a sunset date is a version you will maintain forever. Set expectations early. Publish sunset dates in response headers (Sunset: Sat, 01 Mar 2025 00:00:00 GMT). Send deprecation warnings in headers (Deprecation: true). Track usage metrics per version so you know when it is safe to retire.
Twilio sends email notifications 12 months before sunsetting a version. They also track which accounts are still calling deprecated endpoints and reach out directly. This operational overhead is the real cost of versioning.
Gateway-Level Version Translation
The most scalable pattern is handling version translation at the API gateway. Backend services implement the latest contract. The gateway intercepts requests for older versions and transforms them before forwarding. This keeps business logic clean and centralizes version compatibility in one place.
Kong, Envoy, and AWS API Gateway all support request/response transformation rules. The transformation layer becomes a thin adapter that maps old field names to new ones, fills in defaults for removed parameters, and reshapes response payloads.
When to Skip Versioning Entirely
If your API is internal and consumed by services your team owns, consider skipping formal versioning altogether. Use contract tests (Pact, Protolock for gRPC) to catch breaking changes at build time. Deploy consumers and providers in lockstep. Reserve versioning for external or partner-facing APIs where you do not control the client release cycle.
Key Points
- •URL path versioning (/v1/users) is the most visible and cache-friendly approach but forces clients to update URLs on every major version
- •Header-based versioning (Stripe uses Stripe-Version: 2023-10-16) keeps URLs clean and enables per-request version pinning
- •Non-breaking changes like adding fields should never require a new version. Only breaking changes like removing fields or changing types warrant a version bump
- •Every API version needs a published sunset date. Stripe gives 24 months of overlap. Twilio gives 12 months minimum
- •Version negotiation at the API gateway layer lets backend services stay version-unaware while the gateway handles translation
Common Mistakes
- ✗Creating a new version for every minor change. This fragments the API surface and multiplies maintenance cost across teams
- ✗Skipping deprecation headers and sunset timelines. Consumers need programmatic signals, not just changelog entries
- ✗Maintaining parallel codepaths in the same service for each version. Use a transformation layer at the edge instead
- ✗Treating internal and external APIs with the same versioning rigor. Internal APIs between services you own can tolerate more flexibility