REST API - Which Http status codes to use for a booking API?

Viewed 739

I'm a little confused about which HTTP status codes to return for the API I am building. The API which allows clients to query a product inventory and make rental bookings.

The question is when to use 400s and when to use 200s in the following similar situations:

  1. The end user makes a valid booking request but the items they've requested are all booked up for the dates they've picked.
  2. The end user makes a valid booking request but the order dates are too soon. KEY INFO: we need at least two days notice to fulfil an order.
  3. The end user tries to call my {orderId}/confirm endpoint a day after first reserving it. KEY INFO: You have to confirm an order within 15 minutes of reservation or it will timeout.

The requests are correctly formed and the resources they refer to all exist, so can we really call it an error on the client's side? And if not then is it appropriate to return 400 codes?

Which HTTPStatus would you use in these cases? Something in the 200s or something in the 400s?

3 Answers

Which Http status codes to use for a booking API?

HTTP status codes belong to the transfer of documents over a network domain. So you use them exactly the same way every other server on the world wide web uses them.

The status-code element is a 3-digit integer code describing the result of the server's attempt to understand and satisfy the client's corresponding request. The rest of the response message is to be interpreted in light of the semantics defined for that status code.

The semantics that are specific to your domain don't belong in the HTTP headers, but instead in the message body of the response.

A useful heuristic in abiding the REST architectural constraints: how would you do it on the web? Typically, the information you intend for the human being - the client that understands the context of the domain - belongs in the HTML, which is to say the body of the HTTP response. The metadata (status codes, headers) provide semantic hints to general purpose components (browsers, caches, web-crawlers) that aren't interested in the contents of the documents.

The question is when to use 400s and when to use 200s in the following similar situations

It may help to consider the rules for cache-invalidation. The TL;DR being that if you send a error status code (4xx or 5xx), then the cache knows that the response body is a representation of an error, and previously cached copies of the resource should still be considered valid.

A 2xx code, on the other hand, reports that the request did successfully change the resource, and therefore the previously cached copies of the resource can be invalidated.

POST /orders/12345 HTTP/1.1
Content-Type: application/x-www-form-urlencoded

confirm=yes

If this message changes the /orders/12345 resource, then you should be biased toward returning a 2xx response, even if the change to the document doesn't reflect the "happy path" in your domain. On the other hand, if the resource doesn't change, then a 4xx code is more appropriate.

The status code registry documents where the semantics of each status code are currently defined. You could dig through each of the standards to understand the different meanings and implications of each.

But the short version is that there are two 4xx codes that stand out:

  • 403, which indicates that the server understood the request but declines to fulfill it.
  • 409, which indicates that the server cannot fulfill the request because of the current state of the resource.

I haven't found anything in the standards where these two codes are treated differently.

I think you can make an argument that 409 invites the client to fetch a new copy of the resource, merge the changes, and then resubmit -- in much the same way that 401 invites the client to resubmit the request with authorization credentials. But I've never heard of a general purpose component that does that. It's a bit of a reach, perhaps.

To be honest, REST is such a mess on the web that you will see both APIs that will return 200 with an application-layer error and APIs that return 400 in cases similar to your example. It is always good to check whether your choice impacts whether or not the response can be cached, but I assume you are POSTing to these endpoints anyway.

Personally, I would recommend the approach gRPC takes, i.e., using the same status for application and protocol errors, which feels quite consistent in most APIs. It might help to read through the error types there to get a feeling for what should be an error and what should be OK, but as a rule of thumb 200 OK should always mean "I have fulfilled your request".

Also note that the HTTP RFC says:

o 4xx (Client Error): The request contains bad syntax or cannot be fulfilled

o 5xx (Server Error): The server failed to fulfill an apparently valid request

The 500 space is generally interpreted as "this should have worked, but didn't because we messed up (technical problem)", so all of your examples should be 400s, if you return them as HTTP codes.

I am curious to see if someone will take the opposing view, but IMO it is slightly weird to use application errors, because you now have to do double error-checking and logic a la:

if (response.getHttpStatus() != HttpCode.OK) {
  handleHttpError();
} else if (response.getPayload().getStatus() != PayloadCode.OK) {
  handleApplicationError();
}
doWork(response.getPayload());

I could see it being justified though if the framework you're using uses HTTP errors in a weird way that would cause problems, because a framework 400 requires different treatment than an application 400.

Practically, (not idiomatically) speaking it breaks down to your preference. I would add the following questions to the equation:

  1. How would you branch out your logic on the client based on the "error" response from the server? Let's say you have a few scenarios:

    • Request dates for the specific location are booked up (as you mentioned)
    • Requested dates are available but very limited. So two users from different places try to book the same place simultaneously -> one gets an error.
    • All locations within the area are booked up ... (you can come up with many more)

    All of these need to have distinct error codes to display different error messages in the UI and for "tracking purposes" (Maybe you see that some location is very busy at a certain time, e.g. to show something like "books pretty quickly at this time"). It's important that you have your application-specific internal code within the response body so that UI can act accordingly.

  2. How would you track these errors from an observability standpoint? You can say 95% of all our requests result in a "200" response. What does it mean? I would read it as follows: "business logic" works correctly most of the time. 5% are unexpected bugs that needed to be fixed (somehow user-entered location with a non-ASCII symbol that broke your validation, etc.). On the contrary, if you see that only 50% of your responses are "200", it might bring confusion, even though, your business logic works correctly, right? What can you do to fix about user entering "unavailable" dates? However, this part of the observability can be tuned as well to correctly display the data you need even with using HTTP error codes for business logic.

As you might guess from the statements above, I prefer to use HTTP error codes for non-business logic. It also makes things clear, although a little bit counterintuitive at first due to the fact of using 200 for unsuccessful behavior. But ultimately, it's up to you to decide. Just make sure you have consistency with your error codes and a solid understanding of how you produce/treat them on the server/client sides.

Related