Skip to main content
skill CX Codex

minimal-api — Claude Code skill: .NET 10 HTTP endpoints

A skill that steers your coding agent through .NET 10 minimal APIs: endpoint groups, TypedResults, filters and OpenAPI.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add codewithmukesh/dotnet-claude-kit --skill minimal-api -a claude-code
Open on GitHub

What minimal-api does

This skill covers how to build HTTP endpoints with .NET 10 minimal APIs. It ships rules and code patterns: grouping with MapGroup, auto-discovery of endpoint groups through an IEndpointGroup interface, and a single app.MapEndpoints() call in Program.cs that stays untouched as you add new groups.

There is a section on TypedResults for compile-time safe responses and correct OpenAPI schemas, plus parameter binding from route, query, headers, body and DI. Endpoint filters handle cross-cutting work like validation and logging. The canonical ValidationFilter<TRequest> built on FluentValidation lives in the error-handling skill, and this one tells you to reuse it instead of rewriting it per project. Documentation relies on the built-in OpenAPI support in .NET 10 rather than Swashbuckle.

The skill loads when the conversation mentions things like "endpoint", MapGet, MapPost, MapGroup, TypedResults, "route", "swagger" or "rate limiting". The source text cuts off at the rate limiting section, so there is nothing here about configuring limits — worth checking the full file in the repo.

What it helps with

  • put an endpoint group in its own file and register it automatically
  • return a typed response that shows up correctly in the OpenAPI schema
  • bind route, query and header parameters without manual parsing
  • attach validation to a mutating endpoint with a filter
  • set up OpenAPI docs without Swashbuckle

Example prompts for the agent

  • add a new orders endpoint using MapGroup and TypedResults
  • why is my response schema missing from the OpenAPI spec
  • set up query parameter binding with AsParameters

How to install minimal-api

1Skills CLI (Vercel)

npx skills add codewithmukesh/dotnet-claude-kit --skill minimal-api -a claude-code

2Manually: put the files in place

~/.claude/skills/minimal-api/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/minimal-api/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about minimal-api

How do I install minimal-api in Claude?

Run npx skills add codewithmukesh/dotnet-claude-kit --skill minimal-api -a claude-code. The skill lands in ~/.claude/skills/minimal-api/SKILL.md.

What does minimal-api do?

It gives the agent rules and patterns for .NET 10 minimal APIs: endpoint groups, TypedResults, filters, parameter binding and OpenAPI metadata.

Does the skill implement the validation filter itself?

No. It points to the canonical ValidationFilter<TRequest> in the error-handling skill and advises against re-implementing it per project.

Source text

SKILL.md · MIT
---
name: minimal-api
description: >
  .NET 10 minimal APIs — the default for building HTTP endpoints. Covers MapGroup,
  endpoint filters, TypedResults, OpenAPI metadata, parameter binding, and route
  conventions.
  Load this skill when creating API endpoints, configuring routing, setting up
  OpenAPI documentation, or when the user mentions "endpoint", "MapGet", "MapPost",
  "MapGroup", "TypedResults", "route", "minimal API", "OpenAPI", "swagger",
  "rate limiting", or "output caching".
---

# Minimal APIs (.NET 10)

## Core Principles

1. **Minimal APIs are the default** — Use controllers only when migrating legacy code. Minimal APIs are lighter, faster, and compose well with any architecture style.
2. **Group endpoints with `MapGroup`** — Never scatter individual `MapGet`/`MapPost` calls in `Program.cs`. Group related endpoints together.
3. **Use `TypedResults` for OpenAPI** — `TypedResults.Ok(value)` gives you compile-time type safety AND correct OpenAPI documentation. `Results.Ok(value)` does not.
4. **Metadata over comments** — Use `.WithName()`, `.WithTags()`, `.WithSummary()` to document endpoints. The metadata feeds into OpenAPI specs.

## Patterns

### Endpoint Group Auto-Discovery (Required Pattern)

Every endpoint group lives in its own file and implements `IEndpointGroup`. A single `app.MapEndpoints()` call in `Program.cs` discovers and registers all groups automatically. **Program.cs never changes when you add new endpoint groups.**

```csharp
// Extensions/IEndpointGroup.cs
public interface IEndpointGroup
{
    void Map(IEndpointRouteBuilder app);
}
```

```csharp
// Extensions/EndpointExtensions.cs
public static class EndpointExtensions
{
    public static WebApplication MapEndpoints(this WebApplication app)
    {
        var groups = typeof(Program).Assembly
            .GetTypes()
            .Where(t => t.IsAssignableTo(typeof(IEndpointGroup)) && !t.IsInterface && !t.IsAbstract)
            .Select(Activator.CreateInstance)
            .Cast<IEndpointGroup>();

        foreach (var group in groups)
            group.Map(app);

        return app;
    }
}
```

```csharp
// Program.cs — this NEVER changes when adding endpoints
var app = builder.Build();
app.MapEndpoints();
app.Run();
```

