API Versioning Strategies Explained — URL, Header, and Query Param

The moment an API has external consumers, you cannot freely change response shapes without breaking someone's integration. Versioning gives you a safe way to evolve the API. There are three common strategies, and each makes a different trade-off between simplicity, cacheability, and REST purity.

URL Path Versioning

The version is embedded directly in the URL path. It's the most widely used approach because it's immediately visible and simple to route.

GET https://api.example.com/v1/users/42
GET https://api.example.com/v2/users/42

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

app.use('/v1/users', v1Router);
app.use('/v2/users', v2Router);
  • Pros — obvious and self-documenting, trivial to route, cache-friendly since the URL itself changes
  • Cons — some REST purists argue a resource should have one canonical URL regardless of representation

Header Versioning

The version is passed in a custom header or via HTTP content negotiation using the Accept header, keeping the URL itself stable.

GET /users/42 HTTP/1.1
Accept: application/vnd.example.v2+json

// or a custom header
GET /users/42 HTTP/1.1
X-API-Version: 2

// Express middleware
app.use((req, res, next) => {
  req.apiVersion = req.headers['x-api-version'] || '1';
  next();
});
  • Pros — keeps URLs clean and semantically stable, considered more "correct" REST by purists
  • Cons — harder to test quickly in a browser address bar, less visible in server access logs, easy for clients to forget to set the header

Query Parameter Versioning

GET https://api.example.com/users/42?version=2

app.get('/users/:id', (req, res) => {
  const version = req.query.version || '1';
  if (version === '2') return res.json(getUserV2(req.params.id));
  return res.json(getUserV1(req.params.id));
});
  • Pros — easy to test in a browser, optional (can default sensibly), simple to add to an existing API without route restructuring
  • Cons — easy to forget or omit, mixes routing concerns into query string parsing, complicates caching because the same path serves different content based on a param

Practical Recommendation

  1. For public APIs with external consumers, use URL path versioning — it's the clearest contract
  2. Only bump the major version for breaking changes; additive fields don't need a new version
  3. Keep at most two versions live at once (current + previous) and set a clear deprecation timeline
  4. Document deprecations with a Sunset or Deprecation response header so automated tooling can detect it
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 31 Oct 2026 23:59:59 GMT
Link: <https://api.example.com/v2/users/42>; rel="successor-version"

Frequently Asked Questions

What is the most common API versioning strategy?

URL path versioning, such as /v1/users and /v2/users, is the most common strategy because it is simple, visible in logs and browser history, and easy for API consumers to understand at a glance.

Does versioning an API break REST principles?

URL path versioning is debated among REST purists because a resource technically should have one canonical URL. In practice, most production APIs prioritize practical client stability over strict REST purity, and URL versioning remains widely used.

When should I introduce a new API version instead of just changing the response?

Introduce a new version for breaking changes: removing or renaming a field, changing a field's data type, or altering required request parameters. Additive, backward-compatible changes like adding a new optional field do not require a new version.

Debug Errors Faster with AI

Migrating clients between API versions and hitting unexpected errors? Paste them into our free AI Error Explainer for a plain-English cause and fix.

Related articles