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
- Header Versioning (Preferred)
GET /api/assets HTTP/1.1
api-version: 5.0
- 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
- Announce deprecation at least 6 months in advance
- Mark version as deprecated in API responses
- Add deprecation warnings to response headers
- Document migration path in release notes
- Support deprecated version for minimum 12 months
- 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
Related ADRs
- 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)