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.
On this page · 6 min · Beginner
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Beginner
- Updated
- Oct 28, 2026
Related tutorials
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#
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#
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
}
}
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 → 204This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- Validation failures return 400 with a short message; Web API wraps the string as {"Message": ...}.
- An id that does not exist returns 404 with no body.
- A business conflict (no stock) returns 409 with a JSON body explaining why.
- A successful create returns 201 and a Location header built from the route named GetOrder.
- 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#
Clients then need custom parsing to detect errors, and monitoring tools cannot see failures.
401 = who are you? (not authenticated). 403 = I know who you are, and the answer is no.
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?
Show solution
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#
A logged-in user tries to delete another user’s order. Which code fits?
Want more? Practise all ASP.NET Web API MCQs · ASP.NET Web API interview questions
My notes#
Saved in this browser.