```csharp
// Features/Orders/OrderEndpoints.cs — one file per endpoint group
public sealed class OrderEndpoints : IEndpointGroup
{
    public void Map(IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/api/orders").WithTags("Orders");

        group.MapPost("/", CreateOrder)
            .WithName("CreateOrder")
            .WithSummary("Create a new order")
            .Produces<OrderResponse>(StatusCodes.Status201Created)
            .ProducesValidationProblem()
            .RequireAuthorization();

        group.MapGet("/{id:guid}", GetOrder)
            .WithName("GetOrder")
            .Produces<OrderResponse>()
            .ProducesProblem(StatusCodes.Status404NotFound);

        group.MapGet("/", ListOrders)
            .WithName("ListOrders")
            .Produces<PagedList<OrderResponse>>();
    }

    private static async Task<Results<Created<OrderResponse>, ValidationProblem>> CreateOrder(
        CreateOrderRequest request,
        ISender sender,
        CancellationToken ct)
    {
        var result = await sender.Send(new CreateOrder.Command(request.CustomerId, request.Items), ct);
        return result.IsSuccess
            ? TypedResults.Created($"/api/orders/{result.Value.Id}", result.Value)
            : TypedResults.ValidationProblem(result.Errors);
    }

    private static async Task<Results<Ok<OrderResponse>, NotFound>> GetOrder(
        Guid id,
        ISender sender,
        CancellationToken ct)
    {
        var result = await sender.Send(new GetOrder.Query(id), ct);
        return result.IsSuccess
            ? TypedResults.Ok(result.Value)
            : TypedResults.NotFound();
    }

    private static async Task<Ok<PagedList<OrderResponse>>> ListOrders(
        [AsParameters] ListOrdersQuery query,
        ISender sender,
        CancellationToken ct)
    {
        var result = await sender.Send(query, ct);
        return TypedResults.Ok(result);
    }
}
```

### TypedResults for Type-Safe Responses

`TypedResults` provides compile-time guarantees and automatic OpenAPI schema generation.

```csharp
// GOOD — TypedResults with union return type
private static async Task<Results<Ok<Product>, NotFound, ValidationProblem>> GetProduct(
    Guid id,
    AppDbContext db,
    CancellationToken ct)
{
    var product = await db.Products.FindAsync([id], ct);
    return product is not null
        ? TypedResults.Ok(product)
        : TypedResults.NotFound();
}
```

### Parameter Binding

.NET 10 minimal APIs bind parameters from route, query, header, body, and DI automatically.

```csharp
// Route parameters
app.MapGet("/orders/{id:guid}", (Guid id) => ...);

// Query parameters (nullable = optional)
app.MapGet("/orders", (int page, int? pageSize, string? status) => ...);

// Complex query parameters with [AsParameters]
public record ListOrdersQuery(int Page = 1, int PageSize = 20, string? Status = null);
app.MapGet("/orders", ([AsParameters] ListOrdersQuery query) => ...);

// Header binding
app.MapGet("/orders", ([FromHeader(Name = "X-Correlation-Id")] string? correlationId) => ...);

// DI services are auto-resolved (no attribute needed)
app.MapPost("/orders", (CreateOrderRequest request, ISender sender) => ...);
```

### Endpoint Filters

Filters are the minimal API equivalent of action filters. Use them for cross-cutting concerns like validation, logging, and idempotency checks.

The canonical `ValidationFilter<TRequest>` implementation (FluentValidation, resolves the validator from DI and skips gracefully when none is registered) lives in the **error-handling** skill — use that one, don't re-implement it per project.

```csharp
// Apply the canonical filter (see error-handling skill) to a mutating endpoint
group.MapPost("/", CreateOrder)
    .AddEndpointFilter<ValidationFilter<CreateOrderRequest>>();

// Apply a filter to a group (affects all endpoints in the group)
group.AddEndpointFilter<LoggingFilter>();
```

### OpenAPI / Swagger Configuration

.NET 10 has built-in OpenAPI support. Use it instead of Swashbuckle.

```csharp
// Program.cs — service registration only, no endpoint wiring
builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}
app.MapEndpoints(); // auto-discovers all IEndpointGroup implementations

// Endpoint metadata enriches the OpenAPI spec
group.MapPost("/", CreateOrder)
    .WithName("CreateOrder")
    .WithSummary("Create a new order")
    .WithDescription("Creates a new order for the specified customer with the given line items.")
    .Produces<OrderResponse>(StatusCodes.Status201Created)
    .ProducesValidationProblem()
    .ProducesProblem(StatusCodes.Status500InternalServerError);
```

### Rate Limiting

```csharp
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("api", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(1);
    });
});

// Apply inside an IEndpointGroup.Map method
var group = app.MapGroup("/api/orders")
    .WithTags("Orders")
    .RequireRateLimiting("api");
```

### Output Caching

