Skip to content

API Versioning — Complete Guide to Managing Change

DodaTech Updated 2026-06-28 4 min read

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

API versioning manages changes to endpoints without breaking existing consumers, using URI paths, query parameters, custom headers, or content negotiation strategies to evolve APIs safely.

What You'll Learn

  • The four main API versioning strategies
  • Pros and cons of URI versioning vs header versioning
  • How to deprecate old versions and migrate consumers

Why It Matters

Without versioning, every API change risks breaking consumers. Versioning allows the API to evolve while v1 clients continue working unchanged until they are ready to migrate.

Real-World Use

Doda Browser's sync API uses URI path versioning (/v1/sync, /v2/sync). When v2 introduced delta sync, v1 clients continued using full sync until their app updated, giving a 6-month overlap window.

flowchart LR
    A["API Versioning"] --> B["URI Path"]
    A --> C["Query Parameter"]
    A --> D["Custom Header"]
    A --> E["Content Negotiation"]
    B --> F["/v1/users"]
    B --> G["/v2/users"]
    D --> H["Accept-Version: v1"]
    D --> I["Accept-Version: v2"]
    style A fill:#dbeafe,stroke:#2563eb

Code Examples

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/v1/users')
def v1_get_users():
    return jsonify({'users': [{'id': 1, 'name': 'Alice'}]})

@app.route('/v2/users')
def v2_get_users():
    return jsonify({
        'data': [{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}],
        'meta': {'page': 1, 'total': 1}
    })

app.run(port=5000)

Expected output: /v1/users returns flat user array; /v2/users returns wrapped response with metadata.

const express = require('express');
const app = express();

function versionRouter(req, res, next) {
  const version = req.headers['accept-version'] || '1';
  req.apiVersion = version;
  next();
}

app.get('/api/users', versionRouter, (req, res) => {
  if (req.apiVersion === '1') {
    res.json({ users: [{ id: 1, name: 'Alice' }] });
  } else if (req.apiVersion === '2') {
    res.json({ data: [{ id: 1, name: 'Alice', email: 'alice@example.com' }] });
  } else {
    res.status(400).json({ error: 'Unsupported version' });
  }
});

app.listen(3000);

Expected output: Header Accept-Version: v1 returns v1 format; header v2 returns v2 format.

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/users')
def get_users():
    accept = request.headers.get('Accept', 'application/vnd.api.v1+json')
    if 'vnd.api.v1' in accept:
        return jsonify({'users': [{'id': 1, 'name': 'Alice'}]})
    elif 'vnd.api.v2' in accept:
        return jsonify({'data': [{'id': 1, 'name': 'Alice', 'email': 'a@example.com'}]})
    return jsonify({'error': 'Unsupported media type'}), 415

Expected output: Request with Accept: application/vnd.api.v2+json receives v2 response format.

Common Mistakes

1. Not Versioning from the Start

The first breaking change forces an emergency versioning decision. Version from day one even for internal APIs.

2. Supporting Too Many Versions

Maintaining v1 through v5 simultaneously quadruples testing effort. Support at most two active versions.

3. Breaking Changes in Patch Versions

Breaking changes require a major version bump. Never break backward compatibility in a minor or patch release.

4. No Deprecation Headers

Consumers need to know when a version will be retired. Use Sunset and Deprecation HTTP headers.

5. Mixing Versioning Strategies

Some endpoints use URI versioning, others use headers. Pick one Strategy and apply it consistently.

Practice Questions

  1. What are four common API versioning strategies?
  2. Why is URI path versioning the most popular approach?
  3. What is the problem with supporting more than two API versions?
  4. How do Sunset headers help consumers plan Migration?
  5. Why should you version an API that has only one consumer?

Answers:

  1. URI path, query parameter, custom header, and content negotiation.
  2. It is explicit in the URL, cache-friendly, and easy to test and document.
  3. Each additional version multiplies testing, maintenance, and documentation effort.
  4. Sunset headers tell consumers exactly when a version will stop working.
  5. Even a single consumer may need time to migrate when requirements change.

Challenge: Design a version migration plan for an API with 10 external consumers moving from v1 to v2. Define the deprecation window, communication timeline, and Sunset header implementation.

FAQ

What is the difference between URI and header versioning?

: URI versioning puts the version in the URL path (/v1/users); header versioning uses a custom header (Accept-Version: v1).

Should you version an internal-only API?

: Yes, internal consumers also need stability, and internal APIs evolve.

Can you use query parameters for versioning?

: Yes (?version=1), but it pollutes URLs and is less cache-friendly than path-based versioning.

What does the Sunset HTTP header do?

: It announces when a resource or API version will be removed (RFC 8594).

How long should a deprecation period last?

: 6-12 months for external APIs, 3-6 months for internal APIs.

Mini Project

Create a versioned API with two versions of a product catalog endpoint. v1 returns flat product list; v2 supports pagination, filtering, and nested categories. Include deprecation headers in v1 responses.

What's Next

Explore API documentation strategies for documenting multiple API versions, or read about API lifecycle management for end-to-end version governance.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro