Status Codes for REST APIs

When it comes to Status Codes for REST APIs, we are all in a hurry to deliver functionality and we often ignore them. Come as it may. We use what we get. And that's it.

REST as a concept should also be about meaning, longevity and empathy towards our API consumers. We should be able to look at a request/response in isolation and understand what is wrong or what happened.

Choosing Status Codes for REST APIs sometimes is not easy, but at least we should give this a thought.

In this article, I will talk about Status Codes for REST APIs: the most common and the ignored ones, and a few usage scenarios.

We all know that we have 5 categories when it comes to status codes. And depending on the response category we should be able to tell what happened with that request. Was the request bad? There is an error processing it?

Status Codes - Categories #

  • 1xx - Informational
  • 2xx - Success
  • 3xx - Redirect
  • 4xx - Client Error
  • 5xx - Server error

I've seen APIs where the status code was 200 OK, but the response body looked like this:

In this case, me or my app, as an API consumer...what should I understand? Was my request successfully processed, or.... am I unauthorized? #confusing. So don't do this in your APIs.

2xx - Sucess status codes #

These are success status codes because they start with 2, but in particular, that code should express exactly what happened. You shouldn't return 200 OK for all the scenarios and then write an error in the body. (if you want to add meaning and have a well-rounded API.)

200 OK
When the response what processed with success, and usually has a response body.

201 Created - As a response for a POST request that has the effect of creating a new resource. This should be accompanied by a Location header indicating the location for the newly created resource

202 Accepted - For scenarios where there is no need for a response body. e.g for queuing something for later processing.
Also, this works very fine with the Location header, for polling scenarios, where you call the endpoint from time to time to see the status of a resource processing
204 No Content - usually as a response for a DELETE request

4xx - Client errors #

400 - Bad Request
Bad Request is in my opinion, one of the most miss-used status codes. I've seen it returned when due to some business logic the request couldn't be processed, but the request was well formed.
Think about it - when the request is well-formed, it has the parameters, the body and everything all good, but is a business rule on the backend that prevents that request from being processed....is not a Bad Request. Is anything but a Bad Request.
When you see a Bad Request, the tendency is to look at the content of the request to see what is wrong, fix that, and issue the request again.

Dummy Scenario: Online Store. You can't create 2 products with the same name and brand.
401 - Unauthorized, 403 - Forbidden
404 - Not Found - resource is not there, or maybe it was deleted.
406 - Not acceptable - when the server can't return a response that matches the Accept header of the request. To force .NET to take the Accept header into consideration you will need to set a flag to true:

 services.AddControllers(setupAction =>
 {
    setupAction.ReturnHttpNotAcceptable = true;
  })

409 - Conflict - When there is a business rule that prevents you from creating that resource as a result of a POST. Another scenario can be with using PUT to update a specific resource, and due to its state, you can't.
For this, the RFCs mention the fact the PUT requests might have versions and updating one might give you conflict.

410 - Gone - used to mark something as not available anymore; like promotions...or out of stocks..maybe... or simply to show a soft delete
415 - Unsupported Media-Type; When the server can't understand the body of the request, due to a wrong Content-Type header, or limited capabilities on the server.
For example, if you try to send an XML in the request but the server doesn't know how to read XML, you should get this status code.
422 - Unprocessable Entity - the server understands the request, the request is well-formed but due to some rules, it can't be processed. It can be used instead of 409 - Conflict and also covers a lot of ground. You can configure this in Startup.cs by leveraging ApiBehaviourOptions like this:

           services.AddControllers()
                .ConfigureApiBehaviorOptions(setupAction =>
            {
                setupAction.InvalidModelStateResponseFactory = context =>
                {
                    var problemDetails = new ValidationProblemDetails(context.ModelState)
                    {
                        Type = "https://yourApi.com/modelvalidationproblem",
                        Title = "One or more model validation errors occurred.",
                        Status = StatusCodes.Status422UnprocessableEntity,
                        Detail = "See the errors property for details.",
                        Instance = context.HttpContext.Request.Path
                    };

                    problemDetails.Extensions.Add("traceId", context.HttpContext.TraceIdentifier);

                    return new UnprocessableEntityObjectResult(problemDetails)
                    {
                        ContentTypes = { "application/problem+json" }
                    };
                };
            });

This way, you can hook yourself to the ModelState and treat validation errors as 422 status codes instead of Bad requests if you need that. You can find more examples in the docs. As a result, ASP.NET higher than 2.1 generates RFC-7807 compliant responses.

As a side note, if you configure ApiBehaviour to return BadRequest for model validation, you won't need to scatter all over your controllers these lines of code:

if (ModelState.IsValid) 
 {
     return BadRequest(ModelState);
 }

Conclusion #

Even though the most common status codes that might fit a lot of scenarios, we should bear in mind that there are around 63 of them!! Why? For us to use!
If we don't use "one size fits all" in terms of the technology we choose, why do we apply this on status codes?
If don't think about versioning, or headers, or HATEOAS in our APIs, at least we should have the right status codes.(and verbs, and naming :) )