```csharp
builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(5)));
    options.AddPolicy("ByIdCache", builder => builder
        .Expire(TimeSpan.FromMinutes(10))
        .SetVaryByRouteValue("id"));
});

group.MapGet("/{id:guid}", GetOrder)
    .CacheOutput("ByIdCache");
```

## Anti-patterns

### Don't Put Endpoints in Program.cs

```csharp
// BAD — endpoints scattered in Program.cs
app.MapGet("/orders", async (AppDbContext db) => await db.Orders.ToListAsync());
app.MapGet("/orders/{id}", async (Guid id, AppDbContext db) => await db.Orders.FindAsync(id));
app.MapPost("/orders", async (Order order, AppDbContext db) => { /* ... */ });
app.MapGet("/products", async (AppDbContext db) => await db.Products.ToListAsync());

// ALSO BAD — manual MapGroup calls in Program.cs (grows with every feature)
app.MapGroup("/api/orders").WithTags("Orders").MapOrderEndpoints();
app.MapGroup("/api/products").WithTags("Products").MapProductEndpoints();
app.MapGroup("/api/customers").WithTags("Customers").MapCustomerEndpoints();
// Program.cs grows every time you add a feature...

// GOOD — auto-discovered, Program.cs never changes
app.MapEndpoints(); // discovers all IEndpointGroup implementations
```

### Don't Use Untyped Results

```csharp
// BAD — Results.Ok doesn't contribute to OpenAPI schema
private static async Task<IResult> GetOrder(Guid id, AppDbContext db)
{
    var order = await db.Orders.FindAsync(id);
    return order is not null ? Results.Ok(order) : Results.NotFound();
}

// GOOD — TypedResults with explicit union type
private static async Task<Results<Ok<Order>, NotFound>> GetOrder(Guid id, AppDbContext db)
{
    var order = await db.Orders.FindAsync(id);
    return order is not null ? TypedResults.Ok(order) : TypedResults.NotFound();
}
```

### Don't Return Domain Entities Directly

```csharp
// BAD — leaks internal structure, can't evolve independently
app.MapGet("/orders/{id}", async (Guid id, AppDbContext db) =>
    await db.Orders.Include(o => o.Items).FirstOrDefaultAsync(o => o.Id == id));

// GOOD — map to a response DTO
app.MapGet("/orders/{id}", async (Guid id, AppDbContext db) =>
{
    var order = await db.Orders
        .Where(o => o.Id == id)
        .Select(o => new OrderResponse(o.Id, o.Total, o.CreatedAt))
        .FirstOrDefaultAsync();
    return order is not null ? TypedResults.Ok(order) : TypedResults.NotFound();
});
```

## Decision Guide

| Scenario | Recommendation |
|----------|---------------|
| New HTTP API | `IEndpointGroup` per feature + `app.MapEndpoints()` auto-discovery |
| Existing MVC project | Keep controllers, migrate incrementally |
| OpenAPI documentation | Use `TypedResults` + `.WithName()` + `.WithSummary()` |
| Request validation | Endpoint filter with FluentValidation |
| Authentication/authorization | `.RequireAuthorization("PolicyName")` on group or endpoint |
| Rate limiting | `AddRateLimiter` + `.RequireRateLimiting()` |
| Response caching | `AddOutputCache` + `.CacheOutput()` |
| Complex model binding | `[AsParameters]` with a record type |

© codewithmukesh · MIT · codewithmukesh/dotnet-claude-kit

Similar skills

All skills for Backend and APIs →

From the same set47

ci-cd Generates CI/CD YAML pipelines for .NET projects. Useful when configuring GitHub Actions or Azure DevOps with build, test, format, and deployment stages. CX Codex +1 2,800installs resilience A skill that tells your coding agent how to build fault-tolerant calls in .NET 10 with Polly v8: retries, circuit breakers, timeouts, fallback. CX Codex +1 2,800installs authentication A skill that feeds your coding agent working patterns for ASP.NET Core auth: JWT bearer, OpenID Connect, Identity, authorization policies. CX Codex +1 2,800installs dependency-injection Guides dependency injection patterns in .NET 10, covering service lifetimes, keyed services, Scrutor decorators, and captive dependency fixes. CX Codex +1 2,800installs testing A skill that lays out a testing strategy for .NET 10 apps: integration tests via WebApplicationFactory, real databases in Testcontainers, Verify snapshots. CX Codex +1 2,800installs ef-core Entity Framework Core patterns for .NET 10. Helps agents configure DbContext, run bulk updates, manage migrations, and build fast DTO query projections. CX Codex +1 2,800installs error-handling Defines an error-handling strategy for .NET 10 with Result, ProblemDetails, validation, and a global exception handler. Use it when shaping API error contracts. CX Codex +1 2,800installs architecture-advisor Runs a questionnaire on domain, team and system lifetime, then recommends an architecture for your .NET project. Useful before you lay out folders. CX Codex +1 2,800installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor