API Design & Best Practices

API Versioning Strategies: URI, Header, and Query

A comparison of URI path, header, and query-parameter versioning for REST APIs, with semantic versioning principles and a deprecation strategy.

Why Version APIs?

APIs change. New fields are added, old ones are renamed, endpoints are restructured. Without versioning, every breaking change breaks every client. API versioning lets you evolve the API while giving existing clients time to migrate.

A *breaking change* is any change that causes a previously valid client request to fail or return a different result. Examples:

  • Removing or renaming a field
  • Changing a field's type (stringinteger)
  • Removing an endpoint
  • Changing authentication requirements

*Non-breaking* (additive) changes — new optional fields, new endpoints — generally do not require a version bump.

URL Path Versioning (`/v1/`)

The most common and most visible approach:

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

Pros: immediately obvious in logs, browser history, and documentation. Easy to route at the load balancer level. Cache-friendly — v1 and v2 are distinct URLs.

Cons: technically violates REST's principle that a URL identifies a resource, not a version of a resource. Proliferates endpoints and documentation pages.

This is the pragmatic choice for public APIs. GitHub, Stripe, Twilio, and most major API providers use URL versioning.

Header Versioning (`Accept`)

Encode the version in the Accept header using a custom media type:

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

Or use a custom API-Version header:

GET /users HTTP/1.1
API-Version: 2024-01-01

Pros: keeps URLs clean; a single URL represents the resource regardless of version. Aligns with REST's content negotiation model.

Cons: invisible to browsers and most HTTP clients. Harder to test without dedicated tooling. Breaks caching unless Vary: Accept is set correctly.

Query Parameter Versioning

Pass the version as a query string parameter:

GET /users?version=2
GET /users?api-version=2024-01-01

Pros: easy to test in a browser. Works with any HTTP client without special configuration.

Cons: pollutes URLs, can conflict with pagination/filter parameters, and complicates caching.

Semantic Versioning for APIs

Many teams adapt SemVer (MAJOR.MINOR.PATCH) for APIs:

Version ComponentMeaning
`MAJOR`Breaking changes — increment the public version
`MINOR`New features, backward-compatible
`PATCH`Bug fixes, no API surface change

In practice, only MAJOR versions are exposed to clients (/v1/, /v2/). Minor and patch versions are internal.

An alternative is date-based versioning (used by Stripe and Cloudflare): 2024-01-15. Each date snapshot is a stable contract. Clients pin to a date and receive no breaking changes until they opt in to a newer date.

Deprecation Strategy

A version without a sunset plan becomes technical debt. Follow these steps:

  • Announce deprecation — add a Deprecation header and a Sunset header to all responses on the old version:
Deprecation: true
Sunset: Sat, 01 Jun 2025 00:00:00 GMT
Link: <https://api.example.com/v2/users>; rel="successor-version"
  • Communicate via email and changelog at least 6–12 months in advance for external APIs.
  • Log usage — track which clients are still using the deprecated version so you can reach out directly.
  • Return 410 Gone after sunset, not 404 — 410 signals intentional removal.

Recommendation

For most teams: use URL path versioning (/v1/). It is the most understood pattern, easiest to document, and simplest to route. Combine with Deprecation + Sunset headers for graceful migration.

Related Protocols

Related Glossary Terms

More in API Design & Best Practices