---
title: "API Versioning & Backward Compatibility: Best Practices"
description: "Learn API versioning best practices for backward compatibility, including semantic versioning, RFC 8594 Sunset headers, and gateway-level version routing."
canonicalUrl: "https://zuplo.com/learning-center/api-versioning-backward-compatibility-best-practices"
pageType: "learning-center"
authors: "adrian"
tags: "API Versioning, API Best Practices"
image: "https://zuplo.com/og?text=API%20Versioning%20%26%20Backward%20Compatibility%3A%20Best%20Practices"
---
API Versioning ensures your software evolves without breaking existing
integrations. Here's how to maintain backward compatibility while rolling out
new features:

- **Versioning Methods**: Choose between URI paths (`/v1`), custom headers
  (`api-version: 1.0`), or query parameters (`?version=1.0`).
- **Additive Changes**: Add new fields or endpoints instead of altering existing
  ones.
- **Thorough Testing**: Automate testing for contract compliance, integration,
  and version comparisons.
- **Documentation**: Provide changelogs, migration guides, and accurate API
  specs.
- **Deprecation Planning**: Notify users 6–12 months in advance and allow
  gradual migration.

**Quick Tip**: Tools like API gateways simplify version management with features
like OpenAPI support to manage documentation of different versions,
version-based routing, and auto-syncing developer portals that keep your users
in the loop on changes.

Want to avoid breaking your API users' trust? Stick to these practices to keep
your APIs stable while introducing updates.

## Making APIs Backward Compatible

Ensuring backward compatibility is all about careful planning to keep existing
integrations intact while rolling out new features.

### Version Naming Methods

As covered in our [API Versioning guide](/learning-center/how-to-version-an-api)
here are some common strategies:

| Method           | Implementation   | Benefits           | Considerations          |
| ---------------- | ---------------- | ------------------ | ----------------------- |
| URI Path         | /v1/resources    | Easy to understand | May make URLs longer    |
| Custom Headers   | api-version: 1.0 | Keeps URLs clean   | Requires header parsing |
| Query Parameters | ?version=1.0     | Simple for testing | Less aligned with REST  |

As discussed in our
[Github API versioning mistakes article](/learning-center/what-the-github-api-gets-wrong) -
header-based versioning is usually the one most poorly implemented when
considering backwards compatibility. Make sure that you enforce the user
providing a version header rather than assuming they always want the latest
version when it's omitted. Same advice applies to query parameter versioning as
well.

### Semantic Version Numbers

Semantic versioning (MAJOR.MINOR.PATCH) is a clear way to communicate API
updates. You wouldn't use it in an API path, but either header or query based
versioning can make use of this pattern. Each part of the version number
represents a specific type of change:

| Version Component | Change Type      | Example | Impact                         |
| ----------------- | ---------------- | ------- | ------------------------------ |
| MAJOR (X.0.0)     | Breaking changes | 2.0.0   | Incompatible API updates       |
| MINOR (1.X.0)     | New features     | 1.1.0   | Compatible with older versions |
| PATCH (1.0.X)     | Bug fixes        | 1.0.1   | Compatible fixes               |

Check out our
[full guide to semantic versioning](/learning-center/semantic-api-versioning)
for more info.

### Adding Features Without Breaking Clients

Introduce new features without causing disruptions by following these best
practices:

- **Start with Feature Flags** Use feature flags to control how and when new
  features are rolled out. This approach allows for gradual deployment and quick
  rollbacks if something goes wrong. For example, when adding new fields to a
  response, hide them behind a flag initially.
- **Stick to Additive Changes** Add new fields or endpoints instead of altering
  existing ones. This ensures older clients keep working while newer ones can
  access additional features. For a deeper look at how small design choices
  prevent breaking changes, see our guide on
  [common pitfalls in RESTful API design](/learning-center/common-pitfalls-in-restful-api-design).
- **Keep Response Structures Consistent** Maintain a predictable response format
  by following these rules:

  | Do ✓                  | Don't ✗                |
  | --------------------- | ---------------------- |
  | Add optional fields   | Remove existing fields |
  | Extend arrays/objects | Change field types     |
  | Add new endpoints     | Modify URL structures  |

