Skip to content

API Versioning in REST API Design — Complete Guide

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about API Versioning in REST Api Design. We cover key concepts, practical examples, and best practices to help you master this topic.

API versioning allows REST APIs to evolve without breaking existing clients, with URI path versioning being the most common approach followed by header-based and query parameter strategies.

flowchart TD
  A[API Versioning] --> B[URI Path]
  A --> C[Header Based]
  A --> D[Query Parameter]
  A --> E[Content Negotiation]
  B --> B1[/api/v1/users]
  B --> B2[/api/v2/users]
  C --> C1[Accept: application/vnd.api.v1+json]
  D --> D1[?version=1]
  style A fill:#e1f5fe
  style B fill:#c8e6c9

URI path versioning puts the version in the URL: /api/v1/users and /api/v2/users. It is the most visible and easiest to implement. Header versioning uses a custom header like Accept: application/vnd.api.v1+json or X-API-Version: 1. Query parameter versioning uses ?version=1.

Think of versioning like software releases. Version 1 is like Windows 10. Version 2 is like Windows 11. Both coexist but have different features and behaviors. Clients choose which version to use and stick with it until they are ready to upgrade.

Example: URI Path Versioning

import requests

# v1 returns basic user info
v1_response = requests.get("https://api.example.com/api/v1/users/42")
v1_user = v1_response.json()
print(f"v1: {list(v1_user.keys())}")

# v2 returns enhanced user info
v2_response = requests.get("https://api.example.com/api/v2/users/42")
v2_user = v2_response.json()
print(f"v2: {list(v2_user.keys())}")

Expected output:

v1: ['id', 'name', 'email']
v2: ['id', 'name', 'email', 'profile_picture', 'phone', 'preferences']

Example: Header Versioning

import requests

# Version via Accept header
headers_v1 = {"Accept": "application/vnd.api.v1+json"}
headers_v2 = {"Accept": "application/vnd.api.v2+json"}

response_v1 = requests.get(
    "https://api.example.com/users/42",
    headers=headers_v1
)
print(f"v1 URL: {response_v1.url}")
print(f"v1 format: {response_v1.json()['format']}")

response_v2 = requests.get(
    "https://api.example.com/users/42",
    headers=headers_v2
)
print(f"v2 format: {response_v2.json()['format']}")

Expected output:

v1 URL: https://api.example.com/users/42
v1 format: basic
v2 format: extended

Example: Multiple Version Support

import requests

def call_api(endpoint, version=1):
    base = f"https://api.example.com/api/v{version}"
    response = requests.get(f"{base}{endpoint}")
    return response

# Transparent to client
for v in [1, 2, 3]:
    data = call_api("/users/42", v).json()
    print(f"v{v}: fields={len(data)}")

Expected output:

v1: fields=3
v2: fields=5
v3: fields=8

Common Mistakes

  1. Not versioning at all — Without versioning, changing a response field breaks every client. Always version from day one.
  2. Removing old versions too quickly — Clients need time to migrate. Support each version for at least 6-12 months with deprecation warnings.
  3. Versioning the entire API, not individual resources — Different resources evolve at different speeds. Consider per-resource versioning for large APIs.
  4. Breaking changes in minor versions — Use semantic versioning for APIs. Breaking changes require a major version bump.
  5. Inconsistent version across endpoints — Mixing v1 and v2 endpoints in the same base path confuses clients and is hard to maintain.

Practice Questions

  1. What are the three most common API versioning strategies?
  2. Why is URI path versioning the most popular approach?
  3. What is the advantage of header-based versioning?
  4. How long should you support deprecated API versions?
  5. Challenge: Implement a version-negotiating API client that first tries v2, falls back to v1, and logs the version used. The client should work transparently with either version.

FAQ

Should I use /api/v1 or /v1 in the URI?

Either is fine. /api/v1 is more explicit about separating API routes from other routes. Be consistent across your entire API.

How do I deprecate an API version?

Add a Deprecation and Sunset header to responses. The Deprecation header warns clients, and the Sunset header indicates when the version will be removed.

Can I version by subdomain (v1.api.example.com)?

Yes, subdomain versioning works well for microservices. Each version can be deployed independently on different infrastructure.

What qualifies as a breaking change?

Removing fields, changing field types, changing endpoint URLs, modifying authentication requirements, and altering error response formats are all breaking changes.

Should I increment the version for backward-compatible additions?

No, only increment for breaking changes. Adding new fields to responses is backward-compatible if clients ignore unknown fields.

Mini Project

Build a Python API versioning middleware that routes requests to different handler versions based on the URI prefix. Include deprecation headers for old versions and a Migration guide in responses. Test with v1, v2, and a deprecated v0 endpoint.

What's Next

Now learn about error responses in REST API Design.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro