In this blog post Add OpenAPI to .NET Minimal APIs and Generate Docs the Right Way we will walk through what OpenAPI is, how it pairs beautifully with .NET Minimal APIs, and the practical steps to produce clean, accurate, and automated API documentation.
OpenAPI is the industry-standard way to describe HTTP APIs. With it, your API gains a living contract that developers and tools can read, test against, and generate clients from. Minimal APIs in .NET make building lightweight services fast; OpenAPI turns them into discoverable, easy-to-consume products. In Add OpenAPI to .NET Minimal APIs and Generate Docs the Right Way we’ll start at a high level, then move into concrete steps you can copy into your projects today.
What is OpenAPI and why it matters
OpenAPI (formerly Swagger) is a language-agnostic specification for describing RESTful APIs. It defines your endpoints, inputs, outputs, errors, and security in a single source of truth. From that, teams can:
- Render a friendly, interactive UI for developers.
- Generate client SDKs for C#, TypeScript, Java, and more.
- Automate contract testing and API governance.
- Publish stable, versioned documents for partners and auditors.
.NET Minimal APIs embrace convention over ceremony: a few lines to map routes, return results, and you’re shipping. Add the built-in Endpoint API Explorer and an OpenAPI generator (commonly Swashbuckle), and you get up-to-date docs with almost no effort.
What we’ll build
We’ll add OpenAPI to a Minimal API, customize metadata, secure endpoints, version docs, and generate a static OpenAPI file for CI. You can apply this pattern to new or existing projects.
Prerequisites
- .NET 7 or .NET 8 SDK
- A Minimal API project (e.g., created with
dotnet new web
or any existing service)
Step 1: Add the OpenAPI packages
Install the packages that power OpenAPI for Minimal APIs.
dotnet add package Swashbuckle.AspNetCore
dotnet add package Microsoft.AspNetCore.OpenApi
Swashbuckle generates the OpenAPI spec and hosts Swagger UI. The Microsoft.AspNetCore.OpenApi
package provides helpers like WithOpenApi
to enrich operations.
Step 2: Wire up OpenAPI in Program.cs
Add the Endpoint API Explorer and Swagger services, then enable middleware. Here’s a compact example with a sample Todo endpoint.
using System.Reflection;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
// 1) Discover endpoints and generate OpenAPI
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Todo API",
Version = "v1",
Description = "A minimal API for managing todos",
Contact = new OpenApiContact
{
Name = "CloudPro Inc",
Url = new Uri("https://cloudproinc.com.au")
}
});
});
var app = builder.Build();
// 2) Enable Swagger in dev (or behind a feature flag)
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Todo API v1");
options.DocumentTitle = "Todo API Docs";
});
}
// 3) Sample endpoint
app.MapGet("/todos/{id}", (int id) =>
{
var todo = new Todo(id, "Write blog post", false);
return Results.Ok(todo);
})
.WithTags("Todos")
.WithOpenApi(op =>
{
op.Summary = "Get a single todo";
op.Description = "Returns a todo by its ID.";
return op;
});
app.Run();
record Todo(int Id, string Title, bool IsDone);
Run the app and navigate to /swagger
. You should see an interactive UI and a machine-readable spec at /swagger/v1/swagger.json
.
Step 3: Enrich your documentation
Good docs are more than endpoints—they explain intent. Minimal APIs let you annotate operations without controllers or attributes.
- Tags group related endpoints in the UI (
.WithTags("Todos")
). - Summaries and descriptions clarify behavior (
.WithOpenApi(op => {{...}})
). - Parameter hints improve discoverability (
op.Parameters[i].Description
).
Example of a POST endpoint with richer metadata:
app.MapPost("/todos", (Todo todo) =>
{
// Normally you would save to a database
return Results.Created($"/todos/{todo.Id}", todo);
})
.WithTags("Todos")
.WithOpenApi(op =>
{
op.Summary = "Create a new todo";
op.Description = "Creates a todo item and returns it with its new URI.";
// Describe the request body
if (op.RequestBody != null)
{
var content = op.RequestBody.Content["application/json"];
content.Example = System.Text.Json.JsonDocument.Parse(
"{ \"id\": 123, \"title\": \"Write docs\", \"isDone\": false }").RootElement;
}
return op;
});
Use XML comments for type and field documentation
XML comments let you document record and DTO properties once and reuse those descriptions in OpenAPI.
- Enable XML docs in your project file:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn> <!-- optional: ignore missing XML warnings -->
</PropertyGroup>
- Tell SwaggerGen to include them:
builder.Services.AddSwaggerGen(c =>
{
// ... your SwaggerDoc config
var xml = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xml);
c.IncludeXmlComments(xmlPath, includeControllerXmlComments: true);
});
- Add comments to your DTOs:
/// <summary>Represents a to-do item.</summary>
/// <param name="Id">Unique identifier.</param>
/// <param name="Title">Human-friendly description of the task.</param>
/// <param name="IsDone">Whether the task is completed.</param>
public record Todo(int Id, string Title, bool IsDone);
Rebuild and refresh Swagger UI—your types will now carry these descriptions.
Version and group your API
It’s common to ship multiple versions or group endpoints by domain. Minimal APIs support grouping and Swashbuckle can emit separate documents per group.
// Register multiple docs
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Todo API", Version = "v1" });
c.SwaggerDoc("v2", new OpenApiInfo { Title = "Todo API", Version = "v2" });
});
var app = builder.Build();
// Versioned route groups
var v1 = app.MapGroup("/v1").WithGroupName("v1").WithTags("Todos");
var v2 = app.MapGroup("/v2").WithGroupName("v2").WithTags("Todos");
v1.MapGet("/todos", () => Results.Ok(new[] { new Todo(1, "v1 task", false) }))
.WithOpenApi(op => { op.Summary = "List todos (v1)"; return op; });
v2.MapGet("/todos", () => Results.Ok(new[] { new Todo(1, "v2 task", true) }))
.WithOpenApi(op => { op.Summary = "List todos (v2)"; return op; });
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Todo API v1");
options.SwaggerEndpoint("/swagger/v2/swagger.json", "Todo API v2");
});
}
This yields separate, navigable docs for each version. For enterprise-grade versioning semantics, consider the ASP.NET API Versioning library, but route groups are often enough for Minimal APIs.
Document security and protect endpoints
Secure APIs should communicate how to authenticate. Add a Bearer (JWT) scheme and require it on protected routes.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(); // Configure issuer, audience, keys as needed
builder.Services.AddAuthorization();
builder.Services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization using the Bearer scheme. Example: Bearer {token}",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
}, new string[] {}
}
});
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/me", () => Results.Ok(new { Name = "Ada" }))
.RequireAuthorization()
.WithOpenApi(op => { op.Summary = "Get the current user"; return op; });
Swagger UI now shows a lock icon and an Authorize button. Enter a valid JWT once and try secured endpoints interactively.
Generate static OpenAPI documents for CI/CD
Interactive docs are great, but most teams also need a static, versioned OpenAPI file for reviews, client generation, and publication. Use the Swashbuckle CLI to produce one during builds.
- Install the tool (once per machine/runner):
dotnet tool install --global Swashbuckle.AspNetCore.Cli
- Build and export the spec:
dotnet build -c Release
swagger tofile \
--output ./openapi/v1.json \
./bin/Release/net8.0/YourApi.dll v1
This writes a clean OpenAPI JSON file without hosting your app. Commit it, publish it as a pipeline artifact, or push it to an internal developer portal.
Generate typed clients (optional but powerful)
Once you have OpenAPI, you can generate clients to reduce boilerplate and runtime errors. For example, with NSwag:
dotnet tool install --global NSwag.ConsoleCore
nswag openapi2csclient /input:./openapi/v1.json /output:./Clients/TodoClient.cs /namespace:Todo.Client
Distribute the generated client alongside your API package or include it in a shared SDK repository.
Troubleshooting and tips
- If the UI shows no endpoints, ensure
AddEndpointsApiExplorer
is registered beforeBuild()
and that you are callingUseSwagger()
. - Keep
UseSwagger()
enabled only in safe environments, or behind a feature flag, if your docs reveal sensitive shapes. - Prefer explicit return types and consistent status codes; your docs will be clearer and client generation more reliable.
- When introducing breaking changes, publish both versions (v1 and v2) and provide a migration guide.
A quick quality checklist
- Title, version, and contact info are set in
SwaggerDoc
. - Endpoints grouped with
WithTags
andWithGroupName
. - Operation summaries and descriptions added with
WithOpenApi
. - XML comments enabled and included for model documentation.
- Security scheme defined and enforced where needed.
- Static OpenAPI file produced in CI for each release.
Wrap-up
OpenAPI turns your Minimal API into a product: discoverable, testable, and easy to integrate. With a few lines of code, you get interactive docs, strong contracts, and a path to automated client generation. Start small—add AddEndpointsApiExplorer
and AddSwaggerGen
—then iterate with summaries, versioning, and security until your documentation reflects the professionalism of your API. Your developers, partners, and future self will thank you.
Discover more from CPI Consulting
Subscribe to get the latest posts sent to your email.