Documentation

adrs/011-api-versioning-strategy.md

ADR-011: Flexible API Versioning Strategy

Status

Accepted

Context

API versioning is crucial for maintaining backward compatibility while allowing the API to evolve. There are multiple approaches to API versioning, each with trade-offs. We need a strategy that supports our enterprise clients' needs while maintaining clean, discoverable APIs.

Key considerations:

  • Many clients cannot easily modify URLs in their configurations
  • Version numbers in URLs are considered an anti-pattern (they pollute the URL space)
  • Need to support multiple versioning strategies for different client capabilities
  • Default version must be explicit to avoid breaking changes
  • OpenAPI documentation must clearly indicate version support

Decision

We will use a flexible API versioning strategy that supports header and media type versioning, with NO version numbers in URL paths. The system will support multiple versioning methods simultaneously to accommodate different client capabilities.

Implementation approach:

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(5, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = ApiVersionReader.Combine(
        new HeaderApiVersionReader("api-version"),
        new MediaTypeApiVersionReader("version")
    );
});

Supported Versioning Methods

  1. Header Versioning (Preferred)
GET /api/assets HTTP/1.1
api-version: 5.0
  1. Media Type Versioning (Alternative)
GET /api/assets HTTP/1.1
Accept: application/json;version=5.0

Explicitly Rejected Method

URL Path Versioning - NOT SUPPORTED

# This pattern is NOT used
GET /api/v5/assets  ❌

Consequences

Positive

  • Clean URLs: RESTful URLs without version pollution
  • Flexibility: Clients can choose versioning method that works for them
  • Backward Compatibility: Default version prevents breaking existing clients
  • Standards Compliance: Header and media type versioning follow HTTP standards
  • Configuration-Friendly: Headers are easier to configure than URLs
  • URL Stability: URLs remain stable across versions

Negative

  • Discovery: Version not immediately visible in URL
  • Complexity: Supporting multiple versioning methods adds complexity
  • Documentation: Must clearly document versioning approach
  • Debugging: Version not visible in access logs without header logging
  • Caching: CDNs may not consider headers for cache keys

Neutral

  • Client Education: Clients need to understand header-based versioning
  • Testing Tools: Modern tools support header versioning well
  • Gateway Support: API gateways can route based on headers

Implementation Notes

Controller Decoration

[ApiVersion("5.0")]
[ApiVersion("4.0", Deprecated = true)]
[Route("api/[controller]")]
public class AssetsController : ControllerBase
{
    [MapToApiVersion("5.0")]
    [HttpGet]
    public IActionResult GetV5() { }
    
    [MapToApiVersion("4.0")]
    [HttpGet]
    public IActionResult GetV4() { }
}

Minimal API Versioning

var v5 = app.NewApiVersionSet()
    .HasApiVersion(new ApiVersion(5, 0))
    .HasApiVersion(new ApiVersion(4, 0))
    .ReportApiVersions()
    .Build();

app.MapGet("/api/assets", GetAssetsV5)
    .WithApiVersionSet(v5)
    .MapToApiVersion(5, 0);

Response Headers

All responses include available versions:

HTTP/1.1 200 OK
api-supported-versions: 4.0, 5.0
api-deprecated-versions: 3.0

OpenAPI Documentation

Swagger/Scalar configured to show versions:

services.AddApiVersioning()
    .AddApiExplorer(options =>
    {
        options.GroupNameFormat = "'v'VVV";
        options.SubstituteApiVersionInUrl = false; // Important!
    });

Client Examples

C# Client with Kiota

var client = new ApiClient();
client.RequestAdapter.Headers.Add("api-version", "5.0");

JavaScript/TypeScript

const response = await fetch('/api/assets', {
    headers: {
        'api-version': '5.0',
        'Accept': 'application/json'
    }
});

cURL

curl -H "api-version: 5.0" https://api.example.com/api/assets

Version Deprecation Policy

  1. Announce deprecation at least 6 months in advance
  2. Mark version as deprecated in API responses
  3. Add deprecation warnings to response headers
  4. Document migration path in release notes
  5. Support deprecated version for minimum 12 months
  6. Remove only after all clients have migrated

Default Version Strategy

  • Current default: 5.0
  • Default changes only for major releases
  • Unspecified version always uses default
  • Default version is never deprecated
  • ADR-013: Minimal APIs with Static Classes (versioning in minimal APIs)
  • ADR-017: OpenAPI Metadata Enrichment (version documentation)
  • ADR-021: Microsoft Kiota for API Client Generation (client version handling)
  • ADR-022: Scalar API Documentation (version exploration in UI)