TypedResults: Minimal API Responses That Swagger Actually Understands

You build a Minimal API. You add Swagger. You look at the generated docs and every endpoint shows “200: returns some mysterious object.” Your frontend team Slacks you asking what the actual response shape is. You sigh.

The problem is Results.Ok(product) returns IResult. The OpenAPI generator doesn’t know what’s inside. TypedResults fixes this by returning concrete generic types that the metadata system can inspect at build time.

The Problem

app.MapGet("/products/{id}", async (int id, ProductDb db) =>
{
var product = await db.Products.FindAsync(id);
return product is not null
? Results.Ok(product) // IResult — Swagger sees nothing
: Results.NotFound(); // IResult — also nothing
});

Swagger output: 200: Success. What’s the shape? 🤷

The Fix

app.MapGet("/products/{id}", async Task<Results<Ok<Product>, NotFound>> (int id, ProductDb db) =>
{
var product = await db.Products.FindAsync(id);
return product is not null
? TypedResults.Ok(product)
: TypedResults.NotFound();
});

Now Swagger knows: “200 returns a Product. 404 returns nothing.” The Results<T1, T2> union type in the return signature tells the OpenAPI generator exactly what to document.

Multiple Response Types

The union types support up to six variants:

app.MapPost("/orders", async Task<Results<Created<Order>, ValidationProblem, Conflict>> (
OrderRequest request, OrderDb db) =>
{
if (!IsValid(request))
return TypedResults.ValidationProblem(new Dictionary<string, string[]>
{
["amount"] = ["Amount must be positive"]
});
if (await db.Orders.AnyAsync(o => o.IdempotencyKey == request.IdempotencyKey))
return TypedResults.Conflict();
var order = new Order(request);
db.Orders.Add(order);
await db.SaveChangesAsync();
return TypedResults.Created($"/orders/{order.Id}", order);
});

Swagger now documents all three possible responses with their correct status codes and schemas. Your API consumers know exactly what to expect without reading source code.

Comparison

ApproachSwagger DocsCompile-time checks
Results.Ok(obj)❌ Unknown shape❌ None
TypedResults.Ok(obj)✅ Full schema✅ Type mismatch = error
Results<Ok<T>, NotFound> return✅ All responses documented✅ Must return declared types

Key Takeaway

Switch from Results.Ok() to TypedResults.Ok() and declare your return type as Results<T1, T2, ...>. Swagger generates accurate documentation, the compiler catches response type mismatches, and your API consumers finally know what they’re getting back. One refactor, zero ambiguity.