Every HTTP response has a three-digit status code that tells the client how its request turned out. A response may also have headers and a body. Choose the code to match the result: API clients use it to understand what happened, and browsers can act on it—for example, by following a redirect. section 1.4.1 section 4.2.2 section 5.6
The first digit places a code in one of five classes:
- 1xx: information or acknowledgment. section 4.2.2
- 2xx: success. Common choices are 200 OK, 201 Created for a newly created entity, and 204 No Content when there is no response body. section 4.2.2 section 5.3.1
- 3xx: redirection. A browser can follow a new link automatically. section 4.2.2
- 4xx: a problem with the request. Use 400 Bad Request for an invalid request or 404 Not Found when the requested resource cannot be found. section 4.2.2 section 5.3.1
- 5xx: a server problem caused the request to fail. section 4.2.2
Choose a result for a minimal API endpoint
For GET /fruit/{id}, return TypedResults.Ok(fruit) when the fruit exists and Results.NotFound() when it does not. For a successful POST, TypedResults.Created(url, fruit) returns 201 Created with a Location header identifying the new entity. Results and TypedResults create common responses; endpoint middleware executes their IResult to produce the response. section 5.3.1
Sometimes the pipeline chooses the code for you. If no middleware generates a response, ASP.NET Core returns 404; a controller action returning data directly produces 200 OK. For custom minimal-API requirements, an endpoint can set HttpResponse.StatusCode and write the body manually. section 4.2.2 section 20.1 section 5.6
Listing 5.3 Using Results and TypedResults in a minimal API
using System.Collections.Concurrent; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); WebApplication app = builder.Build(); var _fruit = new ConcurrentDictionary<string, Fruit>(); #1 app.MapGet("/fruit", () => _fruit); app.MapGet("/fruit/{id}", (string id) => _fruit.TryGetValue(id, out var fruit) #2 ? TypedResults.Ok(fruit) #3 : Results.NotFound()); #4 app.MapPost("/fruit/{id}", (string id, Fruit fruit) => _fruit.TryAdd(id, fruit) #5 ? TypedResults.Created($"/fruit/{id}", fruit) #6 : Results.BadRequest(new #7 { id = "A fruit with this id already exists" })); #7 app.MapPut("/fruit/{id}", (string id, Fruit fruit) => { _fruit[id] = fruit; return Results.NoContent(); #8 }); app.MapDelete("/fruit/{id}", (string id) => { _fruit.TryRemove(id, out _); #9 return Results.NoContent(); #9 }); app.Run(); record Fruit(string Name, int stock); #1 Uses a concurrent dictionary to make the API thread-safe #2 Tries to get the fruit from the dictionary. If the ID exists in the dictionary, this returns true . . . #3 . . . and we return a 200 OK response, serializing the fruit in the body as JSON. #4 If the ID doesn’t exist, returns a 404 Not Found response #5 Tries to add the fruit to the dictionary. If the ID hasn’t been added yet. this returns true . . . #6 . . . and we return a 201 response with a JSON body and set the Location header to the given path. #7 If the ID already exists, returns a 400 Bad Request response with an error message #8 After adding or replacing the fruit, returns a 204 No Content response #9 After deleting the fruit, always returns a 204 No Content response
Compare the GET and POST branches to see how each outcome selects a different response code. section 5.3.1










