Error Responses
Response<TBody> is designed for the success path. When an endpoint needs to return different response shapes depending on the outcome — for example, a UserResponse on success and a ProblemDetails on failure — change the return type to IResult and declare each response shape explicitly via ProducesSuccess and ProducesError.
These methods exist solely to register OpenAPI response metadata. The actual response is constructed in HandleAsync using Results.* helpers. The framework cannot verify that the declared types match what HandleAsync actually returns, so keeping declarations and implementation in sync is the caller’s responsibility.
ProblemDetails
Section titled “ProblemDetails”ProducesError(HttpStatusCode) registers a ProblemDetails response for the given status code. This is the default choice for standard HTTP error responses.
public class FindByIdEndpoint : IEndpoint<FindByIdRequest, IResult>{ public void Configure(IEndpointConfiguration config) { config .Get("/{id}") .Group<UsersEndpointGroup>() .ProducesSuccess<UserResponse>() .ProducesError(HttpStatusCode.NotFound); }
public Task<IResult> HandleAsync(FindByIdRequest request, CancellationToken cancel) { if (request.Id != 1) { return Task.FromResult( Results.Problem( statusCode: (int)HttpStatusCode.NotFound, title: "User not found", detail: $"No user with ID {request.Id} exists." ) ); }
return Task.FromResult(Results.Json(new UserResponse { Id = 1, Name = "Alice", ... })); }}Multiple ProducesError calls can be chained to declare several error shapes:
config .Post("/") .ProducesSuccess<OrderResponse>(HttpStatusCode.Created) .ProducesError(HttpStatusCode.NotFound) .ProducesError(HttpStatusCode.Conflict);Custom error type
Section titled “Custom error type”ProducesError<TError>(HttpStatusCode) registers an arbitrary type as the error body. Use this when the error response carries domain-specific fields beyond what ProblemDetails provides.
public record ValidationErrorResponse(IReadOnlyList<string> Errors);
public class CreateOrderEndpoint : IEndpoint<CreateOrderRequest, IResult>{ public void Configure(IEndpointConfiguration config) { config .Post("/orders") .ProducesSuccess<OrderResponse>(HttpStatusCode.Created) .ProducesError<ValidationErrorResponse>(HttpStatusCode.UnprocessableEntity); }
public Task<IResult> HandleAsync(CreateOrderRequest request, CancellationToken cancel) { var errors = Validate(request); if (errors.Count > 0) { return Task.FromResult( Results.Json( new ValidationErrorResponse(errors), statusCode: (int)HttpStatusCode.UnprocessableEntity ) ); }
var order = CreateOrder(request); return Task.FromResult(Results.Json(order, statusCode: (int)HttpStatusCode.Created)); }}ProducesSuccess, ProducesError, and ProducesError<TError> can be mixed freely in the same Configure call.
Each of these methods accepts an optional contentType parameter, which defaults to application/json.
Set it explicitly when HandleAsync returns a non-JSON IResult, such as CsvResponse<TRow> from the
CSV Helper extension, which should be declared with contentType: "text/csv".
For details on the available response types, see the Response Types guide.