KAHIBARO
Discord Login Register

7.19 API Versioning

Why API Versioning Matters

APIs change over time. You fix bugs, add fields, tweak behavior, or even redesign parts of your system. If you change an API that real clients already use, you can easily break them.

API versioning is how you change an API without breaking existing clients. You keep old behavior available under one version, and expose new behavior under another.

Typical situations where you need a new version:

Important rule: Whenever a change can break existing clients, you should treat it as a breaking change and consider a new API version.

Backward compatible changes (like adding a new optional response field) often do not require a new version. More on that later.

Backward Compatibility and Breaking Changes

Before talking about concrete versioning strategies, it is critical to understand the difference between backward compatible changes and breaking changes.

Backward Compatible Changes

A change is backward compatible if old clients can still use the API without any modifications.

Examples of backward compatible changes:

json
  {
    "id": 1,
    "email": "john@example.com",
    "is_active": true    // new field
  }

Old clients that ignore unknown fields keep working.

Old clients that do not send it behave as before.

Because these changes do not break existing clients, you typically do not bump the API version just for them.

Breaking Changes

A breaking change is any change that can make existing clients fail, return unexpected data, or behave incorrectly.

Common breaking changes:

If some clients rely on username, then:

json
  // old
  { "id": 1, "username": "john" }
  // new
  { "id": 1 }

can break them.

json
  // old
  { "price": 10.0 }
  // new
  { "price": "10.00 USD" }

From first_name to given_name.

json
  // old
  { "user": { "id": 1, "email": "john@example.com" } }
  // new
  { "id": 1, "email": "john@example.com" }

Rule of thumb:
If a client written today can break tomorrow without changing its own code, then your change is breaking and should be handled using versioning.

Common API Versioning Strategies

There is no single "correct" way to version APIs. Different companies and frameworks choose different approaches. You should know the most common ones, their pros and cons, and how they look in real requests.

Here are the main strategies:

Strategy typeExample
URI path versioning/api/v1/users
Query parameter/api/users?version=1
Header versioningAccept: application/vnd.app.v1+json
Content negotiationAccept: application/vnd.app.user.v2+json

We will focus on the three most common in REST APIs: URI path, query parameter, and header.

1. URI Path Versioning

The version is part of the URL path.

Examples:

How it works

You introduce new versions by exposing new URL paths. Old paths remain available.

Concrete example:

json
    {
      "id": 1,
      "username": "john"
    }
json
    {
      "id": 1,
      "email": "john@example.com"
    }

Clients that use v1 keep working. New clients can move to v2 when they are ready.

Pros

Cons

For beginners, URI path versioning is usually the easiest to start with.

2. Query Parameter Versioning

The version is passed as a query parameter.

Examples:

How it works

You keep the same base path and switch logic based on a query parameter.

Example:

http
  GET /api/users?version=1

Response:

json
  {
    "id": 1,
    "username": "john"
  }
http
  GET /api/users?version=2

Response:

json
  {
    "id": 1,
    "email": "john@example.com"
  }

Pros

Cons

3. Header-Based Versioning

The version is provided through HTTP headers.

There are two popular variants:

  1. A custom header like X-API-Version.
  2. The Accept header with a versioned media type.

3.1 Custom Header: `X-API-Version`

Example:

http
GET /api/users HTTP/1.1
Host: api.example.com
X-API-Version: 1

or

http
GET /api/users HTTP/1.1
Host: api.example.com
X-API-Version: 2

Server uses the header to decide which version logic to apply.

Pros:

Cons:

3.2 Accept Header (Content Negotiation)

This uses content negotiation. The client specifies exactly what media type (format) it accepts, including version.

Example:

http
GET /api/users HTTP/1.1
Host: api.example.com
Accept: application/vnd.myapp.v1+json

or

http
GET /api/users HTTP/1.1
Host: api.example.com
Accept: application/vnd.myapp.v2+json

The part application/vnd.myapp.v1+json is a vendor-specific media type.

Pros:

Cons:

For many beginner backends, path versioning or query parameter versioning will be simpler and more practical.

Designing Version Numbers

Once you choose how to send versions, you also need to decide what the version number means.

Common styles:

StyleExamplesTypical meaning
Simple intv1, v2Major versions only
SemVer-likev1.2, v2.0Major and minor versions
Date-basedv2024-01-01Version corresponds to a release date

For beginner REST APIs, a simple integer is usually enough, for example:

