Skip to content

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.

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);

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.

For details on the available response types, see the Response Types guide.