---
# === IDENTITY ===
id: software/patterns/api-versioning/2026
canonical_question: "What are the best API versioning strategies?"
aliases:
  - "API versioning best practices"
  - "REST API versioning strategies"
  - "how to version an API"
  - "URL path versioning vs header versioning"
  - "API version management"
  - "API deprecation strategy"
  - "breaking changes API versioning"
  - "API backward compatibility"
entity_type: software_reference
domain: software > patterns > api_versioning
region: global
jurisdiction: global
temporal_scope: 2015-2026

# === VERIFICATION ===
last_verified: 2026-02-24
confidence: 0.90
version: 1.0
first_published: 2026-02-24

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: null
  next_review: 2026-08-23
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "Never introduce breaking changes to a published API version without incrementing the version identifier"
  - "Maintain at most 2-3 active API versions simultaneously — more creates unsustainable maintenance burden"
  - "Public APIs must use explicit versioning from day one — retrofitting versions onto an unversioned API forces all clients to migrate at once"
  - "Sunset headers (RFC 8594) must accompany any deprecation — silent removal breaks client trust and integrations"
  - "GraphQL APIs should use schema evolution (additive changes + deprecation directives), not URL versioning"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Building a GraphQL API and need schema evolution patterns"
    use_instead: "software/patterns/graphql-schema-patterns/2026"
  - condition: "Need to design API rate limiting, not versioning"
    use_instead: "software/patterns/api-rate-limiting/2026"
  - condition: "Building an internal microservice with a single consumer"
    use_instead: "Consider contract testing (Pact) instead of formal versioning"

# === AGENT HINTS ===
inputs_needed:
  - key: "api_type"
    question: "What type of API are you building?"
    type: choice
    options: ["REST", "GraphQL", "gRPC"]
  - key: "breaking_change_frequency"
    question: "How often do you expect breaking changes?"
    type: choice
    options: ["rarely (yearly)", "occasionally (quarterly)", "frequently (monthly)"]
  - key: "client_control"
    question: "Do you control all API consumers, or is this a public API?"
    type: choice
    options: ["internal (all consumers controlled)", "public (third-party consumers)", "partner (limited known consumers)"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/patterns/api-versioning/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-24)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/patterns/rest-pagination/2026"
      label: "REST Pagination Patterns"
    - id: "software/patterns/api-rate-limiting/2026"
      label: "API Rate Limiting"
    - id: "software/system-design/api-gateway/2026"
      label: "API Gateway Design"
  solves:
    - id: "software/patterns/webhook-implementation/2026"
      label: "Webhook Implementation"
  often_confused_with:
    - id: "software/patterns/graphql-schema-patterns/2026"
      label: "GraphQL Schema Patterns — uses schema evolution, not URL versioning"
    - id: "software/patterns/semantic-versioning/2026"
      label: "Semantic Versioning (SemVer) — library versioning, not API endpoint versioning"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "What is API versioning? Benefits, types & best practices"
    author: Postman
    url: https://www.postman.com/api-platform/api-versioning/
    type: industry_report
    published: 2025-06-15
    reliability: high
  - id: src2
    title: "API Versioning Best Practices: How to Manage Changes Effectively"
    author: Gravitee
    url: https://www.gravitee.io/blog/api-versioning-best-practices
    type: technical_blog
    published: 2025-03-20
    reliability: moderate_high
  - id: src3
    title: "API versioning best practices"
    author: Redocly
    url: https://redocly.com/blog/api-versioning-best-practices
    type: technical_blog
    published: 2025-01-10
    reliability: moderate_high
  - id: src4
    title: "Versioning a REST API"
    author: Baeldung
    url: https://www.baeldung.com/rest-versioning
    type: technical_blog
    published: 2024-09-15
    reliability: high
  - id: src5
    title: "How to Smartly Sunset and Deprecate APIs"
    author: Nordic APIs
    url: https://nordicapis.com/how-to-smartly-sunset-and-deprecate-apis/
    type: technical_blog
    published: 2024-11-20
    reliability: moderate_high
  - id: src6
    title: "REST API Versioning: by URL vs. Content Negotiation"
    author: Vincenzo Racca
    url: https://www.vincenzoracca.com/en/blog/framework/spring/versioning-api/
    type: technical_blog
    published: 2025-02-10
    reliability: moderate
  - id: src7
    title: "How to Implement API Versioning Strategies in Node.js (2026 Guide)"
    author: DEV Community
    url: https://dev.to/1xapi/how-to-implement-api-versioning-strategies-in-nodejs-2026-guide-58cc
    type: community_resource
    published: 2026-01-15
    reliability: moderate
---

# API Versioning Strategies: URL Path, Header, Query Parameter, and Content Negotiation

## TL;DR

- **Bottom line**: Use URL path versioning (`/api/v1/resource`) for public APIs due to discoverability and caching; use header versioning for internal APIs where you control all clients.
- **Key tool/command**: `/api/v{N}/resource` (URL path) or `Accept: application/vnd.api.v1+json` (content negotiation)
- **Watch out for**: Versioning too eagerly — most changes can be additive (new fields, new endpoints) without a version bump.
- **Works with**: Any HTTP-based API framework (Express, FastAPI, Go net/http, Spring Boot, ASP.NET Core). gRPC uses protobuf package versioning instead.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Never introduce breaking changes to a published API version without incrementing the version identifier
- Maintain at most 2-3 active API versions simultaneously — more creates unsustainable maintenance burden
- Public APIs must use explicit versioning from day one — retrofitting versions onto an unversioned API forces all clients to migrate at once
- Sunset headers (RFC 8594) must accompany any deprecation — silent removal breaks client trust and integrations
- GraphQL APIs should use schema evolution (additive changes + deprecation directives), not URL versioning

## Quick Reference

| Strategy | Format | URL Impact | Client Adoption | Caching | Routing Complexity | Best For |
|---|---|---|---|---|---|---|
| URL path | `/api/v1/users` | Version in URL | Trivial — visible in URL | Excellent — unique cache keys | Low — standard routing | Public APIs, most common |
| Query parameter | `/api/users?v=1` | Param appended | Easy — add query param | Good — varies by CDN config | Low | Optional versioning, testing |
| Accept header | `Accept: application/vnd.api.v1+json` | None — clean URLs | Moderate — requires header knowledge | Poor — Vary header needed | Medium | Internal APIs, RESTful purists |
| Custom header | `X-API-Version: 1` | None — clean URLs | Moderate — custom header required | Poor — Vary header needed | Medium | Internal APIs, microservices |
| Content negotiation | `Accept: application/vnd.company.resource.v1+json` | None — clean URLs | Hard — complex media types | Poor — complex Vary | High | Enterprise APIs with strict contracts |
| No versioning (evolution) | `/api/users` (additive only) | None | Trivial | Excellent | None | Internal APIs, rarely-changing APIs |
| Date-based | `/api/2025-01-15/users` | Date in URL | Moderate — must track dates | Excellent | Medium | APIs with frequent releases (Stripe model) |
| Semantic (major only) | `/api/v2/users` | Major version in URL | Trivial | Excellent | Low | Public APIs with infrequent breaking changes |

## Decision Tree

```
START
├── API type is GraphQL?
│   ├── YES → Use schema evolution with @deprecated directives. Do NOT use URL versioning.
│   └── NO ↓
├── API type is gRPC?
│   ├── YES → Use protobuf package versioning (package api.v1;). Route at service level.
│   └── NO ↓
├── Is this a public API with third-party consumers?
│   ├── YES → URL path versioning (/api/v1/...). Maximum discoverability.
│   │   └── Breaking changes very frequent? → Consider date-based versioning (Stripe model)
│   └── NO ↓
├── Do you control all consumers (internal/microservices)?
│   ├── YES → Header versioning (Accept or custom header) keeps URLs clean.
│   │   └── Want zero versioning overhead? → Additive-only evolution + contract tests
│   └── NO ↓
├── Is this a partner API with limited known consumers?
│   ├── YES → URL path versioning + direct migration support per partner
│   └── NO ↓
└── DEFAULT → URL path versioning (/api/v1/...) — safest, most widely understood
```

## Step-by-Step Guide

### 1. Choose your versioning strategy

Evaluate your API's audience, breaking change frequency, and infrastructure. Public APIs benefit from URL path versioning for maximum discoverability. Internal APIs can use header versioning for cleaner URLs. [src1]

```
Decision factors:
- Public API → URL path versioning (/api/v1/)
- Internal API, all clients controlled → Header versioning or evolution
- Frequent breaking changes → Date-based versioning (Stripe model)
- GraphQL → Schema evolution only
```

**Verify**: Document your choice in your API design spec. Ensure the team agrees before implementation.

### 2. Implement URL path versioning with route prefixes

Structure your application with version-prefixed route groups. Each version can share common logic through service layers while keeping route handlers separate. [src7]

```javascript
// Express.js — version-prefixed routers
const express = require('express');
const app = express();

const v1Router = require('./routes/v1');
const v2Router = require('./routes/v2');

app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

// Each version router handles its own endpoints
// routes/v1/users.js exports: GET /users, POST /users
// routes/v2/users.js exports: GET /users (new response shape), POST /users
```

**Verify**: `curl http://localhost:3000/api/v1/users` and `curl http://localhost:3000/api/v2/users` return version-appropriate responses.

### 3. Add version middleware for header-based versioning

When using header versioning, create middleware that extracts the version from headers and routes to the correct handler. [src4]

```javascript
// Express.js — header-based version routing middleware
function versionRouter(req, res, next) {
  const version = req.headers['accept-version']
    || req.headers['x-api-version']
    || '1'; // default to v1

  req.apiVersion = parseInt(version, 10);
  next();
}

app.use(versionRouter);

app.get('/api/users', (req, res) => {
  if (req.apiVersion === 2) {
    return res.json({ data: users, meta: { total: users.length } });
  }
  // v1 — original flat response
  return res.json(users);
});
```

**Verify**: `curl -H "Accept-Version: 2" http://localhost:3000/api/users` returns v2 format; omitting the header returns v1.

### 4. Implement deprecation with Sunset headers

When deprecating an API version, add Sunset and Deprecation headers to every response. This gives clients programmatic notice of the retirement date. [src5]

```javascript
// Express.js — deprecation middleware for v1
function deprecateV1(req, res, next) {
  res.set('Deprecation', 'true');
  res.set('Sunset', 'Sat, 31 Dec 2026 23:59:59 GMT');
  res.set('Link', '</api/v2/docs>; rel="successor-version"');
  next();
}

app.use('/api/v1', deprecateV1);
```

**Verify**: `curl -I http://localhost:3000/api/v1/users` includes `Deprecation: true` and `Sunset:` headers.

### 5. Set up version lifecycle management

Establish a deprecation timeline: announce 6 months before retirement, provide active migration support for 12 months, remove after 18-24 months. Track version usage to identify migration stragglers. [src2]

```
Timeline template:
  T+0:    Release v2, v1 still fully supported
  T+6mo:  Announce v1 deprecation, add Sunset headers
  T+12mo: v1 returns Warning header, rate-limit v1 calls
  T+18mo: v1 returns 410 Gone for new integrations
  T+24mo: v1 fully removed
```

**Verify**: Monitor version usage in your API analytics dashboard. v1 traffic should trend toward zero before removal.

## Code Examples

### Node.js/Express: URL Path Versioning with Shared Services

```javascript
// Input:  HTTP requests to /api/v1/users or /api/v2/users
// Output: Version-appropriate JSON responses

// routes/v1/users.js
const router = require('express').Router();
const UserService = require('../../services/userService');

router.get('/users', async (req, res) => {
  const users = await UserService.list(req.query);
  // v1: flat array response
  res.json(users);
});

module.exports = router;

// routes/v2/users.js
const router = require('express').Router();
const UserService = require('../../services/userService');

router.get('/users', async (req, res) => {
  const users = await UserService.list(req.query);
  // v2: envelope response with pagination metadata
  res.json({
    data: users,
    meta: { total: users.length, page: 1 }
  });
});

module.exports = router;
```

### Python/FastAPI: URL Path Versioning with APIRouter

> Full script: [python-fastapi-url-path-versioning-with-apirouter.py](scripts/python-fastapi-url-path-versioning-with-apirouter.py) (26 lines)

```python
# Input:  HTTP requests to /api/v1/users or /api/v2/users
# Output: Version-appropriate JSON responses
from fastapi import FastAPI, APIRouter
app = FastAPI()
v1 = APIRouter(prefix="/api/v1", tags=["v1"])
# ... (see full script)
```

### Go: URL Path Versioning with http.ServeMux

> Full script: [go-url-path-versioning-with-http-servemux.go](scripts/go-url-path-versioning-with-http-servemux.go) (31 lines)

```go
// Input:  HTTP requests to /api/v1/users or /api/v2/users
// Output: Version-appropriate JSON responses
package main
import (
    "encoding/json"
# ... (see full script)
```

## Anti-Patterns

### Wrong: Breaking changes without version bump

```javascript
// BAD — changing response shape in existing version
// v1 originally returned: [{ id: 1, name: "Alice" }]
// Now silently returns:
app.get('/api/v1/users', (req, res) => {
  res.json({ data: [{ id: 1, full_name: "Alice" }], meta: {} });
  // Renamed "name" to "full_name" and wrapped in envelope
  // Every existing client just broke
});
```

### Correct: New version for breaking changes

```javascript
// GOOD — breaking change goes in new version
app.get('/api/v1/users', (req, res) => {
  res.json([{ id: 1, name: "Alice" }]); // unchanged
});

app.get('/api/v2/users', (req, res) => {
  res.json({ data: [{ id: 1, full_name: "Alice" }], meta: {} });
  // New shape in new version
});
```

### Wrong: Copying entire codebase per version

```
// BAD — duplicating all code for each version
src/
  v1/
    controllers/    # full copy
    models/         # full copy
    services/       # full copy
  v2/
    controllers/    # full copy with one change
    models/         # full copy
    services/       # full copy
```

### Correct: Shared services with version-specific route handlers

```
// GOOD — share business logic, version only the API layer
src/
  services/         # shared business logic
  routes/
    v1/             # thin route handlers calling services
    v2/             # thin route handlers calling services
  middleware/
    deprecation.js  # version lifecycle headers
```

### Wrong: Versioning too eagerly

```javascript
// BAD — creating v3 just to add a new optional field
// v1: { id, name }
// v2: { id, name, email }       <-- could have been additive
// v3: { id, name, email, phone } <-- could have been additive
// Now you maintain 3 versions for no reason
```

### Correct: Additive changes without version bump

```javascript
// GOOD — adding optional fields is NOT a breaking change
// v1 originally: { id, name }
// v1 now:        { id, name, email, phone }
// Existing clients ignore new fields — no break
app.get('/api/v1/users', (req, res) => {
  res.json([{ id: 1, name: "Alice", email: "a@b.com", phone: null }]);
  // Old clients safely ignore email and phone
});
```

## Common Pitfalls

- **No default version**: Clients that omit version info get undefined behavior. Fix: always default to the latest stable version or return `400 Bad Request` with a version hint. [src1]
- **Removing fields from existing version**: Removing a response field is a breaking change even if you think nobody uses it. Fix: add new fields freely, never remove — create a new version to change the shape. [src3]
- **Forgetting CORS preflight for versioned headers**: Custom `X-API-Version` headers trigger CORS preflight. Fix: include the header in `Access-Control-Allow-Headers` and handle OPTIONS. [src4]
- **No deprecation communication**: Retiring a version without notice. Fix: add `Deprecation: true`, `Sunset: <date>`, and `Link: <successor-version>` headers at least 6 months before removal. [src5]
- **Inconsistent versioning across endpoints**: Some endpoints at v2, others still at v1 only. Fix: version the entire API uniformly — all endpoints advance together. [src2]
- **Over-versioning**: Creating a new version for every change. Fix: distinguish breaking changes (require version bump) from additive changes (new fields, new endpoints — no bump needed). [src3]
- **Ignoring version usage metrics**: Supporting dead versions wastes resources. Fix: track per-version request counts and sunset versions with <1% traffic. [src2]

## Diagnostic Commands

```bash
# Check which API version a response is using
curl -s -D - http://localhost:3000/api/v1/users | head -20

# Test header-based versioning
curl -s -H "Accept-Version: 2" http://localhost:3000/api/users | jq .

# Check for deprecation headers
curl -s -I http://localhost:3000/api/v1/users | grep -i -E "deprecation|sunset|link"

# List all versioned routes in Express app (if using express-list-endpoints)
npx express-list-endpoints ./app.js

# Count API version distribution in access logs
awk -F'"' '{print $2}' access.log | grep -oP '/api/v\d+' | sort | uniq -c | sort -rn
```

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Public API with third-party consumers | Internal single-consumer microservice | Contract testing (Pact) + additive evolution |
| Breaking response shape changes needed | Adding new optional fields to responses | Additive changes — no version needed |
| Multiple client generations must coexist | GraphQL API | Schema evolution with @deprecated directives |
| Regulatory or compliance requires stability | gRPC service-to-service | Protobuf package versioning |
| API monetization with SLA commitments | Prototype or MVP stage | Ship unversioned, add versioning before public launch |
| Long-lived webhooks with fixed payloads | Rapidly iterating internal tool | Feature flags or gradual rollout |

## Important Caveats

- URL path versioning is the most popular strategy (used by GitHub, Google, Stripe, Twilio) but is technically not RESTful — REST purists argue the version is not part of the resource identity
- Date-based versioning (Stripe's model: `/v1` with `Stripe-Version: 2025-01-15`) requires sophisticated API gateway infrastructure to maintain many date-pinned behaviors simultaneously
- Content negotiation (`Accept: application/vnd.api.v1+json`) is the most RESTful approach but has the worst developer experience — hard to test in browsers, difficult to document, breaks simple curl commands
- API gateways (Kong, Apigee, AWS API Gateway) can handle version routing externally, decoupling versioning from application code — but adds infrastructure complexity
- Mobile clients cannot be forced to upgrade, so mobile-facing APIs may need to support old versions for years longer than web-facing APIs

## Related Units
<!-- Generated from related_kos frontmatter -->

- [REST Pagination Patterns](/software/patterns/rest-pagination/2026)
- [API Rate Limiting](/software/patterns/api-rate-limiting/2026)
- [API Gateway Design](/software/system-design/api-gateway/2026)
- [Webhook Implementation](/software/patterns/webhook-implementation/2026)
- [GraphQL Schema Patterns](/software/patterns/graphql-schema-patterns/2026)