Common practice:
Use v1, v2, v3 where each new version includes breaking changes compared to the previous one. Backward compatible changes can be added inside the same version.

You can track minor non-breaking changes in documentation or internal release notes without changing the URL or version identifier.

When Should You Create a New Version?

Not every change needs a new version. Knowing when to create a new version is as important as knowing how.

Changes That Usually Do Not Need a New Version

These are backward compatible, so you normally stay within the same version:

In some cases you still need to communicate these changes, even if you do not bump version.

Changes That Usually Require a New Version

These are breaking and should almost always create a new version:

Example:

json
  {
    "id": 1,
    "username": "john",
    "age": 30
  }

Better approach:

json
    {
      "id": 1,
      "full_name": "John Doe",
      "birth_year": 1994
    }

Supporting Multiple API Versions

Once you introduce a new version, you often need to support multiple versions simultaneously. Old clients stay on the old version until they are updated.

Parallel Version Support

Common approaches:

Example structure in pseudocode:

text
app/
  api/
    v1/
      users.py
      orders.py
    v2/
      users.py  # new behavior
      orders.py # same as v1 or modified
  core/
    models.py   # shared data models
    services.py # shared business logic

Version-specific code (like old and new response models) lives under each version folder. Shared logic that is not version-dependent lives in a shared location.

Deprecation and Sunsetting Old Versions

You probably do not want to support all versions forever. Common lifecycle:

  1. Release v2 next to v1.
  2. Mark v1 as deprecated in documentation.
  3. Communicate a timeline for removal.
  4. After that date, turn off v1 or return clear error messages.

You can also send warnings:

http
  Deprecation: true
  Sunset: Wed, 01 Jan 2025 00:00:00 GMT

Even for beginner projects, it is useful to at least think about a plan:

Handling Version-Specific Bugs

Sometimes a bug exists only in one version. You have a choice:

Example:

The correct choice depends on how serious the bug is and how much clients rely on the current behavior.

Versioning at the Resource vs API Level

Sometimes you might want to version not the entire API, but only specific resources or responses.

For example, your API path might stay as /users, but you have different representations.

Using header-based versioning (content negotiation):

http
  GET /users/1
  Accept: application/vnd.myapp.user.v1+json
http
  GET /users/1
  Accept: application/vnd.myapp.user.v2+json

This can be useful if:

This approach is more advanced, and beginners often do not need it right away. It is good to know it exists and that versioning can happen at different granularity levels:

LevelExample
Whole API/api/v1/... vs /api/v2
Group of endpoints/api/v1/users/* only
Single resourceAccept: ...user.v2+json

Best Practices for API Versioning

Finally, some general guidelines to keep your versioning strategy clean and understandable.

1. Choose One Main Strategy and Be Consistent

Do not mix several different versioning schemes randomly.

For example, choose:

or

Being consistent helps both your users and your future self.

2. Start with `v1` Once the API Is Stable

During early experiments you may change the API often. You may keep it "unversioned" while you are still prototyping.

Once you have a stable API that other people can rely on, introduce v1 to signal stability. From there:

3. Keep Changes Small and Documented

Instead of huge jumps from v1 to v3, make smaller, controlled changes:

A simple version change log per endpoint can help:

text
GET /api/v1/users
- v1.0: initial version.
- v1.1: added "is_active" field (optional).
- v2.0: replaced "username" with "email".

Even if your URLs only show the major version v1 and v2, you still know what has changed inside each.

4. Design for Backward Compatibility When Possible

Try to evolve your API in a way that reduces the need for new versions:

Example:

Instead of:

json
// old
{ "price": 10.0 }
// new
{ "price": "10.00 USD" }

Consider:

json
// transitional
{
  "price": 10.0,          // deprecated
  "price_with_currency": "10.00 USD"
}

Then:

5. Communicate Deprecation Early

If you plan to remove or change something that many clients use:

For example, for a deprecated endpoint in v1, you might return:

http
Warning: 299 - "Deprecated API. Please migrate to /api/v2/users"

6. Avoid Too Many Versions at the Same Time

Maintaining 5 or 6 active versions is complex and error-prone.

Better pattern:

For small or personal projects, you might keep only 1 or 2 versions.

7. Test Each Version Separately

Each version is effectively a separate contract.

Simple example idea:

If you change something, these tests can quickly show which version is affected.

Summary

API versioning helps you:

Key ideas:

With these principles you can design REST APIs that can evolve over time without surprising or breaking the people who use them.

Views: 10

Comments

Please login to add a comment.

Don't have an account? Register now!