API Versioning Best Practices: The Complete Guide
Your API will change. Users will add new features, deprecate old endpoints, and evolve data structures. Without a solid versioning strategy, you'll break existing clients every time you ship an update. This guide covers every major API versioning approach, when to use each one, and real-world examples from Stripe, GitHub, Twitter, and more.
Quick Takeaways
- → URI path versioning (/v1/users) is the most common and easiest to debug. Use it for public APIs.
- → Header versioning keeps URIs clean but requires tooling to inspect. Use it for internal or B2B APIs.
- → Content negotiation (Accept header) is the most RESTful but hardest to implement. Use it for mature platforms.
- → Never break v1 — always introduce v2, v3, etc. and deprecate old versions with clear timelines.
- → Document deprecation with sunset headers, migration guides, and advance notices (90+ days minimum).
Why API Versioning Matters
APIs are contracts. When you change the response format, rename fields, or remove endpoints, you break applications that depend on your API. Versioning allows you to evolve your API while maintaining backward compatibility for existing users.
Consider Stripe's approach: their /v1/charges endpoint has existed for years. When they introduced new features,
they didn't change the existing response — they added fields. When they needed breaking changes, they introduced /v2/
and gave users months to migrate. This is why Stripe has one of the best developer reputations in the industry.
5 API Versioning Strategies Compared
1. URI Path Versioning
The most straightforward approach: include the version number directly in the URL path.
GET https://api.example.com/v1/users
GET https://api.example.com/v1/users/123
POST https://api.example.com/v2/users
Pros:
- • Visible in browser address bar and logs
- • Easy to test with curl, Postman, or any HTTP client
- • Clear separation between versions
- • Works with all caching layers and CDNs
Cons:
- • Pollutes URIs with version numbers
- • Technically violates REST principles (URIs should identify resources, not versions)
- • Requires routing configuration for each version
When to use: Public APIs, developer-facing services, and any API that needs to be easy to debug.
Used by: Stripe, GitHub, Twitter, Shopify, Twilio
2. Query Parameter Versioning
Pass the version as a query string parameter.
GET https://api.example.com/users?version=1
GET https://api.example.com/users?v=2
Pros:
- • Clean URIs without version numbers
- • Easy to implement (just read query param)
- • Simple to override per-request during testing
Cons:
- • Version not visible in REST resource identifier
- • Can conflict with other query parameters
- • May be cached incorrectly by some CDNs
When to use: Internal APIs, simple services, or when URI cleanliness is a priority.
3. Custom Header Versioning
Use a custom HTTP header to specify the API version.
GET https://api.example.com/users
X-API-Version: 1
GET https://api.example.com/users
Accept-Version: v2
Pros:
- • Clean URIs that follow REST principles
- • Version is explicit and documented
- • Easy to change version programmatically
Cons:
- • Not visible in browser address bar
- • Requires HTTP client configuration
- • Harder to test quickly (need header tools)
When to use: B2B APIs, internal microservices, or when working with sophisticated API consumers.
Used by: Fastly, some GitHub Enterprise APIs
4. Content Negotiation (Accept Header)
Use the standard HTTP Accept header with a custom media type to specify version.
GET https://api.example.com/users
Accept: application/vnd.example.v1+json
GET https://api.example.com/users
Accept: application/vnd.example.v2+json
Pros:
- • Most RESTful approach (uses HTTP as designed)
- • Clean URIs that identify only the resource
- • Standard HTTP mechanism (content negotiation)
- • Can negotiate both format AND version
Cons:
- • Complex to implement correctly
- • Hard to test without HTTP client tools
- • Not intuitive for new developers
- • Requires careful header parsing
When to use: Mature platforms, REST purists, or when you need fine-grained control over response formats.
Used by: GitHub API (primary method), Heroku
5. Media Type Versioning
Similar to content negotiation but focuses on the response format version specifically.
POST https://api.example.com/users
Content-Type: application/vnd.example.user.v1+json
When to use: When request/response schemas evolve independently of API behavior.
Real-World Examples
GitHub API (v3 → v4)
GitHub uses Accept header versioning for their REST API:
Accept: application/vnd.github.v3+json
When they launched GraphQL, they created an entirely new endpoint (/graphql) rather than versioning it.
This is a smart pattern: use REST for CRUD operations, GraphQL for complex queries.
Stripe API
Stripe uses URI path versioning and has maintained remarkable backward compatibility:
https://api.stripe.com/v1/charges
https://api.stripe.com/v1/customers
Stripe also supports date-based versioning — you can send a Stripe-Version: 2020-08-27 header
to get the API behavior as it existed on that date. This is brilliant for long-running applications that need predictable behavior.
Twitter API v2
Twitter went through a major API overhaul. v1.1 used URI versioning, and v2 continued the pattern:
https://api.twitter.com/1.1/statuses/home_timeline.json
https://api.twitter.com/2/tweets
The v2 API also introduced more granular endpoints and better pagination, showing how versioning enables fundamental redesigns.
Deprecation Strategy: The Other Half of Versioning
Versioning is useless without a solid deprecation plan. Here's how to retire old versions without angering users:
1. Announce Early and Often
Give at least 90 days notice before deprecating a version. Better: 6-12 months for major changes. Communicate via:
- • API response headers (deprecation warnings)
- • Email to registered developers
- • Changelog and documentation updates
- • Blog posts for major transitions
2. Use Deprecation Headers
Deprecation: true
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Link: <https://docs.example.com/migration/v1-to-v2>; rel="deprecation"
These standard HTTP headers tell clients exactly when the API will sunset and where to find migration docs.
3. Provide Migration Guides
Every version change should have a migration guide that covers:
- • What changed (with before/after examples)
- • Why the change was made
- • Step-by-step migration instructions
- • Code snippets in multiple languages
- • FAQ and troubleshooting tips
4. Monitor Usage
Track which versions are still in use and by whom. Reach out to heavy users personally before sunsetting.
Versioning Anti-Patterns to Avoid
🚫 Breaking Changes Without Version Bump
Never remove fields, change types, or alter behavior in a "compatible" update. If it breaks existing clients, it needs a new version.
🚫 Too Many Versions
Don't support v1, v1.1, v1.2, v2, v2.1, v2.2, v3 simultaneously. Pick 2-3 versions max and deprecate aggressively.
🚫 Vague Deprecation Timelines
"Will be deprecated soon" is useless. Give exact dates: "Deprecated on Jan 1, 2026. Sunset on Jun 30, 2026."
🚫 Inconsistent Versioning Strategy
Don't mix URI versioning for some endpoints and header versioning for others. Pick one pattern and stick to it.
Our Recommendation
For most public APIs: Use URI path versioning. It's the easiest to understand, debug, and document.
Start with /v1/ and plan for /v2/ from day one.
For internal/B2B APIs: Consider header versioning. Your consumers are more sophisticated, and you can work with them directly during transitions.
Whatever you choose: Document it clearly, deprecate slowly, and never break existing clients without warning.
Related Resources
- • API Design Checker Tool
- • How to Test an API Online
- • REST API Design Best Practices
- • API Design Cheat Sheet
Share this guide
Help others discover this guide by sharing it.
About this guide: Part of the DevKits API Design series. Last updated March 2026. This guide covers industry best practices based on analysis of Stripe, GitHub, Twitter, Shopify, and other developer-first APIs.