ASP.NET Web API (.NET Framework) · REST Foundations

HTTP Status Codes

The status code is the first thing a client reads. A correct code lets the front end react without parsing the body: show the data, show a validation message, or redirect to login.

Beginner 6 min Lesson 4 of 36Covers ASP.NET Web API 2.2 on .NET Framework 4.8.
Before you start:HTTP Methods
0/36 in ASP.NET Web API
On this page · 6 min · Beginner

Learning objectives#

  • Know the five status code families
  • Use the common codes correctly
  • Return them with IHttpActionResult helpers
  • Return a custom code with a body

Explanation#

1xx informational, 2xx success, 3xx redirection, 4xx client error (the request was wrong), 5xx server error (the server failed).

Success: 200 OK (data returned), 201 Created (new resource, with a Location header), 204 No Content (success, nothing to return).

Client errors: 400 Bad Request (invalid input), 401 Unauthorized (not logged in), 403 Forbidden (logged in but not allowed), 404 Not Found, 409 Conflict (clashes with current state, e.g. duplicate email or out of stock).

500 Internal Server Error means an unhandled exception. Clients cannot fix it; you must. Never return details of the exception to the client.

ApiController has helpers: Ok(), Created(), CreatedAtRoute(), BadRequest(), NotFound(), Conflict(), Unauthorized(), InternalServerError(), StatusCode(HttpStatusCode.X) and Content(HttpStatusCode.X, body).

Syntax#

C# Illustrative
return Ok(data);                                          // 200
return CreatedAtRoute("GetOrder", new { id }, order);     // 201
return StatusCode(HttpStatusCode.NoContent);              // 204
return BadRequest("Quantity must be at least 1.");        // 400
return NotFound();                                        // 404
return Content(HttpStatusCode.Conflict, new { message }); // 409 with a body

Code example: An orders controller that answers precisely#

C#OrdersController.cs Illustrative
using System.Net;
using System.Web.Http;


public class OrderRequest
{
    public int ProductId { get; set; }
    public int Quantity { get; set; }
}


[RoutePrefix("api/orders")]
public class OrdersController : ApiController
{
    [HttpGet, Route("{id:int}", Name = "GetOrder")]
    public IHttpActionResult Get(int id)
    {
        if (id <= 0) return BadRequest("Order id must be positive.");     // 400
        if (id > 5000) return NotFound();                                   // 404
        return Ok(new { Id = id, Status = "Shipped" });                     // 200
    }


    [HttpPost, Route("")]
    public IHttpActionResult Create(OrderRequest request)
    {
        if (request == null || request.Quantity <= 0)
            return BadRequest("Quantity must be at least 1.");              // 400


        if (request.ProductId == 99)
            return Content(HttpStatusCode.Conflict,
                new { message = "Product 99 is out of stock." });           // 409


        var id = 1001;
        return CreatedAtRoute("GetOrder", new { id },
            new { Id = id, request.ProductId, request.Quantity });          // 201
    }


    [HttpDelete, Route("{id:int}")]
    public IHttpActionResult Cancel(int id)
    {
        return StatusCode(HttpStatusCode.NoContent);                        // 204
    }
}
Expected output
GET    /api/orders/0                         → 400 {"Message":"Order id must be positive."}
GET    /api/orders/9999                      → 404
POST   /api/orders {"ProductId":99,"Quantity":1} → 409 {"message":"Product 99 is out of stock."}
POST   /api/orders {"ProductId":5,"Quantity":2}  → 201 Location: /api/orders/1001
DELETE /api/orders/1001                      → 204

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. Validation failures return 400 with a short message; Web API wraps the string as {"Message": ...}.
  2. An id that does not exist returns 404 with no body.
  3. A business conflict (no stock) returns 409 with a JSON body explaining why.
  4. A successful create returns 201 and a Location header built from the route named GetOrder.
  5. A successful delete returns 204: nothing more to say.

Real-world use case#

An Angular front end uses an HTTP interceptor: 401 → redirect to login, 403 → “you do not have access” page, 400 → show field errors, 5xx → a generic “try again” toast. This only works because the API returns honest status codes.

Common mistakes#

Everything is 200

Clients then need custom parsing to detect errors, and monitoring tools cannot see failures.

Mixing up 401 and 403

401 = who are you? (not authenticated). 403 = I know who you are, and the answer is no.

Returning 500 for bad input

If the client sent something wrong, it is a 4xx. 500 should mean a bug or outage on your side.

Best practices#

  • Return 201 + Location for creates
  • Use 409 for business-rule conflicts
  • Keep error bodies small and consistent
  • Never expose stack traces in responses

Remember this#

2xx = it worked. 4xx = you made a mistake. 5xx = we made a mistake.

401 = not logged in. 403 = not allowed.

201 Created, 204 No Content, 409 Conflict.

Practice exercise#

Your task: Write a RegisterController.Post(RegisterRequest req) that returns 400 if Email is empty, 409 if the email is "taken@example.com", and 201 otherwise.

Need a hint?
Use BadRequest, Content(HttpStatusCode.Conflict, ...) and Created.
Show solution
C# Illustrative
using System.Net;
using System.Web.Http;


public class RegisterRequest { public string Email { get; set; } }


public class RegisterController : ApiController
{
    [HttpPost, Route("api/register")]
    public IHttpActionResult Post(RegisterRequest req)
    {
        if (req == null || string.IsNullOrWhiteSpace(req.Email))
            return BadRequest("Email is required.");
        if (req.Email == "taken@example.com")
            return Content(HttpStatusCode.Conflict, new { message = "Email already registered." });
        return Created("api/users/42", new { Id = 42, req.Email });
    }
}

Mini quiz#

HTTP Status Codes quizMultiple choice Question 1 of 3

A logged-in user tries to delete another user’s order. Which code fits?

Score so far: 0/0

Want more? Practise all ASP.NET Web API MCQs · ASP.NET Web API interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.