Skip to content
API and Integration

API Versioning Strategies That Actually Work at Scale

11 min read Matt Hammond

API versioning is about managing change without breaking consumers. URL path versioning is the right default for most teams. The harder problems are defining what counts as a breaking change, running multiple versions in parallel, and deprecating old versions without disruption. This guide covers the strategies, the operational reality of each, and how to implement them on Azure.

Why versioning matters more than you think

Every API change is a potential breaking change for someone. A field you consider unused might be the one a client’s automation depends on. A response format tweak that “improves” the API might break a mobile app that has not been updated in six months.

Without a versioning strategy, teams face a choice between two bad options:

  • Never change the API (stagnation, technical debt, inability to improve)
  • Change the API and hope for the best (broken clients, support tickets, lost trust)

Versioning gives you a third option: change the API deliberately, give consumers time to adapt, and retire old versions on a schedule.

The three versioning approaches

URL path versioning

GET /v1/orders
GET /v2/orders

The version is part of the URL. This is the most common approach and the one we recommend as the default.

Advantages:

  • Explicit and visible. You can see the version in logs, browser network tabs, and curl commands.
  • Easy to route at the gateway level. Azure APIM, Nginx, and every API gateway handle path-based routing natively.
  • Easy to document. Each version has its own OpenAPI specification and API reference.
  • Easy to test. You can hit both versions from any HTTP client without special configuration.

Disadvantages:

  • URL proliferation. Each version creates a new set of URLs. This can complicate client code if URLs are constructed dynamically.
  • Purist objection: the version is not a property of the resource, so it does not belong in the URL. This is a valid conceptual argument but rarely a practical problem.

Best for: public APIs, mobile app backends, and any API where consumers vary in how quickly they adopt changes.

Header versioning

GET /orders
Accept: application/vnd.myapi.v2+json

Or with a custom header:

GET /orders
Api-Version: 2

The version is in the request header, keeping URLs clean.

Advantages:

  • Clean URLs that do not change between versions.
  • Aligns with HTTP content negotiation semantics (if using the Accept header).

Disadvantages:

  • Invisible in URLs. You cannot tell which version a request targets by looking at the URL in a log or browser.
  • Harder to test. You need to set headers explicitly, which adds friction for debugging and documentation.
  • Some HTTP clients and tools default to no custom headers, making accidental “latest version” requests common.

Best for: internal APIs where all consumers are controlled by the same team and tooling can be standardised.

Query parameter versioning

GET /orders?api-version=2

The version is a query parameter.

Advantages:

  • Visible in the URL (like path versioning) without changing the path structure.
  • Easy to add to existing APIs without restructuring routes.

Disadvantages:

  • Easily forgotten or omitted. Without a default version policy, missing the parameter can route to an unexpected version.
  • Muddies the query string, mixing version control with filtering and pagination.
  • Less conventional than path versioning, so some tooling and documentation generators handle it less gracefully.

Best for: Azure-native APIs (Azure’s own APIs use this convention) or as a migration path when adding versioning to an existing unversioned API.

What counts as a breaking change?

This is the most important part of a versioning strategy, and the most frequently misunderstood. A breaking change is any change that requires existing clients to modify their code.

Breaking changes (require a new major version)

  • Removing a field from a response
  • Changing a field’s data type (string to integer, object to array)
  • Changing the meaning or semantics of a field
  • Removing an endpoint
  • Changing an endpoint’s URL path
  • Changing required request parameters
  • Changing the structure of error responses
  • Changing authentication requirements

Non-breaking changes (safe within the current version)

  • Adding a new field to a response (clients should ignore unknown fields)
  • Adding a new optional request parameter
  • Adding a new endpoint
  • Adding a new enum value (if documented as extensible)
  • Improving error messages without changing the error structure
  • Performance improvements
  • Bug fixes that make the API behave as documented

The grey area

Some changes are technically non-breaking but practically disruptive:

  • Adding a new required field to a request body (breaks clients that do not send it)
  • Changing default values for optional parameters
  • Changing rate limits or pagination defaults
  • Changing the order of fields in a response (should not break well-written clients, but does break brittle parsers)

Treat grey-area changes as breaking unless you have strong evidence that all consumers handle them correctly. When in doubt, bump the version.

Running multiple versions in parallel

Code organisation

There are three approaches to maintaining multiple API versions in your codebase:

Separate controllers/handlers per version. Each version has its own endpoint handlers. Shared business logic lives in services that both versions call. This is the cleanest approach when versions differ significantly.

Version transformation layer. A single set of handlers serves the latest version. Older versions are served by a transformation layer that converts requests and responses between the old and new formats. This works well when the differences between versions are small.

Branched deployments. Each version runs as a separate deployment (separate App Service, separate container). The API gateway routes to the correct deployment. This provides the strongest isolation but increases operational cost.

For most teams, the transformation layer approach balances maintainability with isolation. Reserve separate deployments for versions with fundamentally different architectures.

Testing across versions

Every version in production needs:

  • Contract tests that verify the API conforms to its OpenAPI specification
  • Integration tests that cover the critical paths for that version
  • Smoke tests that run after every deployment to catch regressions

Automate these in your CI/CD pipeline. A change to shared business logic should trigger tests for all supported versions, not just the latest.

Deprecation workflow

Deprecation is how you retire old versions without breaking consumers. A good deprecation workflow gives consumers clear notice, actionable guidance, and enough time to migrate.

Step 1: announce

Communicate the deprecation through every channel your consumers use:

  • Documentation: mark the version as deprecated with a banner linking to the migration guide
  • Changelog: publish a deprecation notice with the sunset date
  • Email: notify registered API consumers directly
  • Developer portal: display the deprecation status in Azure APIM’s developer portal

