Mastering API Versioning with OpenAPI in .NET 10: A Practical Q&A Guide
By
<p>API versioning is a critical practice for any long-lived API, allowing you to introduce changes without breaking existing clients. With .NET 10, Microsoft has strengthened OpenAPI support through <em>Microsoft.AspNetCore.OpenApi</em>, and the <strong>Asp.Versioning v10</strong> package now seamlessly integrates with it. This Q&A covers everything you need to know—from why versioning matters to implementing it with Minimal APIs and controllers, generating separate OpenAPI documents, and visualizing documentation with SwaggerUI or Scalar. Follow along step by step, and you'll keep both your API evolution and documentation clean.</p>
<h2 id="q1">Why is API versioning important, and what strategies exist?</h2>
<p>API versioning ensures backward compatibility as your API evolves. Without it, a single breaking change could disrupt all existing clients. Common strategies include:</p><figure style="margin:20px 0"><img src="https://devblogs.microsoft.com/dotnet/wp-content/uploads/sites/10/2026/04/api-versioning.webp" alt="Mastering API Versioning with OpenAPI in .NET 10: A Practical Q&A Guide" style="width:100%;height:auto;border-radius:8px" loading="lazy"><figcaption style="font-size:12px;color:#666;margin-top:5px">Source: devblogs.microsoft.com</figcaption></figure>
<ul>
<li><strong>URL Path Versioning</strong> – e.g., <code>/api/v1/users</code></li>
<li><strong>Query String Versioning</strong> – e.g., <code>/api/users?version=1</code></li>
<li><strong>Header Versioning</strong> – e.g., <code>X-API-Version: 1</code></li>
<li><strong>Media Type Versioning</strong> – using the <code>Accept</code> header.</li>
</ul>
<p>Each approach has trade-offs. URL path versioning is the most visible and commonly used. The choice depends on your API's requirements and client expectations. The key is to pick a strategy and apply it consistently, which the <strong>Asp.Versioning</strong> library makes straightforward in .NET 10.</p>
<h2 id="q2">What changed with OpenAPI and versioning in .NET 10?</h2>
<p>Prior to .NET 9, adding OpenAPI documentation for versioned APIs required custom work or third-party libraries. <em>Microsoft.AspNetCore.OpenApi</em>, introduced in .NET 9, provides built-in OpenAPI support but initially lacked tight versioning integration. With .NET 10 and <strong>Asp.Versioning v10</strong>, Microsoft's package now generates separate OpenAPI documents per version automatically (e.g., <code>/openapi/v1.json</code>, <code>/openapi/v2.json</code>). This eliminates duplicated configuration and manual wires. The library also respects the versioning scheme you choose, so whether you use URL path, header, or query string versioning, your OpenAPI output reflects the correct endpoints and schemas for each version.</p>
<h2 id="q3">How do I implement API versioning in a .NET 10 application with Minimal APIs?</h2>
<p>Start by adding the <strong>Asp.Versioning.Http</strong> and <strong>Asp.Versioning.MinimalApi</strong> NuGet packages. In <code>Program.cs</code>, configure versioning:</p>
<pre><code>builder.Services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
}).AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});</code></pre>
<p>Then, on your Minimal API endpoints, use <code>MapVersionedEndpoint</code> or simply call <code>.HasApiVersion(1.0)</code> on route handlers. For example:</p>
<pre><code>app.MapGet("/api/v{version:apiVersion}/products", (ApiVersion version) =>
{
return Results.Ok(new { Version = version.ToString() });
}).HasApiVersion(1.0);</code></pre>
<p>This ensures that each endpoint is bound to the specified version. The <code>ApiVersion</code> parameter is automatically injected. You can build multiple version sets by chaining different routes or using version policies.</p>
<h2 id="q4">How does versioning work with controllers in .NET 10?</h2>
<p>Controllers follow a similar pattern but use attributes. Install the <strong>Asp.Versioning.Mvc.ApiExplorer</strong> package (for OpenAPI integration). Decorate your controller with <code>[ApiVersion("1.0")]</code> and optionally <code>[Route("api/v{version:apiVersion}/[controller]")]</code>. For example:</p>
<pre><code>[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/products")]
[ApiController]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts(ApiVersion version)
{
return Ok(new { Version = version.ToString() });
}
}</code></pre>
<p>For multiple versions, create separate controllers with different version attributes or use a single controller and let the versioning middleware map the correct action. The <code>SubstituteApiVersionInUrl</code> option, when set to <code>true</code>, automatically replaces <code>{version:apiVersion}</code> in the route with the actual version number for OpenAPI document generation.</p><figure style="margin:20px 0"><img src="https://uhf.microsoft.com/images/microsoft/RE1Mu3b.png" alt="Mastering API Versioning with OpenAPI in .NET 10: A Practical Q&A Guide" style="width:100%;height:auto;border-radius:8px" loading="lazy"><figcaption style="font-size:12px;color:#666;margin-top:5px">Source: devblogs.microsoft.com</figcaption></figure>
<h2 id="q5">How do I generate separate OpenAPI documents for each API version?</h2>
<p>After setting up versioning as described, add OpenAPI support by calling <code>AddOpenApi()</code> on the service collection. To produce distinct documents per version, configure the <code>OpenApiOptions</code> using the <strong>Asp.Versioning.Options</strong> pattern:</p>
<pre><code>builder.Services.AddOpenApi("v1", options =>
{
options.AddDocumentTransformer((document, context, cancellationToken) =>
{
document.Info.Version = "1.0";
return Task.CompletedTask;
});
});
// Repeat for v2, v3, etc.</code></pre>
<p>Or use a dynamic approach by iterating over the supported API versions from <code>builder.Services.GetRequiredApiVersionSet()</code>. Then, in the pipeline, call <code>app.MapOpenApi()</code> without parameters—the framework will create endpoints like <code>/openapi/v1.json</code> and <code>/openapi/v2.json</code> automatically. This keeps your OpenAPI documentation in sync with your actual versioned routes.</p>
<h2 id="q6">How can I visualize versioned API docs with SwaggerUI or Scalar?</h2>
<p>Once you have OpenAPI documents per version, add a UI. For <strong>SwaggerUI</strong>, use the <em>Swashbuckle.AspNetCore.SwaggerUI</em> package:</p>
<pre><code>app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/openapi/v1.json", "API v1");
options.SwaggerEndpoint("/openapi/v2.json", "API v2");
});</code></pre>
<p>For <strong>Scalar</strong>, install <em>Scalar.AspNetCore</em> and add:</p>
<pre><code>app.MapScalarApiReference(options =>
{
options.WithOpenApiRoutePattern("/openapi/{documentName}.json");
});</code></pre>
<p>Both tools will display version dropdowns, allowing developers to test each version separately. Ensure your <code>AddApiExplorer</code> configuration includes <code>GroupNameFormat = "'v'VVV"</code> so that the version group names match the document names (<code>v1</code>, <code>v2</code>).</p>
<h2 id="q7">How do I maintain versioned APIs as my application evolves?</h2>
<p>Plan your versioning strategy early. Deprecate versions gracefully by setting <code>options.ReportApiVersions = true</code> to include sunset headers. Use the <code>[Deprecated]</code> attribute or <code>options.DeprecatedApiVersions</code> to mark endpoints as deprecated. In OpenAPI, you can add custom transformers to set a <code>deprecated</code> flag on each endpoint. For breaking changes, introduce a new version rather than modifying an existing one. Keep your OpenAPI documents updated by regenerating them when you add or remove versioned endpoints. Regularly audit version usage via logging and client feedback. Tools like <strong>Automatically generate changelog</strong> from OpenAPI diffs can help communicate changes to consumers.</p>