Reliable APIs start at the boundaries.
Clear contracts, useful errors, and a few deliberate choices that make services easier to work with.
An API boundary is where your assumptions meet someone else's code. Making that boundary explicit reduces surprises on both sides.
Validate before doing work
Validate the shape of an incoming request before passing it into the application. Keep transport concerns separate from business rules: a missing field and an unavailable product are different kinds of problems.
public record CreateNoteRequest(string Title, string Body);
app.MapPost("/notes", (CreateNoteRequest request) =>
{
if (string.IsNullOrWhiteSpace(request.Title))
{
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["title"] = ["A title is required."]
});
}
// Demonstration only: persist the note in a real application.
return Results.Ok(new { title = request.Title.Trim() });
});
Make failures understandable
A useful error tells a caller what went wrong and what they can do next. Use consistent status codes and a stable error shape. Keep stack traces and internal details in your logs.
Design for retries
Networks fail. Clients retry. For operations with side effects, decide how to recognize a repeated request before the first duplicate arrives.
The implementation depends on your system, but the question is universal: can this operation safely happen twice?
Thanks for reading.
More notes from the build ↗