Add API versioning
Granit.ApiVersioning integrates Asp.Versioning.Mvc to provide URL-segment
versioning for all API endpoints. Combined with Granit.ApiDocumentation, it
generates per-version OpenAPI documents and exposes them through Scalar UI.
Prerequisites
Section titled “Prerequisites”- A working Granit application with at least one endpoint
- Familiarity with Minimal API endpoints
Step 1 — Install the packages
Section titled “Step 1 — Install the packages”dotnet add package Granit.ApiVersioningdotnet add package Granit.ApiDocumentationAdd the module dependencies:
using Granit.Core.Modularity;using Granit.ApiDocumentation;
[DependsOn(typeof(GranitApiDocumentationModule))]public sealed class MyAppHostModule : GranitModule { }GranitApiDocumentationModule depends on GranitApiVersioningModule transitively,
so you do not need to declare both.
Step 2 — Configure versioning
Section titled “Step 2 — Configure versioning”Add the configuration to appsettings.json:
{ "ApiVersioning": { "DefaultMajorVersion": 1, "ReportApiVersions": true }, "ApiDocumentation": { "Title": "My API", "MajorVersions": [1] }}| Option | Type | Default | Description |
|---|---|---|---|
DefaultMajorVersion | int | 1 | Version assumed when client omits it |
ReportApiVersions | bool | true | Adds api-supported-versions and api-deprecated-versions response headers |
Step 3 — Set up versioned route groups
Section titled “Step 3 — Set up versioned route groups”Create an ApiVersionSet and a versioned route group in Program.cs:
using Asp.Versioning;
var app = builder.Build();
await app.UseGranitAsync();
app.UseAuthentication();app.UseAuthorization();
// Declare supported API versionsvar apiVersionSet = app.NewApiVersionSet() .HasApiVersion(new ApiVersion(1)) .ReportApiVersions() .Build();
// Create versioned route groupvar api = app.MapGroup("api/v{version:apiVersion}") .WithApiVersionSet(apiVersionSet);
// All endpoints registered on this group inherit the versionapi.MapInventoryItemEndpoints();api.MapPatientEndpoints();
// Enable Scalar UI at /scalar/v1app.UseGranitApiDocumentation();
app.Run();Clients access endpoints using URL-segment versioning:
GET /api/v1/inventory-itemsGET /api/v1/patients/abc-123Step 4 — Add a new version
Section titled “Step 4 — Add a new version”When you need to introduce breaking changes, declare a new API version:
var apiVersionSet = app.NewApiVersionSet() .HasApiVersion(new ApiVersion(1)) .HasApiVersion(new ApiVersion(2)) .ReportApiVersions() .Build();
var api = app.MapGroup("api/v{version:apiVersion}") .WithApiVersionSet(apiVersionSet);
// Available on both v1 and v2 (same behavior)api.MapGet("/patients", GetAllPatients);
// Available only on v2api.MapGet("/patients/summary", GetPatientsSummary) .MapToApiVersion(2);
// Different implementations per versionapi.MapGet("/patients/{id}", GetPatientV1).MapToApiVersion(1);api.MapGet("/patients/{id}", GetPatientV2).MapToApiVersion(2);Without .MapToApiVersion(), an endpoint is available on all versions declared
in the ApiVersionSet. Use it to restrict an endpoint to a specific version.
Update the documentation to generate both OpenAPI documents:
{ "ApiDocumentation": { "MajorVersions": [1, 2] }}This generates /openapi/v1.json and /openapi/v2.json, each containing only
the endpoints available on that version.
Step 5 — Deprecate an old version
Section titled “Step 5 — Deprecate an old version”Mark a version as deprecated to signal clients they should migrate:
var apiVersionSet = app.NewApiVersionSet() .HasApiVersion(new ApiVersion(1)) .HasDeprecatedApiVersion(new ApiVersion(1)) .HasApiVersion(new ApiVersion(2)) .ReportApiVersions() .Build();Clients receive the api-deprecated-versions: 1.0 response header on every
v1 request, signaling that migration to v2 is expected.
Deprecate individual endpoints
Section titled “Deprecate individual endpoints”For finer control, deprecate specific endpoints with RFC 8594 headers:
api.MapGet("/patients/legacy", GetLegacyPatients) .Deprecated( sunsetDate: "2026-11-01", link: "https://docs.example.com/migration/v1-to-v2");Each response from this endpoint includes:
Deprecation: trueSunset: Sat, 01 Nov 2026 00:00:00 GMTLink: <https://docs.example.com/migration/v1-to-v2>; rel="deprecation"A warning is also logged for every call to a deprecated endpoint using
[LoggerMessage] source-generated logging.
Step 6 — MVC controller versioning
Section titled “Step 6 — MVC controller versioning”If your application uses MVC controllers instead of Minimal APIs:
[ApiController][Route("api/v{version:apiVersion}/patients")][ApiVersion("1.0")]public sealed class PatientController : ControllerBase{ [HttpGet] public IActionResult GetAll() => Ok();}
[ApiController][Route("api/v{version:apiVersion}/patients")][ApiVersion("2.0")]public sealed class PatientV2Controller : ControllerBase{ [HttpGet] public IActionResult GetAll() => Ok();}Versioning strategies summary
Section titled “Versioning strategies summary”| Strategy | URL format | ISO 27001 audit trail |
|---|---|---|
| URL segment (recommended) | /api/v1/patients | Version in access logs |
| Query string (fallback) | /api/patients?api-version=1.0 | Version in access logs |
| Header (not supported) | X-Api-Version: 1.0 | Often omitted by reverse proxies |
Complete example
Section titled “Complete example”using Asp.Versioning;using Granit.Core.Extensions;using MyApp.Host;
var builder = WebApplication.CreateBuilder(args);
await builder.AddGranitAsync<MyAppHostModule>();
var app = builder.Build();
await app.UseGranitAsync();
app.UseAuthentication();app.UseAuthorization();
var apiVersionSet = app.NewApiVersionSet() .HasApiVersion(new ApiVersion(1)) .HasApiVersion(new ApiVersion(2)) .HasDeprecatedApiVersion(new ApiVersion(1)) .ReportApiVersions() .Build();
var api = app.MapGroup("api/v{version:apiVersion}") .WithApiVersionSet(apiVersionSet);
api.MapGet("/patients", GetAllPatients);api.MapGet("/patients/{id}", GetPatientV1).MapToApiVersion(1);api.MapGet("/patients/{id}", GetPatientV2).MapToApiVersion(2);api.MapGet("/patients/summary", GetPatientsSummary).MapToApiVersion(2);
app.UseGranitApiDocumentation();app.MapHealthChecks("/healthz");
app.Run();Next steps
Section titled “Next steps”- Add an endpoint — create endpoints with validation and error handling
- Configure multi-tenancy — add tenant headers to versioned APIs
- API & Web reference — full API documentation module details