What Is the Best API Versioning Strategy: URL, Header, or Content Negotiation?

API versioning strategies each have tradeoffs. Learn the pros and cons of URL versioning, header versioning, and content negotiation, and how Modern PetstoreAPI implements versioning correctly.

Ashley Innocent

Ashley Innocent

13 March 2026

What Is the Best API Versioning Strategy: URL, Header, or Content Negotiation?

TL;DR

URL versioning (/v1/pets) is the most practical API versioning strategy for most teams. It’s visible, cacheable, and easy to test. Header versioning and content negotiation are more “pure” REST but add complexity. Modern PetstoreAPI uses URL versioning with semantic versioning and clear deprecation policies.

Introduction

Your API needs a breaking change. You’re changing the response format for /pets from a bare array to a wrapped object with pagination metadata. Existing clients will break. What do you do?

You need API versioning. But which strategy? URL versioning (/v1/pets vs /v2/pets)? Header versioning (Accept: application/vnd.petstore.v1+json)? Content negotiation? Each approach has passionate advocates and strong opinions.

The answer: URL versioning wins for most teams. It’s pragmatic, visible, and works with all HTTP tooling. Header versioning and content negotiation are theoretically cleaner but add complexity that most teams don’t need.

Modern PetstoreAPI uses URL versioning with semantic versioning and clear deprecation policies. The current version is v1, with v2 planned for future breaking changes.

💡
If you’re building or testing REST APIs, Apidog helps you test multiple API versions, validate version-specific behavior, and ensure backward compatibility. You can maintain separate specs for each version and run tests against all versions simultaneously.
button

In this guide, you’ll learn the three main versioning strategies, their tradeoffs, and how to implement versioning correctly using Modern PetstoreAPI as a reference.

Why APIs Need Versioning

APIs evolve. You add features, fix bugs, and improve designs. Sometimes these changes break existing clients.

Breaking Changes

Changes that break existing clients:

1. Removing fields:

// v1
{"id": "123", "name": "Fluffy", "age": 3}

// v2 (breaking: removed age)
{"id": "123", "name": "Fluffy"}

2. Changing field types:

// v1
{"price": "19.99"}

// v2 (breaking: string to number)
{"price": 19.99}

3. Changing response structure:

// v1 (bare array)
[{"id": "123"}]

// v2 (breaking: wrapped object)
{"data": [{"id": "123"}], "pagination": {...}}

4. Changing URL structure:

// v1
GET /pet/123

// v2 (breaking: plural)
GET /pets/123

5. Changing authentication:

// v1: API key in query
GET /pets?api_key=xxx

// v2 (breaking: Bearer token)
GET /pets
Authorization: Bearer xxx

Non-Breaking Changes

Changes that don’t break clients:

The Versioning Decision

When you need a breaking change, you have two options:

1. Force all clients to upgrade - Simple but breaks existing integrations

2. Support multiple versions - More work but maintains backward compatibility

Most public APIs choose option 2. Versioning lets you evolve the API while giving clients time to migrate.

URL Versioning

URL versioning puts the version number in the URL path.

How It Works

GET /v1/pets
GET /v2/pets

The version is part of the resource identifier. Different versions are different resources.

Pros

1. Visible and explicit

The version is in the URL. You can see it in logs, browser history, and documentation. No hidden headers to remember.

2. Easy to test

curl https://petstoreapi.com/v1/pets
curl https://petstoreapi.com/v2/pets

You can test both versions with simple HTTP requests.

3. Works with all HTTP tooling

Browsers, caches, proxies, and load balancers see different URLs. They can route, cache, and log each version independently.

4. Simple for clients

Clients just change the URL. No custom headers or content negotiation logic.

5. Easy to deprecate

You can remove /v1 endpoints without affecting /v2.

Cons

1. Not “pure” REST

REST purists argue that /v1/pets/123 and /v2/pets/123 are the same resource, so they should have the same URL. The version should be in headers or content negotiation.

2. URL pollution

Your API has multiple URL spaces: /v1/*, /v2/*, etc.

3. Harder to version individual resources

If you want to version just one endpoint, you need to version the entire API or create inconsistency.

Implementation

Major version in URL:

/v1/pets
/v2/pets

Don’t include minor versions:

❌ /v1.2/pets  (too granular)
✅ /v1/pets    (major version only)

Use semantic versioning internally:

Modern PetstoreAPI uses URL versioning with /v1 as the current version.

Header Versioning

Header versioning puts the version in a custom HTTP header.

How It Works

GET /pets
API-Version: 1

GET /pets
API-Version: 2

The URL stays the same. The header specifies the version.

Pros

1. Clean URLs

/pets is the same for all versions. No /v1 or /v2 prefix.

2. More “RESTful”

The resource identifier (/pets/123) doesn’t change. The representation changes based on the header.

3. Granular versioning

You can version individual resources:

GET /pets
API-Version: 2

GET /orders
API-Version: 1

Cons

1. Invisible

The version isn’t in the URL. You can’t see it in logs or browser history without checking headers.

2. Harder to test

curl -H "API-Version: 1" https://petstoreapi.com/pets
curl -H "API-Version: 2" https://petstoreapi.com/pets

You must remember to include the header.

3. Caching complexity

Caches must consider the API-Version header. You need Vary: API-Version in responses.

4. Client complexity

Clients need custom header logic. Not all HTTP clients make this easy.

5. Default version ambiguity

What happens if the client doesn’t send the header? You need a default, which creates implicit behavior.

Implementation

Custom header:

API-Version: 1

Or use Accept header:

Accept: application/vnd.petstore.v1+json

Include Vary header:

Vary: API-Version

This tells caches to consider the header when caching.

Content Negotiation

Content negotiation uses the Accept header with custom media types.

How It Works

GET /pets
Accept: application/vnd.petstore.v1+json

GET /pets
Accept: application/vnd.petstore.v2+json

The version is part of the media type.

Pros

1. Most “RESTful”

This is how REST was designed. Different representations of the same resource.

2. Follows HTTP standards

Uses standard HTTP content negotiation.

3. Supports multiple formats

You can version and format simultaneously:

Accept: application/vnd.petstore.v1+json
Accept: application/vnd.petstore.v1+xml

Cons

1. Complex

Clients must understand media types and content negotiation.

2. Harder to test

curl -H "Accept: application/vnd.petstore.v1+json" https://petstoreapi.com/pets

3. Poor tooling support

Many HTTP clients and tools don’t handle custom media types well.

4. Caching complexity

Caches must consider the Accept header. You need Vary: Accept.

5. Overkill for most APIs

Most APIs don’t need this level of sophistication.

Implementation

Vendor-specific media type:

Accept: application/vnd.petstore.v1+json

Response:

Content-Type: application/vnd.petstore.v1+json
Vary: Accept

How Modern PetstoreAPI Implements Versioning

Modern PetstoreAPI uses URL versioning with clear policies.

Current Version: v1

https://petstoreapi.com/v1/pets
https://petstoreapi.com/v1/orders
https://petstoreapi.com/v1/users

All endpoints are under /v1.

Version Response Header

Every response includes the API version:

X-API-Version: 1.2.0

This shows the exact version (major.minor.patch) even though the URL only shows major version.

Deprecation Warnings

When a version is deprecated, responses include:

Deprecation: true
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Link: <https://docs.petstoreapi.com/migration/v1-to-v2>; rel="deprecation"

Version Discovery

The root endpoint lists available versions:

GET https://petstoreapi.com/

{
  "versions": [
    {
      "version": "v1",
      "status": "current",
      "docsUrl": "https://docs.petstoreapi.com/v1"
    }
  ]
}

Semantic Versioning

Modern PetstoreAPI follows semantic versioning internally:

Only major versions appear in URLs.

Testing API Versions with Apidog

Apidog helps you test multiple API versions.

Import Multiple Versions

Import OpenAPI specs for each version:

petstore-v1.yaml → Environment: v1
petstore-v2.yaml → Environment: v2

Run Tests Against All Versions

Create test suites that run against both versions:

// Test v1
pm.environment.set("baseUrl", "https://petstoreapi.com/v1");
pm.sendRequest(pm.environment.get("baseUrl") + "/pets");

// Test v2
pm.environment.set("baseUrl", "https://petstoreapi.com/v2");
pm.sendRequest(pm.environment.get("baseUrl") + "/pets");

Validate Version-Specific Behavior

Test that v1 and v2 behave differently:

// v1 returns bare array
pm.test("v1 returns array", function() {
    pm.expect(pm.response.json()).to.be.an('array');
});

// v2 returns wrapped object
pm.test("v2 returns wrapped object", function() {
    pm.expect(pm.response.json()).to.have.property('data');
    pm.expect(pm.response.json()).to.have.property('pagination');
});

Check Deprecation Headers

Test that deprecated versions include proper headers:

pm.test("Deprecated version includes headers", function() {
    pm.response.to.have.header("Deprecation");
    pm.response.to.have.header("Sunset");
});

Version Deprecation Strategy

How to deprecate old versions without breaking clients.

1. Announce Deprecation Early

Give clients at least 6-12 months notice:

Deprecation: true
Sunset: Sat, 31 Dec 2026 23:59:59 GMT

2. Provide Migration Guide

Document all breaking changes and how to migrate:

Link: <https://docs.petstoreapi.com/migration/v1-to-v2>; rel="deprecation"

3. Monitor Usage

Track which clients still use deprecated versions:

X-API-Version: 1.2.0
X-Client-ID: abc123

Contact clients directly if needed.

4. Gradual Shutdown

Don’t remove the version immediately:

  1. Month 1-6: Announce deprecation
  2. Month 7-9: Add deprecation headers
  3. Month 10-11: Reduce rate limits for deprecated version
  4. Month 12: Remove deprecated version

5. Keep Documentation

Even after removal, keep documentation for the old version. Clients may need to reference it.

Conclusion

URL versioning is the most practical API versioning strategy for most teams. It’s visible, easy to test, and works with all HTTP tooling. Header versioning and content negotiation are more “pure” REST but add complexity.

Modern PetstoreAPI uses URL versioning with /v1 as the current version, semantic versioning internally, and clear deprecation policies. This approach balances pragmatism with good API design.

Use Apidog to test multiple API versions, validate version-specific behavior, and ensure smooth migrations between versions.

button

FAQ

Should I use URL versioning or header versioning?

Use URL versioning unless you have a specific reason not to. It’s simpler, more visible, and easier to test. Header versioning is more “RESTful” but adds complexity most teams don’t need.

How many versions should I support simultaneously?

Support 2 versions maximum: current and previous. Supporting more creates maintenance burden. Give clients 6-12 months to migrate, then remove old versions.

Should I version from v0 or v1?

Start with v1. v0 implies instability. If your API isn’t stable enough for v1, don’t release it publicly yet.

Do I need to version every endpoint?

No. Only version when you make breaking changes. If you add new endpoints without changing existing ones, you don’t need a new version.

What about minor versions in URLs?

Don’t include minor versions in URLs. Use /v1, not /v1.2. Minor versions are backward compatible, so clients don’t need to change URLs.

How do I handle version-specific bugs?

Fix bugs in all supported versions. If a bug only exists in v1, fix it in v1. Don’t force clients to upgrade to v2 for bug fixes.

Should I use semantic versioning?

Yes, internally. Track major.minor.patch versions, but only expose major versions in URLs. This gives you flexibility for non-breaking changes.

What if I need to version just one endpoint?

With URL versioning, you’d need to version the entire API or create inconsistency. This is a tradeoff. Most teams accept versioning the entire API for simplicity.

Explore more

Socket.IO vs Native WebSocket: Which Should You Use?

Socket.IO vs Native WebSocket: Which Should You Use?

Socket.IO adds features like automatic reconnection and fallbacks, but Native WebSocket is simpler and faster. Learn when to use each and how Modern PetstoreAPI implements both.

13 March 2026

When Should You Use MQTT Instead of HTTP for APIs?

When Should You Use MQTT Instead of HTTP for APIs?

MQTT excels for IoT devices with limited bandwidth and unreliable networks. Learn when MQTT beats HTTP and how Modern PetstoreAPI uses MQTT for pet tracking devices and smart feeders.

13 March 2026

WebSocket vs Server-Sent Events: Which Is Better for Real-Time APIs?

WebSocket vs Server-Sent Events: Which Is Better for Real-Time APIs?

WebSocket and Server-Sent Events both enable real-time communication, but they solve different problems. Learn when to use each and how Modern PetstoreAPI implements both protocols.

13 March 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs