Understanding HTTP Status Codes in REST APIs

Comprehensive guide to HTTP status codes and their appropriate usage in REST APIs

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:

  1. Clear communication: Status codes provide immediate feedback about what happened with the request
  2. Protocol compliance: Using standard HTTP status codes ensures compatibility with HTTP infrastructure
  3. Client error handling: Consistent status codes enable clients to implement reliable error handling
  4. Debugging: Accurate status codes simplify troubleshooting and problem diagnosis
  5. 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 CodeCategoryDescription
1xxInformationalThe request has been received and the process is continuing.
2xxSuccessThe request was successfully received, understood, and accepted.
3xxRedirectionFurther action needs to be taken to complete the request.
4xxClient ErrorThe request contains bad syntax or cannot be fulfilled by the server.
5xxServer ErrorThe 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 CodeNameDescriptionWhen to UseASP.NET Core Example
200OKStandard response for successful HTTP requestsWhen a request has succeeded and is returning datareturn Ok(product);
201CreatedRequest succeeded and a new resource was createdAfter successfully creating a new resourcereturn CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
202AcceptedRequest accepted for processing but not yet completedFor asynchronous operationsreturn Accepted(requestId);
204No ContentRequest succeeded but no content to returnAfter successful operations that don’t return content (e.g., DELETE)return NoContent();
206Partial ContentServer is delivering only part of the resourceWhen responding to range requests for large resourcesreturn StatusCode(206, partialContent);

3xx - Redirection

Status CodeNameDescriptionWhen to UseASP.NET Core Example
301Moved PermanentlyResource has permanently moved to a new URLWhen a resource location has changed permanentlyreturn RedirectPermanent("/new-location");
302FoundResource temporarily resides at a different URLWhen temporarily redirecting a clientreturn Redirect("/temporary-location");
303See OtherResponse is found at another URL using GETAfter a POST operation when directing to a result pagereturn RedirectToAction("GetResource", new { id = resourceId });
304Not ModifiedResource not modified since last requestedFor conditional GET requests to reduce bandwidthreturn StatusCode(304);
307Temporary RedirectResource temporarily moved; use same method for new requestFor temporary redirects preserving the HTTP methodreturn StatusCode(307, "/temporary-location");

4xx - Client Error

Status CodeNameDescriptionWhen to UseASP.NET Core Example
400Bad RequestServer cannot process the request due to client errorWhen request has invalid syntax or parametersreturn BadRequest("Invalid parameters");
401UnauthorizedAuthentication required and has failed or not providedWhen authentication is required but not providedreturn Unauthorized();
403ForbiddenRequest understood but server refuses to authorize itWhen a user is authenticated but lacks permissionsreturn Forbid();
404Not FoundRequested resource not foundWhen the requested resource doesn’t existreturn NotFound();
405Method Not AllowedRequest method not supported for the resourceWhen an HTTP method is not allowed for the endpointreturn StatusCode(405, "Method not allowed");
406Not AcceptableResource cannot generate content acceptable per Accept headersWhen content negotiation failsreturn StatusCode(406, "Cannot produce acceptable response");
409ConflictRequest conflicts with the current state of the resourceWhen there’s a conflict updating a resource (e.g., concurrent updates)return Conflict("Resource already exists");
415Unsupported Media TypeMedia format of the request not supportedWhen the request content type is not supportedreturn StatusCode(415, "Unsupported content type");
422Unprocessable EntityRequest well-formed but semantically incorrectWhen validation fails despite correct syntaxreturn UnprocessableEntity(validationErrors);
429Too Many RequestsUser has sent too many requests in a given timeFor rate limitingreturn StatusCode(429, "Too many requests");

5xx - Server Error

Status CodeNameDescriptionWhen to UseASP.NET Core Example
500Internal Server ErrorGeneric server errorWhen an unexpected error occursreturn StatusCode(500, "An error occurred");
501Not ImplementedServer doesn’t support functionality requiredFor unimplemented API featuresreturn StatusCode(501, "Functionality not implemented");
502Bad GatewayServer acting as gateway received invalid responseWhen an upstream service returns an invalid responsereturn StatusCode(502, "Invalid response from upstream service");
503Service UnavailableServer temporarily unavailableDuring maintenance or overloadreturn StatusCode(503, "Service temporarily unavailable");
504Gateway TimeoutGateway timeout waiting for upstream responseWhen an upstream service times outreturn 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 MethodCommon Success Status Codes
GET200 OK, 206 Partial Content, 304 Not Modified
POST201 Created, 202 Accepted
PUT200 OK, 204 No Content
PATCH200 OK, 204 No Content
DELETE204 No Content, 200 OK

4. Handling Asynchronous Operations

For long-running processes, use a pattern like:

  1. Return 202 Accepted with a resource URL for checking status
  2. Provide a status endpoint that returns 200 OK with progress information
  3. When complete, return either 303 See Other with the resource location or the completed resource with 200 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

  1. Using 200 OK for everything: Each response should use the most appropriate status code.

  2. Returning 404 Not Found for authorization failures: Use 401 Unauthorized for authentication issues and 403 Forbidden for permission issues.

  3. Returning 500 Internal Server Error for validation failures: Use 400 Bad Request or 422 Unprocessable Entity for validation issues.

  4. Using non-standard status codes: Stick to widely recognized HTTP status codes.

  5. 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