Updated October 2026 for .NET 10 (LTS) and ASP.NET Core 10, with controller and minimal API examples.
Returning the right HTTP status code tells API clients exactly what happened: success, a bad request, a missing resource or a server failure. ASP.NET Core has a helper method for every common status code, and built-in support for standard error responses (Problem Details). This guide shows both controllers and minimal APIs.
Quick reference
| Status | Meaning | Controller | Minimal API |
|---|---|---|---|
| 200 OK | Success with a body | Ok(item) | TypedResults.Ok(item) |
| 201 Created | New resource created | CreatedAtAction(...) | TypedResults.Created(uri, item) |
| 204 No Content | Success, no body | NoContent() | TypedResults.NoContent() |
| 400 Bad Request | Invalid input | BadRequest() / ValidationProblem() | TypedResults.ValidationProblem(errors) |
| 401 Unauthorized | Not signed in | Unauthorized() | TypedResults.Unauthorized() |
| 403 Forbidden | Signed in, not allowed | Forbid() | TypedResults.Forbid() |
| 404 Not Found | Resource does not exist | NotFound() | TypedResults.NotFound() |
| 409 Conflict | Clashes with current state | Conflict() | TypedResults.Conflict() |
| 500 / 503 | Server-side failure | Problem(...) | TypedResults.Problem(...) |
For any other code, use StatusCode(StatusCodes.Status418ImATeapot) in a controller or TypedResults.StatusCode(...) in a minimal API. The StatusCodes class has a named constant for every standard code.
Controllers: return ActionResult<T>
[ApiController]
[Route("api/[controller]")]
public class BooksController(IBookService books) : ControllerBase
{
[HttpGet("{id}")]
[ProducesResponseType<Book>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Book>> Get(string id)
{
var book = await books.GetAsync(id);
return book is null ? NotFound() : Ok(book);
}
[HttpPost]
[ProducesResponseType<Book>(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult<Book>> Create(Book book)
{
if (await books.ExistsAsync(book.Id))
{
return Conflict();
}
await books.AddAsync(book);
return CreatedAtAction(nameof(Get), new { id = book.Id }, book); // 201 + Location header
}
[HttpDelete("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(string id) =>
await books.DeleteAsync(id) ? NoContent() : NotFound();
}
With [ApiController], invalid model input returns 400 automatically, and error codes such as 404 get a standard Problem Details body. The ProducesResponseType attributes document each code in your OpenAPI/Swagger output.
Minimal APIs: use TypedResults
In minimal APIs, return TypedResults and declare the possible results with Results<...>. The compiler checks your code, and OpenAPI picks up every status code automatically:
using Microsoft.AspNetCore.Http.HttpResults;
app.MapGet("/books/{id}", async Task<Results<Ok<Book>, NotFound>> (string id, IBookService books) =>
await books.GetAsync(id) is { } book
? TypedResults.Ok(book)
: TypedResults.NotFound());
app.MapPost("/books", async Task<Results<Created<Book>, Conflict>> (Book book, IBookService books) =>
{
if (await books.ExistsAsync(book.Id))
{
return TypedResults.Conflict();
}
await books.AddAsync(book);
return TypedResults.Created($"/books/{book.Id}", book);
});
Errors: return Problem Details, not plain text
Problem Details is the standard JSON format for HTTP API errors (RFC 9457, which replaced RFC 7807). Clients get a consistent body with type, title, status, detail and instance, served as application/problem+json. Return one directly:
return Problem(
title: "Payment service unavailable",
detail: "Please try again in a few minutes.",
statusCode: StatusCodes.Status503ServiceUnavailable);
Handle unexpected exceptions in one place
Do not wrap every action in try/catch just to return 500. Register a global exception handler once, and every unhandled exception becomes a clean Problem Details response:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
var app = builder.Build();
app.UseExceptionHandler(); // unhandled exceptions -> Problem Details
app.UseStatusCodePages(); // empty error responses -> Problem Details
app.MapControllers();
app.Run();
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
public sealed class GlobalExceptionHandler(
IProblemDetailsService problemDetails,
ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
logger.LogError(exception, "Unhandled exception");
var status = exception switch
{
ArgumentException => StatusCodes.Status400BadRequest,
KeyNotFoundException => StatusCodes.Status404NotFound,
_ => StatusCodes.Status500InternalServerError
};
httpContext.Response.StatusCode = status;
return await problemDetails.TryWriteAsync(new ProblemDetailsContext
{
HttpContext = httpContext,
Exception = exception,
ProblemDetails = new ProblemDetails
{
Status = status,
Title = status == 500 ? "An unexpected error occurred" : exception.Message
}
});
}
}
Never send stack traces or internal messages to clients for 500 errors. Log them on the server, as above.
FAQ
200 or 204 for a successful update?
Return 200 with the updated resource if clients need it, or 204 if there is nothing to send back. Both are correct; be consistent across your API.
401 or 403?
401 means “we don’t know who you are” (missing or invalid credentials). 403 means “we know who you are, but you are not allowed to do this”.
Related
- Using Problem Details in ASP.NET Core
- REST API URL naming conventions
- API versioning in ASP.NET Core
- Free tool: generate C# request/response classes from JSON
Sources: Microsoft Learn: Controller action return types · Microsoft Learn: Minimal API responses · Microsoft Learn: Handle errors in ASP.NET Core · RFC 9457 · RFC 9110

Awesome. Very helpful
Thanks Ashu for your feedback.