REST API Versioning Strategies: URL, Header & Content Negotiation
API versioning lets you evolve your API without breaking existing clients. Each strategy has trade-offs between visibility, REST purity, and developer experience. This guide covers all four approaches with working examples and a pros/cons comparison.
URL Path Versioning (Most Common)
The version is embedded in the URL path. Simple to understand, route, and document. Used by Stripe, GitHub, and most major public APIs:
# URL versioning — version in path prefix
GET https://api.example.com/v1/users/42
GET https://api.example.com/v2/users/42
# Express router setup — mount each version separately
import express from 'express';
import { v1Router } from './routes/v1';
import { v2Router } from './routes/v2';
const app = express();
app.use('/v1', v1Router); // Legacy — maintained until 2027-01-01
app.use('/v2', v2Router); // Current stable version
# Kong API Gateway routing by path prefix
services:
- name: api-v1
url: http://api-service:8080/v1
routes:
- paths: ['/v1']
- name: api-v2
url: http://api-service:8080/v2
routes:
- paths: ['/v2'] Header Versioning & Content Negotiation
Keep URLs clean by encoding the version in a request header or the Accept header. Both approaches require client-side discipline but produce cleaner resource URIs:
# Option A: Custom header versioning
GET /users/42
API-Version: 2
Authorization: Bearer eyJ...
# Option B: Content negotiation (most RESTful)
GET /users/42
Accept: application/vnd.example.v2+json
Authorization: Bearer eyJ...
# Express middleware — read version from header or Accept
function resolveVersion(req: Request, res: Response, next: NextFunction) {
// Try custom header first
let version = req.headers['api-version'] as string;
// Fall back to Accept header content negotiation
if (!version) {
const accept = req.headers.accept ?? '';
const match = accept.match(/application\/vnd\.example\.v(\d+)\+json/);
version = match?.[1] ?? '1'; // default to v1
}
req.apiVersion = parseInt(version, 10);
next();
}
app.use(resolveVersion);
app.get('/users/:id', (req: any, res) => {
if (req.apiVersion >= 2) return userHandlerV2(req, res);
return userHandlerV1(req, res);
}); Sunset & Deprecation Headers
Always announce deprecated versions via response headers so automated tooling and monitoring can alert developers before the cutoff date:
// Middleware: add deprecation headers to all v1 responses
function deprecationWarning(req: Request, res: Response, next: NextFunction) {
// RFC 8594 Sunset header — machine-readable cutoff date
res.set('Sunset', 'Sat, 01 Jan 2027 00:00:00 GMT');
// Deprecation header — when deprecation was announced
res.set('Deprecation', 'Mon, 01 Jul 2026 00:00:00 GMT');
// Link to migration guide
res.set('Link', [
'<https://devplaybook.cc/api/migrate-v1-to-v2>; rel="successor-version"',
'<https://devplaybook.cc/api/sunset>; rel="sunset"',
].join(', '));
next();
}
// Apply only to v1 routes
app.use('/v1', deprecationWarning, v1Router);
// Response headers clients will see on every v1 call:
// Sunset: Sat, 01 Jan 2027 00:00:00 GMT
// Deprecation: Mon, 01 Jul 2026 00:00:00 GMT
// Link: <.../migrate-v1-to-v2>; rel="successor-version" Versioning Strategy Comparison
| Strategy | Visibility | REST Purity | Browser Testable | Routing |
|---|---|---|---|---|
| URL (/v1/) | High | Low (URL ≠ resource) | Yes | Very easy |
| Header (API-Version) | Medium | Medium | Harder | Moderate |
| Content Negotiation | Low | High | Difficult | Moderate |
| Query Param (?v=2) | High | Low | Yes | Easy |
Related Products
on GumroadDevPlaybook Interview Vault
210+ real technical interview questions with model answers, asked at Google, Meta, Amazon, Microsoft, Stripe, and Shopify. Covers Frontend (55), Backend (55), System Design (50), and DSA (50). Available in English and Traditional Chinese.
Buy on Gumroad →TypeScript Quick Reference Toolkit
5 practical reference files for TypeScript developers: cheatsheet, TS vs JS comparison, 20 utility types with real examples, tsconfig best practices template, and 15 common mistakes guide. Print it. Keep it on your desk.
Buy on Gumroad →Developer Interview Vault
210 technical interview questions with model answers, key points, and follow-up questions. Covers Frontend, Backend, System Design, and DSA — bilingual English + Traditional Chinese (ZH-TW). PWYW from $29.
Buy on Gumroad →You Might Also Need
OpenAPI Validator
Paste your OpenAPI 3.x spec (YAML or JSON) to validate structure, find missing required fields, detect duplicate paths, check for missing operationIds, and get errors and warnings with path references.
Open free tool →
API Response Mock Generator
Generate realistic mock API responses with custom status codes, headers, and JSON body. Quick templates for 200, 401, 404, 429, 500 and more. Runs in your browser.
Open free tool →
API Response Time Tester
Test API endpoint response times directly from your browser. Run multiple requests, see average/min/max times, status codes, and response sizes. GET, POST, PUT, DELETE support. Free.
Open free tool →
More Free Tools
Related Articles
GraphQL vs REST vs gRPC: A 2026 API Design Comparison
A practical guide comparing GraphQL vs REST vs gRPC for modern API development. Covers performance, type safety, tooling, use cases, and a decision framework for 2026.
Complete HTTP Status Codes Reference for REST API Devs
The complete HTTP status codes reference for REST API developers. Every 1xx, 2xx, 3xx, 4xx, and 5xx code explained with real-world API examples and best practices.
REST API Best Practices: Endpoint Naming, Versioning, and Error Handling
Design clean, consistent REST APIs with proper URL naming, HTTP methods, versioning, error formats, pagination, auth, and rate limiting.
Understanding REST API Design Best Practices
A practical guide to designing REST APIs that developers love — covering resource modeling, HTTP semantics, status codes, versioning, and error handling.
The Ultimate API Design Checklist for Production Apps
Ship production-ready APIs with this comprehensive checklist. Covers REST design, authentication, validation, error handling, rate limiting, versioning, documentation, and monitoring.
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- ⚡ AI Code Review, Doc Generator & SQL Builder
- ⚡ All premium templates & early access
- ⚡ Member discounts on Gumroad products
$9/mo after trial · Cancel anytime
Want the full toolkit?
Get DevPlaybook Pro
Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.
MIT licensed · Instant download · No subscription
See what's included → Browse all deals →Frequently Asked Questions
What is the most common API versioning strategy?▼
URL path versioning (/v1/, /v2/) is by far the most widely used approach. It is immediately visible in browser address bars, easy to route at the load balancer or API gateway level, simple to document separately per version, and trivial for developers to understand. GitHub, Stripe, Twitter, and most major public APIs use URL versioning. The main downside is that it violates strict REST principles (the URL should identify a resource, not a contract version).
What is the difference between header versioning and content negotiation?▼
Header versioning uses a custom request header like API-Version: 2 to specify the desired version. Content negotiation uses the standard Accept header with a vendor MIME type: Accept: application/vnd.myapi.v2+json. Content negotiation is more RESTful because it uses standard HTTP mechanics, but it is harder to test in browsers, less visible in logs, and less intuitive for developers. Both keep URLs clean. Header versioning is a practical middle ground between URL versioning and full content negotiation.
How long should I maintain an old API version?▼
Industry practice is 6 to 24 months of parallel support after announcing a version's deprecation. The exact period depends on your user base — public APIs with many third-party integrations need longer windows. Communicate the sunset date clearly in documentation, in the Sunset and Deprecation headers on every response from the old version, and via direct email to registered API users.
What is the Sunset header?▼
The Sunset HTTP response header (RFC 8594) communicates the date after which a deprecated API version or endpoint will no longer be available. Example: Sunset: Sat, 01 Jan 2027 00:00:00 GMT. Combine it with a Link header pointing to migration documentation: Link: <https://devplaybook.cc/migrate>; rel='sunset'. Clients that inspect headers can automatically warn developers when they hit a sunsetted endpoint.
Should I version the entire API or individual endpoints?▼
Versioning the entire API (all endpoints move from /v1 to /v2 together) is simpler to manage but forces consumers to upgrade everything at once. Endpoint-level versioning lets you version only the changed resources, reducing migration scope for consumers. A pragmatic approach is API-level versioning with feature flags or additive changes for minor updates — increment the major API version only for breaking changes that affect multiple endpoints.
What counts as a breaking change that requires a version bump?▼
Breaking changes include: removing a field from a response, renaming a field, changing a field's type (e.g. string to number), changing HTTP status codes for existing operations, removing an endpoint, making an optional field required, and changing authentication mechanisms. Non-breaking (additive) changes that do not require a version bump: adding new optional fields to responses, adding new optional request parameters, adding new endpoints, and adding new enum values (with caution — strict enum parsers may break).