Most developers think Swagger is their API documentation. It’s not.
Swagger UI and Scalar are rendering tools. They display your documentation, they don’t create it.
The actual documentation is a structured file that follows the OpenAPI specification. It describes every endpoint, schema, request, and response in your API. This file can exist without any UI at all.
Once I understood this distinction, the way I approached API documentation changed completely.
In this article, I’ll show you how I set up proper API documentation in .NET using Minimal APIs, OpenAPI, and both Swagger and Scalar as rendering layers.
Why Separation Matters
For years, the typical .NET setup looked like this: install Swashbuckle, call AddSwaggerGen(), and you’re done.
The problem? Your documentation becomes tightly coupled to a specific UI library. Swapping Swagger for something else means reconfiguring everything.
The modern approach recommended by Microsoft is straightforward: generate the OpenAPI document independently, then point any UI tool at it. This gives you a clean boundary. The contract stays the same no matter which frontend you choose.
Generating the OpenAPI Document
.NET ships with a dedicated package for this: Microsoft.AspNetCore.OpenApi
Register it in the service container:
builder.Services.AddOpenApi();
This step does one thing, it enables OpenAPI document generation. No UI, no Swagger page. Just the raw contract.
Working With Multiple Versions
I always version my API documentation from day one. Each version gets its own OpenAPI document. Here’s how I configure two versions using document transformers:
builder.Services.AddOpenApi("v1", options =>
{
options.AddDocumentTransformer((document, context, ct) =>
{
document.Info = new()
{
Title = "My API",
Description = "Production-ready API - Version 1",
Version = "v1"
};
document.Servers =
[
new() { Url = "https://localhost:5001", Description = "Development" },
new() { Url = "https://api.myapp.com", Description = "Production" }
];
document.ExternalDocs = new()
{
Description = "External Documentation",
Url = new Uri("https://www.external.com")
};
return Task.CompletedTask;
});
});
builder.Services.AddOpenApi("v2", options =>
{
options.AddDocumentTransformer((document, context, ct) =>
{
document.Info = new()
{
Title = "My API",
Description = "Production-ready API - Version 2",
Version = "v2"
};
document.Servers =
[
new() { Url = "https://localhost:5001", Description = "Development" },
new() { Url = "https://api.myapp.com", Description = "Production" }
];
return Task.CompletedTask;
});
});
var v1 = app.MapGroup("/v1").WithGroupName("v1");
var v2 = app.MapGroup("/v2").WithGroupName("v2");
Each version is completely independent. You can evolve v2 without breaking the v1 contract.
Exposing the Documents
Next, map the OpenAPI JSON endpoints:
app.MapOpenApi("/docs/{documentName}.json");
Now you have two accessible files:
/docs/v1.json – the full contract for version 1
/docs/v2.json – the full contract for version 2
These files are portable. You can version them in source control, share them with frontend teams, or feed them into code generators.
Adding Swagger UI
With the OpenAPI documents ready, Swagger becomes a thin rendering layer. Install only the UI package – Swashbuckle.AspNetCore.SwaggerUI – and point it at your documents:
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/docs/v1.json", "My API - v1");
options.SwaggerEndpoint("/docs/v2.json", "My API - v2");
});
Notice there’s no AddSwaggerGen() call. Swagger is only consuming the OpenAPI files you already generated.
Adding Scalar as an Alternative
Scalar is a more modern documentation UI. I find it cleaner for teams that consume APIs frequently. Install Scalar.AspNetCore and configure it:
app.MapScalarApiReference("/scalar", options =>
{
options.WithOpenApiRoutePattern("/docs/{documentName}.json");
options.AddDocument("v1", "My API - v1");
options.AddDocument("v2", "My API - v2");
});
Same OpenAPI documents, different visual experience. That’s the whole point of keeping things decoupled.
Swagger vs Scalar – Which One?
There’s no wrong answer here. Swagger is battle-tested. Every .NET developer knows it, and most teams already have it integrated. If you need broad familiarity, it’s the safe choice.
Scalar offers a more polished developer experience. Built-in code generation, better navigation, and a cleaner layout. It’s worth trying if your priority is DX for API consumers.
The important takeaway: when your OpenAPI document is well-structured, the UI becomes a replaceable detail.
Takeaways
Here’s how I think about API documentation in .NET:
- Treat the OpenAPI document as the source of truth – it’s your API contract
- Generate the document using Microsoft.AspNetCore.OpenApi – the official library
- Version your documents from the start – switching later is painful
- Keep the UI layer separate – Swagger and Scalar are interchangeable consumers
- Store the generated JSON files in source control if your team needs offline access
The contract is what matters. The UI is just a window into it.