Step 2: add response headers

Add a Deprecation header and a Sunset header to every response from the deprecated version:

Deprecation: true
Sunset: Sat, 10 Oct 2026 00:00:00 GMT
Link: <https://docs.example.com/migration-v2>; rel="successor-version"

Azure APIM can inject these headers via an outbound policy without changing application code. Clients that monitor response headers can detect deprecation automatically.

Step 3: monitor adoption

Track traffic to the deprecated version. Use Azure APIM analytics or Application Insights to monitor:

  • Request volume per version over time
  • Unique consumers still using the deprecated version
  • Error rates on the deprecated version

Step 4: sunset

On the sunset date, return 410 Gone for requests to the deprecated version, with a response body explaining the situation and linking to the migration guide. Do not silently redirect to the new version. Silent redirects mask breaking changes and cause subtle bugs.

Timeline

  • Public APIs: 12 months minimum notice
  • Partner APIs: 6-12 months
  • Internal APIs: 3-6 months (or less if you control all consumers)
  • Mobile app APIs: 12 months minimum (users need time to update the app)

Versioning with Azure API Management

Azure APIM has native versioning support that handles the routing, documentation, and lifecycle management.

Versions vs revisions

APIM distinguishes between two concepts:

Versions represent breaking changes that consumers must opt into (v1, v2). Each version has its own API resource in APIM, its own OpenAPI specification, and its own subscription management.

Revisions represent non-breaking changes within a version. A new revision can be tested in isolation (APIM assigns it a revision-specific URL) and promoted to the current revision when validated. Consumers on that version automatically receive the updated revision.

Version routing

APIM supports all three routing schemes:

  • URL path: /v1/orders, /v2/orders
  • Header: Api-Version: 1
  • Query parameter: /orders?api-version=1

Configure the scheme when creating the versioned API in APIM. All subsequent versions use the same scheme.

Deprecation via policy

Add an outbound policy to inject deprecation headers:

<outbound>
  <set-header name="Deprecation" exists-action="override">
    <value>true</value>
  </set-header>
  <set-header name="Sunset" exists-action="override">
    <value>Sat, 10 Oct 2026 00:00:00 GMT</value>
  </set-header>
</outbound>

This marks the version as deprecated at the gateway level without changing application code.

Developer portal

APIM’s developer portal displays all active versions and marks deprecated versions visually. Consumers can browse documentation, test endpoints, and manage subscriptions per version.

For full details on APIM configuration, see Azure API Management.

A versioning policy template

Document your versioning policy and share it with API consumers and internal teams. Here is a template you can adapt.

Version numbering

We use major versions only (v1, v2, v3). Minor and patch changes are non-breaking and do not increment the version number.

Breaking change definition

A breaking change is any change that requires existing clients to modify their code. This includes removing fields, changing types, removing endpoints, and changing auth requirements. See the “What counts as a breaking change” section above for the full taxonomy.

Support window

We support the current version and one prior version. Deprecated versions receive security patches but no new features. The deprecation notice period is [6/12] months.

Communication

Deprecation is communicated via: documentation banner, changelog, email to registered consumers, Deprecation and Sunset response headers, and developer portal status.

Consumer responsibilities

Clients should: ignore unknown fields in responses, handle new optional fields gracefully, monitor Deprecation and Sunset headers, and plan migration within the notice period.

Where to start

If your API has no versioning today:

  1. Add a /v1/ prefix to your current endpoints. This establishes the convention without changing behaviour. Configure APIM to route the versioned path to your existing backend.
  2. Document your breaking change policy. Define what counts as breaking, your support window, and your deprecation timeline. Share it with consumers.
  3. Set up version monitoring. Track requests per version in Application Insights or APIM analytics. You will need this data when deprecation decisions arise.

For help designing a versioning strategy or migrating to versioned APIs, see our REST API development and API and integration services or book a consultation.

Frequently asked questions

What is the best API versioning strategy?
URL path versioning (/v1/resource) is the best default for most teams. It is explicit, debuggable, and well-supported by API gateways. Header versioning offers cleaner URLs but is harder to test and document. Query parameter versioning is a pragmatic middle ground but is rarely used for major versions. Choose based on your consumers: external developers strongly prefer URL path versioning.
When should I increment the major version?
Increment the major version when you make a breaking change: removing a field, changing a field type, changing the meaning of an existing field, removing an endpoint, or changing an error response structure. If existing clients would break without code changes, it is a major version bump.
How many API versions should I support simultaneously?
At minimum, the current version and one prior version. For APIs consumed by mobile apps, support two prior versions because users do not update promptly. For internal APIs with coordinated deployments, you may be able to support only the current version. Define a deprecation timeline (typically 6-12 months) and communicate it clearly.
How does Azure API Management handle versioning?
Azure APIM has native versioning and revision support. Versions represent breaking changes (v1, v2) and are exposed as separate API resources. Revisions represent non-breaking changes within a version and can be tested in isolation before promotion. APIM routes to the correct backend based on the version identifier in the URL path, header, or query parameter.
Should I version GraphQL APIs?
GraphQL's design philosophy avoids versioning. You add new fields and types without breaking existing queries, and deprecate old fields with the @deprecated directive. In practice, this works well for internal APIs. For public GraphQL APIs, you may still need versioned schemas when fundamental schema restructuring is required, but this should be rare.
How do I communicate deprecation to API consumers?
Use multiple channels: a Deprecation response header on every response from the deprecated version, email to registered API consumers, documentation banner, changelog entry, and a sunset date in the API developer portal. Azure APIM can inject the Deprecation header via policy without changing application code.

Ready to transform your software?

Let's talk about your project. Contact us for a free consultation and see how we can deliver a business-critical solution at startup speed.