When deciding between partial updates and full replacements for your resources,
understanding the difference between
[HTTP PATCH and PUT](/learning-center/http-patch-vs-put-whats-the-difference)
helps you design version-safe endpoints from the start.

## Testing for Breaking Changes

Automated testing in CI/CD pipelines is a must for catching compatibility issues
before they make it to production. Here's a breakdown of effective testing
methods:

| Testing Layer       | Purpose                      | Implementation                       |
| ------------------- | ---------------------------- | ------------------------------------ |
| Contract Testing    | Checks API spec compliance   | OpenAPI specification validation     |
| Integration Testing | Ensures client compatibility | Test against various client versions |
| Version Comparison  | Identifies breaking changes  | Automated diff analysis              |

Every code change should trigger automated tests to confirm:

- Response structures remain consistent, and required fields are intact
- Compatibility with earlier API versions
- Data types are consistent
- Endpoint behaviors stay reliable

This level of testing ensures smooth version management and effective routing.

## Implementing API Versioning at the Gateway

API gateways play a key role in managing versions and ensuring compatibility.
They centralize client routing, enforce version integrity, and keep your backend
code free of version-switching logic.

Zuplo's programmable API gateway, for example, provides tools to maintain
compatibility while reducing complexity:

- **OpenAPI Native** Keeps your API gateway configuration aligned with the
  latest design, avoiding spec-drift. Users typically create a new OpenAPI
  document per version in order to maintain backwards compatibility. These
  documents are then cataloged by the autogenerated developer portal.
- **Version Routing** Built-in routing support for path and header based
  versioning - with a programmable override if you want to do dynamic routing.
- **Custom Version Management** Developers can create custom logic for version
  management using extensible policies, tailoring the solution to specific
  requirements without compromising compatibility.

### URL-Based Versioning with Gateway Routes

URL path versioning is the most widely adopted strategy for public APIs because
it is explicit and easy to discover. When you manage versioned routes through an
API gateway like Zuplo, your `routes.oas.json` file acts as a single source of
truth for both routing and documentation. Here is an example showing v1 and v2
of a resource endpoint:

```json
{
  "paths": {
    "/v1/products": {
      "get": {
        "operationId": "list-products-v1",
        "summary": "List products (v1 - deprecated)",
        "x-zuplo-route": {
          "handler": {
            "export": "default",
            "module": "$import(./modules/v1/products)",
            "options": {}
          },
          "policies": {
            "inbound": ["api-key-auth"],
            "outbound": ["v1-deprecation-headers"]
          }
        }
      }
    },
    "/v2/products": {
      "get": {
        "operationId": "list-products-v2",
        "summary": "List products (v2)",
        "x-zuplo-route": {
          "handler": {
            "export": "default",
            "module": "$import(./modules/v2/products)",
            "options": {}
          },
          "policies": {
            "inbound": ["api-key-auth"],
            "outbound": []
          }
        }
      }
    }
  }
}
```

Each route version gets its own `operationId`, handler module, and policy
pipeline. This means you can direct v1 traffic to a legacy service and v2
traffic to an entirely different implementation without any conditional logic
inside the handlers themselves. The route file is also the foundation for the
auto-generated developer portal, so consumers always see accurate, per-version
documentation.

### Deprecation Headers with an Outbound Policy

One of the strongest signals you can send to API consumers is the combination of
`Deprecation`, `Sunset`, and `Link` headers on responses from older versions.
Instead of adding this logic to every backend service, you can implement it once
as a reusable outbound policy in your gateway. The following TypeScript example
shows how:

```typescript
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

export default async function addDeprecationHeaders(
  response: Response,
  request: ZuploRequest,
  context: ZuploContext,
) {
  // Create a mutable copy of the response headers
  const headers = new Headers(response.headers);

  // RFC 9745 Deprecation header — signals this version is deprecated
  // Note: "true" is a widely used shorthand; RFC 9745 specifies a date value
  headers.set("Deprecation", "true");

  // Sunset header — the date after which this version may stop working
  headers.set("Sunset", "Sat, 31 Jan 2027 23:59:59 GMT");

  // Link header — direct consumers to the replacement version
  headers.set(
    "Link",
    '<https://api.example.com/v2/products>; rel="successor-version"',
  );

  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers,
  });
}
```

The `Deprecation` header follows
[RFC 9745](https://www.rfc-editor.org/rfc/rfc9745) and tells clients that this
version is officially deprecated. The `Sunset` header, defined in
[RFC 8594](https://www.rfc-editor.org/rfc/rfc8594), communicates the exact date
when the endpoint will be decommissioned, giving consumers a concrete deadline.
The `Link` header with `rel="successor-version"` provides a machine-readable
pointer to the replacement, enabling automated migration tooling and developer
dashboards to surface upgrade paths without manual intervention. Because this
policy is attached at the route level, you can add or remove it from any
endpoint through a configuration change rather than a code deployment.

Centralizing version routing at the gateway rather than inside individual
services offers several concrete benefits. It puts all version routing into a
single, declarative configuration file, so every team follows the same
versioning conventions without reimplementing routing logic in each service.
Policies like the deprecation header example are written once and applied to any
number of endpoints, ensuring consistency and reducing the surface area for
mistakes. The entire configuration is stored in version control, so every change
to your versioning strategy is auditable through your standard code review
process. Additionally, because the gateway is the entry point for all traffic,
it is the natural place to collect per-version analytics that inform decisions
about when to sunset older endpoints and how to allocate support resources
during migration windows.

## Change Management for APIs

Managing changes effectively ensures API users stay informed and can adjust
without disruptions.

### Writing Clear Documentation

Documentation acts as the bridge between API providers and users. It should
include changelogs, migration guides, and accurate API specifications:

| Documentation Type | Purpose                        | Key Components                                  |
| ------------------ | ------------------------------ | ----------------------------------------------- |
| Changelogs         | Record version updates         | Version number, date, changes, impacts          |
| Migration Guides   | Help users transition versions | Step-by-step instructions, code examples        |
| API Specifications | Describe current endpoints     | OpenAPI/Swagger specs, request/response schemas |

When updating documentation, focus on:

- **Impact Assessment**: Clearly identify affected endpoints or features.
- **Code Examples**: Show before-and-after examples to guide users.
- **Version Differences**: Highlight specific changes between versions.
- **Breaking Changes**: Clearly flag any updates requiring client modifications.

Using OpenAPI ensures your documentation stays aligned with the API's
implementation. This approach creates a solid foundation for managing updates
effectively.

### Update and End-of-Life Planning

Planning version updates and retirements helps users prepare for changes. Set
clear timelines for:

1\. **Version Deprecation Notice**

Notify users 6–12 months in advance about version deprecation.

2\. **Sunset Schedule**

Provide a timeline that outlines:

- Release dates for new versions.
- Support duration for older versions.
- Final cutoff dates for deprecated versions.

3\. **Migration Windows**

Define transition periods where both old and new versions operate
simultaneously. This allows users to migrate gradually without affecting their
services.

A [developer portal](https://zuplo.com/docs/dev-portal/) integrated with your
API can simplify this process. It offers users self-service access to:

- Current API status and health.
- Version-specific documentation.
- Usage analytics.
- API key management.
- Rate limiting details.

Having a centralized developer portal ensures users can access everything they
need in one place, making transitions smoother.

> If you'd like a more technical walkthrough of API deprecation, check out our
> [API deprecation guide](/learning-center/deprecating-rest-apis)

## Your API Versioning Checklist

Maintaining backward compatibility requires striking the right balance between
introducing new features and ensuring stability. Before you release a new API
version, confirm you have addressed each of these practices:

- **Requiring Explicit Versions from users** ideally in the semver format
- **Implementing thorough testing** to catch potential issues early
- **Providing detailed documentation** and migration guides for developers
- **Planning version lifecycles** to manage updates effectively
- **Allowing adequate transition periods** to minimize disruption

These steps help ensure APIs evolve smoothly while keeping both developers and
end-users in mind. If you're looking to release a new version of your API you'll
need an API gateway tool to manage the transition.
[Sign up for a free Zuplo account today](https://portal.zuplo.com/signup?utm_source=blog)
and discover how easy versioning can be with native OpenAPI support,
version-based routing, gitops, developer portal auto-generation, and more!