Blog Get API key
BlogAPI Versioning Strategies
All articles Abstract visualization of API versioning branching and compatibility

API Versioning Strategies That Don't Break Your Partners

Every API versioning discussion eventually collapses into a debate about URL versioning versus header versioning versus content negotiation. Teams spend weeks on this decision and then discover — usually after their first breaking change — that the version scheme was never the hard part. The hard part is the deprecation process: how much notice you give, how you communicate it, whether your gateway enforces sunset dates, and whether your documentation reflects the current state of all active versions simultaneously. The scheme is a routing mechanism; the process is what determines whether your partners have a bad day or a very bad quarter.

URL Versioning: The Default for Good Reason

URI versioning — /v1/invoices, /v2/invoices — is the most common approach in production partner APIs, and it earned that position through practical advantages. It's immediately visible in logs, in network traces, in gateway access logs, and in browser network panels. A partner debugging an integration failure sees the version in the URL before they look at anything else. It's cache-friendly: a response from /v1/invoices/123 can be cached with a separate TTL from /v2/invoices/123 with no content negotiation overhead. And it's testable without any special client configuration — you can curl https://api.example.com/v1/invoices without headers.

The argument against URL versioning — that "the version is a property of the representation, not the resource" — is technically correct but operationally irrelevant for most partner API use cases. REST purists prefer header-based versioning because a resource's URL should be stable across representation changes. In practice, partners manage version pinning through environment variables, deployment configs, and sometimes by finding the version in documentation and hard-coding it, not by crafting headers. URL versioning works with whatever HTTP client library they're using, without any configuration.

The one legitimate concern with URL versioning is URL proliferation. If you version every resource individually rather than versioning the API surface as a whole, you end up with /v1/invoices, /v2/charges, /v1/customers, /v3/webhooks — and partners need a version map to know which version of each resource they're on. Versioning the entire API surface together avoids this: when you release v2, every endpoint under /v2/ is available, even if most endpoints are identical to their v1 counterparts. The routing layer at the gateway proxies unchanged endpoints transparently.

Header Versioning: When It Makes Sense

Header versioning — typically via Api-Version: 2025-11-01 or a custom X-API-Version header — makes more sense when your versioning semantics are calendar-date based rather than major-version based. Calendar versioning communicates "this is the state of the API as of this date" rather than "this is a major revision with breaking changes." For APIs that evolve incrementally with many small additive changes and occasional breaking changes, calendar versioning with explicit sunset dates per change is more expressive than a major version number that jumps from 1 to 2 only when something breaks.

The gateway implementation is more complex: the routing decision requires header inspection rather than URL prefix matching, which means every request that doesn't include a version header needs a defined fallback (use latest stable? use the oldest stable? return 400?). Your gateway policy on this fallback is a significant API contract decision — changing it later is a breaking change for clients that have been relying on the implicit default.

One genuinely useful property of header versioning: you can add a new field to a response body and route only clients that request the new version to the new handler, while keeping the old handler intact. With URL versioning, adding a field to /v1/invoices without bumping to /v2/ is possible but muddies your versioning semantics. With date-header versioning, adding the tax_breakdown field to invoice responses as of 2026-01-01 is a clean opt-in change.

Content Negotiation: Theoretically Correct, Practically Painful

Content negotiation via Accept headers — Accept: application/vnd.yourapi.v2+json — is the most RESTful approach and the least practical for partner APIs. The vendor-specific media type is opaque to every generic HTTP client, load balancer, CDN, and proxy in the chain. Caching behavior is unpredictable unless you set explicit Vary: Accept headers everywhere. Debugging is hard because the version isn't visible in logs without parsing headers. And the majority of client libraries default to Accept: application/json, meaning partners have to configure versioning explicitly for every HTTP client instantiation.

We're not saying content negotiation is wrong — it's the correct model for hypermedia APIs where representations genuinely evolve. For a partner API where the primary concern is predictable stability and operational debuggability, it adds friction without proportional benefit.

The Sunset Timeline: More Important Than the Scheme

The version scheme determines how you route requests. The sunset timeline determines whether your partners trust you. A one-month deprecation window for a breaking change is a hostile partner experience — a month is not enough time for a mid-size engineering team with competing priorities to discover the deprecation, plan the work, allocate sprint capacity, write the migration, test it, and deploy to production. Six months is the minimum realistic window for anything touching production payment flows or core data retrieval. Twelve months is common for large platforms with enterprise partners.

The mechanics of a sunset: your gateway should start returning a Sunset header (RFC 8594) on requests to deprecated versions as soon as the sunset date is set. This is a machine-readable signal that partners' monitoring can detect automatically. Pair this with a Deprecation header (also RFC 8594) and a Link header pointing to the migration guide. Partners who are reading response headers — and mature integrations do — will catch the deprecation signal without needing to monitor your changelog.

Consider a platform API team that ran a payments integration with an early-stage partner ecosystem in 2025. When they released v2 of their charges API with a changed response structure for 3DS authentication data, they gave a three-month deprecation window. Two partners migrated quickly; four did not. On the sunset date, traffic to the v1 endpoint dropped by about 60% — but the remaining 40% forced a one-month extension, a support escalation to each non-migrated partner, and an all-hands debugging session the week the deadline passed. The lesson wasn't about the version scheme — the URL versioning worked fine. The lesson was that three months was insufficient notice and that they had no automated signal to tell them which partners had not migrated.

The automated detection of "who is still on v1" is a gateway-level analytics question: partition traffic by API key and version, identify keys that have zero v2 traffic as the sunset date approaches, and surface that as a proactive alert. This is the difference between finding out you have four non-migrated partners one month before sunset versus one day before.

Additive Changes: The Free Lunch You Should Be Taking

Not every API change requires a version bump. Additive changes — adding optional fields to response bodies, adding optional query parameters, adding new endpoints — are generally backward-compatible. A client that ignores unknown fields in a JSON response (which all well-written clients should do) is unaffected by new fields in the response body. A client that doesn't send a new optional parameter continues to work as before.

Documenting your backward-compatibility policy explicitly in your API contract documentation — "we will add optional fields to response bodies without bumping the version; we will never remove or rename existing fields without a version bump and deprecation cycle" — lets partners write integration code with the correct level of defensiveness. Partners who know your additive-change policy can write code that tolerates new fields rather than failing on unknown keys. Partners who don't know your policy have to assume the worst and write defensive code that can make debugging harder.

The hard boundary is removing fields, renaming fields, changing field types, changing authentication behavior, or changing the semantics of existing fields (even if the type is unchanged). All of those require a version bump, a deprecation timeline, and migration documentation. The sunsets that go smoothly are the ones where the migration guide is written before the sunset is announced, not after partners start asking questions.