API Versioning Strategies: URL vs Header vs Query Parameter
Every API that lives long enough will need to make breaking changes. How you version your API determines whether those changes are a smooth transition or a painful migration for your consumers. This guide compares the four major versioning strategies with real-world examples from APIs that have been managing versions at massive scale for years.
The Four Versioning Strategies
| Strategy | Example | Used By |
|---|---|---|
| URL path | /v1/users, /v2/users | Twilio, Slack, Google Maps |
| Custom header | X-GitHub-Api-Version: 2022-11-28 | GitHub |
| Date-based header | Stripe-Version: 2024-12-18 | Stripe |
| Query parameter | /users?version=2 | Google (some APIs), Amazon |
URL Path Versioning
How It Works
The version is embedded directly in the URL path. When a breaking change is introduced, a new URL prefix is created:
# Version 1
GET https://api.twilio.com/2010-04-01/Accounts.json
# Google Maps
GET https://maps.googleapis.com/maps/api/geocode/v2/json
# Slack
POST https://slack.com/api/v2/chat.postMessage
Twilio's Approach
Twilio uses a date-based URL path (/2010-04-01/) that has remained stable for over 15 years. Rather than incrementing version numbers, Twilio chose to date-stamp the API at launch and has avoided breaking changes to this day. New functionality is added as new resources under the same version prefix.
Advantages
- Maximally explicit: The version is visible in every URL. There is zero ambiguity about which version a client is using. Logs, documentation, and debugging are straightforward.
- Easy routing: Load balancers, API gateways, and reverse proxies can route by URL path trivially. No header inspection needed.
- Cache-friendly: Different versions produce different URLs, so HTTP caches naturally separate them without cache key configuration.
- Discovery: New developers can see available versions by looking at the base URL. No hidden header knowledge required.
Disadvantages
- URL pollution: Every version creates an entirely new set of URLs. If only one endpoint has a breaking change, clients still need to decide whether to use
/v1/or/v2/for all endpoints. - Unclear scope: Does
/v2/usersmean only the users endpoint changed, or did everything change? URL versioning does not communicate the scope of changes. - Resource identity problem: In REST, the URL is supposed to identify a resource.
/v1/users/123and/v2/users/123are technically different resources representing the same user, which violates REST principles.
Custom Header Versioning
How It Works
The version is specified in a request header, keeping URLs clean and stable:
# GitHub
GET https://api.github.com/repos/octocat/hello-world
X-GitHub-Api-Version: 2022-11-28
# Microsoft Graph
GET https://graph.microsoft.com/users/me
api-version: 1.0
GitHub's Approach
GitHub adopted calendar-based header versioning in late 2022. Each version is identified by a date (e.g., 2022-11-28). When GitHub makes a breaking change, they release a new dated version. The key design decisions:
- If no version header is sent, the oldest supported version is used (safe default)
- Sunset dates are announced well in advance with deprecation headers
- Non-breaking changes (new fields, new endpoints) are added to all supported versions
- Only breaking changes (removed fields, changed types) create new versions
# GitHub deprecation headers in responses
X-GitHub-Api-Version: 2022-11-28
Deprecation: true
Sunset: 2025-04-01
Link: <https://developer.github.com/changes/>; rel="deprecation"
Advantages
- Clean URLs: Resource URLs remain stable across versions.
/repos/octocat/hello-worldis always the same resource regardless of version. - Granular versioning: Breaking changes can be scoped precisely. Version
2023-06-15might only affect the pulls endpoint, but clients opt into the full version. - Content negotiation precedent: Using headers for API behavior is consistent with how HTTP handles content types (
Accept), encoding (Accept-Encoding), and language (Accept-Language).
Disadvantages
- Hidden versioning: New developers may not realize they need to set a header. Default behavior when no header is sent must be carefully considered.
- Harder to test casually: You cannot paste a versioned URL into a browser to test. Every request needs the header configured.
- Cache key complexity: CDNs and proxies must be configured to include the version header in cache keys, or different versions will serve stale responses.
Date-Based Header Versioning (Stripe's Model)
How It Works
Stripe pioneered what is arguably the most sophisticated API versioning system. Each API key is pinned to the version that was current when the key was created. Changes are then opt-in:
# Stripe pins your account to a version
# Override per-request with a header:
curl https://api.stripe.com/v1/charges \
-H "Stripe-Version: 2024-12-18" \
-H "Authorization: Bearer sk_test_..."
What Makes Stripe's Approach Unique
- Account-level pinning: Your Stripe account is pinned to the API version that existed when you integrated. You never experience unexpected breaking changes.
- Per-request override: You can test a newer version on a single request by adding the
Stripe-Versionheader, without changing your account default. - Rolling upgrades: Stripe publishes a changelog for every version date. You upgrade from
2024-06-20to2024-12-18by reviewing the changes between those dates and updating your code. Then you change your account's pinned version in the dashboard. - No version sunset: Stripe maintains backward compatibility across all supported versions simultaneously. They have supported dozens of version dates concurrently.
Advantages
- Zero surprise breaking changes: Clients are never broken by an API update. They upgrade on their own schedule.
- Granular change tracking: Every breaking change has a specific date, making it easy to audit exactly what changed and when.
- Testability: You can test the next version in isolation before committing your entire integration to it.
Disadvantages
- Implementation complexity: The version transformer chain pattern is hard to build and maintain. Most teams cannot invest the engineering effort Stripe does.
- Combinatorial testing: Supporting many version dates simultaneously means testing all of them on every deployment.
- Documentation burden: You need per-version documentation showing exactly which fields and behaviors differ.
Query Parameter Versioning
How It Works
The version is passed as a query parameter:
# Google Cloud APIs (some)
GET https://compute.googleapis.com/compute/v1/projects/my-project?alt=json
# AWS (service-specific)
GET https://ec2.amazonaws.com/?Action=DescribeInstances&Version=2016-11-15
Advantages
- Optional with defaults: Like headers, the version can be omitted to use a default. But unlike headers, query parameters are visible in the URL.
- Easy to add to existing APIs: Adding
?version=2to an existing API requires no URL restructuring and no header infrastructure.
Disadvantages
- Query string pollution: Mixing API behavior controls with data parameters is semantically messy. Is
?version=2&page=3pagination on v2, or is version a filter? - Cache key issues: Query string variations can cause cache fragmentation. Some CDNs strip query parameters by default.
- Not widely adopted: Few modern APIs use this approach, making it unfamiliar to most developers.
Comparison Matrix
| Criteria | URL Path | Header | Stripe-style | Query Param |
|---|---|---|---|---|
| Explicitness | High | Medium | Medium | Medium |
| URL stability | Poor | Excellent | Excellent | Good |
| Cache friendliness | Excellent | Needs config | Needs config | Variable |
| Implementation effort | Low | Medium | Very high | Low |
| Developer experience | Good | Good | Excellent | Acceptable |
| Breaking change safety | Low | Medium | Excellent | Low |
| API gateway support | Universal | Good | Custom | Good |
Practical Recommendations
For Most Teams: URL Path Versioning
If you are building an API and do not have the engineering bandwidth for a sophisticated versioning system, URL path versioning is the pragmatic choice. It is simple, explicit, universally understood, and supported by every tool and gateway. Use it with a commitment to minimize breaking changes and provide long deprecation periods.
For Platform APIs: Stripe-Style Date Versioning
If your API is a platform that thousands of developers depend on and you have the engineering resources, Stripe's model is the gold standard. The investment in version transformer infrastructure pays off through dramatically reduced support burden and happier developers who never wake up to broken integrations.
For Internal APIs: Header Versioning
For APIs consumed only by your own services, header versioning keeps URLs clean and gives you granular control. Since you control both client and server, the discoverability disadvantage is irrelevant.
Avoid: Query Parameter Versioning
Unless you have a specific technical reason (such as AWS-style API compatibility), query parameter versioning offers the worst trade-offs. It is neither as explicit as URL versioning nor as clean as header versioning.
The best API versioning strategy is the one your team can maintain consistently over years. Simple and well-communicated beats sophisticated and poorly managed every time.
Further Reading
- Designing APIs with Swagger and OpenAPI — Covers versioning strategies, backward compatibility, and deprecation patterns in OpenAPI specs.
- Building Microservices (O'Reilly) — Essential guidance on managing breaking changes, consumer-driven contracts, and API evolution at scale.