Understanding HTTP Status Codes in REST APIs
8 minute read
HTTP status codes are standardized response codes returned by a web server to indicate the outcome of an HTTP request. In RESTful APIs, these codes play a crucial role in communicating the result of API operations to clients in a consistent and predictable manner.
Choosing the appropriate status code is essential for several reasons:
- Clear communication: Status codes provide immediate feedback about what happened with the request
- Protocol compliance: Using standard HTTP status codes ensures compatibility with HTTP infrastructure
- Client error handling: Consistent status codes enable clients to implement reliable error handling
- Debugging: Accurate status codes simplify troubleshooting and problem diagnosis
- Caching behavior: Certain status codes influence how responses are cached
For example, a server can process a request synchronously and respond with 200 OK
, but it can also accept a request for asynchronous processing and respond with 202 Accepted
. Similarly, a newly created resource should return 201 Created
rather than a generic 200 OK
to accurately reflect what happened.
Status Code Categories
The first digit of the status code defines the class of response. HTTP status codes are grouped into five categories:
Status Code | Category | Description |
---|---|---|
1xx | Informational | The request has been received and the process is continuing. |
2xx | Success | The request was successfully received, understood, and accepted. |
3xx | Redirection | Further action needs to be taken to complete the request. |
4xx | Client Error | The request contains bad syntax or cannot be fulfilled by the server. |
5xx | Server Error | The server failed to fulfill an apparently valid request. |
Commonly Used Status Codes in REST APIs
Below are the most commonly used HTTP status codes in REST APIs, organized by category, with guidelines on when to use each one.
2xx - Success
Status Code | Name | Description | When to Use | ASP.NET Core Example |
---|---|---|---|---|
200 | OK | Standard response for successful HTTP requests | When a request has succeeded and is returning data | return Ok(product); |
201 | Created | Request succeeded and a new resource was created | After successfully creating a new resource | return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product); |
202 | Accepted | Request accepted for processing but not yet completed | For asynchronous operations | return Accepted(requestId); |
204 | No Content | Request succeeded but no content to return | After successful operations that don’t return content (e.g., DELETE) | return NoContent(); |
206 | Partial Content | Server is delivering only part of the resource | When responding to range requests for large resources | return StatusCode(206, partialContent); |
3xx - Redirection
Status Code | Name | Description | When to Use | ASP.NET Core Example |
---|---|---|---|---|
301 | Moved Permanently | Resource has permanently moved to a new URL | When a resource location has changed permanently | return RedirectPermanent("/new-location"); |
302 | Found | Resource temporarily resides at a different URL | When temporarily redirecting a client | return Redirect("/temporary-location"); |
303 | See Other | Response is found at another URL using GET | After a POST operation when directing to a result page | return RedirectToAction("GetResource", new { id = resourceId }); |
304 | Not Modified | Resource not modified since last requested | For conditional GET requests to reduce bandwidth | return StatusCode(304); |
307 | Temporary Redirect | Resource temporarily moved; use same method for new request | For temporary redirects preserving the HTTP method | return StatusCode(307, "/temporary-location"); |
4xx - Client Error
Status Code | Name | Description | When to Use | ASP.NET Core Example |
---|---|---|---|---|
400 | Bad Request | Server cannot process the request due to client error | When request has invalid syntax or parameters | return BadRequest("Invalid parameters"); |
401 | Unauthorized | Authentication required and has failed or not provided | When authentication is required but not provided | return Unauthorized(); |
403 | Forbidden | Request understood but server refuses to authorize it | When a user is authenticated but lacks permissions | return Forbid(); |
404 | Not Found | Requested resource not found | When the requested resource doesn’t exist | return NotFound(); |
405 | Method Not Allowed | Request method not supported for the resource | When an HTTP method is not allowed for the endpoint | return StatusCode(405, "Method not allowed"); |
406 | Not Acceptable | Resource cannot generate content acceptable per Accept headers | When content negotiation fails | return StatusCode(406, "Cannot produce acceptable response"); |
409 | Conflict | Request conflicts with the current state of the resource | When there’s a conflict updating a resource (e.g., concurrent updates) | return Conflict("Resource already exists"); |
415 | Unsupported Media Type | Media format of the request not supported | When the request content type is not supported | return StatusCode(415, "Unsupported content type"); |
422 | Unprocessable Entity | Request well-formed but semantically incorrect | When validation fails despite correct syntax | return UnprocessableEntity(validationErrors); |
429 | Too Many Requests | User has sent too many requests in a given time | For rate limiting | return StatusCode(429, "Too many requests"); |
5xx - Server Error
Status Code | Name | Description | When to Use | ASP.NET Core Example |
---|---|---|---|---|
500 | Internal Server Error | Generic server error | When an unexpected error occurs | return StatusCode(500, "An error occurred"); |
501 | Not Implemented | Server doesn’t support functionality required | For unimplemented API features | return StatusCode(501, "Functionality not implemented"); |
502 | Bad Gateway | Server acting as gateway received invalid response | When an upstream service returns an invalid response | return StatusCode(502, "Invalid response from upstream service"); |
503 | Service Unavailable | Server temporarily unavailable | During maintenance or overload | return StatusCode(503, "Service temporarily unavailable"); |
504 | Gateway Timeout | Gateway timeout waiting for upstream response | When an upstream service times out | return StatusCode(504, "Upstream service timeout"); |
Implementation Best Practices
1. Be Specific with Status Codes
Use the most specific status code that applies to the situation. For example, use 404 Not Found
for missing resources rather than a generic 400 Bad Request
.
2. Consistent Error Responses
Return consistent JSON error bodies with your status codes:
{
"status": 400,
"title": "Bad Request",
"detail": "The product name must be between 3 and 50 characters",
"type": "https://example.com/errors/validation",
"instance": "/products/create",
"errors": {
"name": ["The field Name must be between 3 and 50 characters."]
}
}
ASP.NET Core Implementation:
public class ErrorResponse
{
public int Status { get; set; }
public string Title { get; set; }
public string Detail { get; set; }
public string Type { get; set; }
public string Instance { get; set; }
public Dictionary<string, string[]> Errors { get; set; }
}
// In a controller action
[HttpPost]
public IActionResult CreateProduct(ProductModel model)
{
if (!ModelState.IsValid)
{
var errorResponse = new ErrorResponse
{
Status = 400,
Title = "Bad Request",
Detail = "Validation failed",
Instance = HttpContext.Request.Path,
Errors = ModelState.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
)
};
return BadRequest(errorResponse);
}
// Create product logic...
}
3. HTTP Method and Status Code Relationships
Different HTTP methods have appropriate status codes for successful operations:
HTTP Method | Common Success Status Codes |
---|---|
GET | 200 OK, 206 Partial Content, 304 Not Modified |
POST | 201 Created, 202 Accepted |
PUT | 200 OK, 204 No Content |
PATCH | 200 OK, 204 No Content |
DELETE | 204 No Content, 200 OK |
4. Handling Asynchronous Operations
For long-running processes, use a pattern like:
- Return
202 Accepted
with a resource URL for checking status - Provide a status endpoint that returns
200 OK
with progress information - When complete, return either
303 See Other
with the resource location or the completed resource with200 OK
[HttpPost("long-running")]
public IActionResult StartLongRunningOperation(OperationRequest request)
{
string operationId = _operationService.StartOperation(request);
// Return 202 Accepted with a URL to check the status
Response.Headers.Add("Location", $"/api/operations/{operationId}");
return Accepted(new { id = operationId, status = "Processing" });
}
[HttpGet("operations/{id}")]
public IActionResult GetOperationStatus(string id)
{
OperationStatus status = _operationService.GetStatus(id);
if (status.IsComplete)
{
// Option 1: Redirect to the created resource
return RedirectToAction("GetResource", new { id = status.ResourceId });
// Option 2: Return the status with completion info
// return Ok(new { id = status.ResourceId, status = "Complete" });
}
return Ok(new {
id = id,
status = "Processing",
percentComplete = status.PercentComplete
});
}
5. Pagination Headers
Include pagination information in HTTP headers:
[HttpGet]
public IActionResult GetItems([FromQuery] PaginationParameters parameters)
{
var (items, totalCount, pageCount) = _itemService.GetPaginatedItems(parameters);
Response.Headers.Add("X-Total-Count", totalCount.ToString());
Response.Headers.Add("X-Page-Count", pageCount.ToString());
// Create links for HATEOAS
var links = new List<LinkDto>();
// Add first, prev, next, last links as appropriate
Response.Headers.Add("Link", string.Join(", ", links));
return Ok(items);
}
Common Anti-Patterns to Avoid
Using 200 OK for everything: Each response should use the most appropriate status code.
Returning 404 Not Found for authorization failures: Use
401 Unauthorized
for authentication issues and403 Forbidden
for permission issues.Returning 500 Internal Server Error for validation failures: Use
400 Bad Request
or422 Unprocessable Entity
for validation issues.Using non-standard status codes: Stick to widely recognized HTTP status codes.
Not including adequate error details: Status codes alone are often insufficient; include useful error messages.
Status Code Selection Flow Chart
Request Received
├─► Authentication Problem? → 401 Unauthorized
│ ↓
├─► Authorization Problem? → 403 Forbidden
│ ↓
├─► Resource Not Found? → 404 Not Found
│ ↓
├─► Validation Failed? → 400 Bad Request or 422 Unprocessable Entity
│ ↓
├─► Request Rate Exceeded? → 429 Too Many Requests
│ ↓
├─► Resource Conflict? → 409 Conflict
│ ↓
├─► Success with No Content? → 204 No Content
│ ↓
├─► Resource Created? → 201 Created
│ ↓
├─► Asynchronous Operation? → 202 Accepted
│ ↓
└─► Success with Content → 200 OK
Resources
- IETF RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
- HTTP Status Codes Specification
- Microsoft REST API Guidelines - Microsoft’s recommendations for REST API design