REST API – Should HTTP Status Codes Represent Business Logic Errors?

apirestweb

I'm at a bit of a crossroads with some API design for a client (JS in a browser) to talk to a server. We use HTTP 409 Conflict to represent the failing of an action because of a safety lock in effect. The satefy lock prevents devs from accidentally making changes in our customers' production systems. I've been tasked with handling 409s a bit more gracefully on the client to indicate why a particular API call failed.

My solution was to wrap the failure handlers of any of our AJAX calls which will display a notification on the client when something fails due to 409 – this is all fine and works well alongside other 4XX and 5XX errors which use the same mechanism.

A problem has arisen where one of our route handlers responds with 409s when encountering a business logic error – my AJAX wrapper reports that the safety lock is on, whilst the client's existing failure handler reports what (it thinks) the problem is based on the body of the response. A simple solution would be to change either the handler's response or the status code we use to represent the safety lock.

Which brings me to my crossroad: should HTTP status codes even be used to represent business logic errors? This question addresses the same issue I am facing but it did not gain much traction. As suggested in the linked answer, I'm leaning towards using HTTP 200 OK with an appropriate body to represent failure within the business logic.

Does anyone have any strong opinions here? Is anyone able to convince me this is the wrong way to represent failure?

Best Answer

Kasey covers the main point.

The key idea in any web api: you are adapting your domain to look like a document store. GET/PUT/POST/DELETE and so on are all ways of interacting with the document store.

So a way of thinking about what codes to use, is to understand what the analogous operation is in a document store, and what this failure would look like in that analog.

2xx is completely unsuitable

The 2xx (Successful) class of status code indicates that the client's request was successfully received, understood, and accepted.

5xx is also unsuitable

The 5xx (Server Error) class of status code indicates that the server is aware that it has erred

In this case, the server didn't make a mistake; it's aware that you aren't supposed to modify that resource that way at this time.

Business logic errors (meaning that the business invariant doesn't allow the proposed edit at this time) are probably a 409

The 409 (Conflict) status code indicates that the request could not be completed due to a conflict with the current state of the target resource. This code is used in situations where the user might be able to resolve the conflict and resubmit the request. The server SHOULD generate a payload that includes enough information for a user to recognize the source of the conflict.

Note this last bit -- the payload of the 409 response should be communicating information to the consumer about what has gone wrong, and ideally includes hypermedia controls that lead the consumer to the resources that can help to resolve the conflict.

My solution was to wrap the failure handlers of any of our AJAX calls which will display a notification on the client when something fails due to 409 - this is all fine and works well alongside other 4XX and 5XX errors which use the same mechanism.

And I would point to this as the problem; your implementation at the client assumed that the status code was sufficient to define the problem. Instead, your client code should be reviewing the payload, and acting on the information available there.

That is, after all, how a document store would do it

409  Conflict

your proposed change has been declined because ${REASON}.  
The following resolution protocols are available: ${LINKS[@]})

The same approach with a 400 Bad Request would also be acceptable; which roughly" translates to "There was a problem with your request. We can't be bothered to figure out which status code is the best fit, so here you go. See the payload for details."

I would use 422. Input is valid so 400 is not the right error code to use

The WebDAV specification includes this recommendation

The 422 (Unprocessable Entity) status code means the server understands the content type of the request entity (hence a 415(Unsupported Media Type) status code is inappropriate), and the syntax of the request entity is correct (thus a 400 (Bad Request) status code is inappropriate) but was unable to process the contained instructions. For example, this error condition may occur if an XML request body contains well-formed (i.e., syntactically correct), but semantically erroneous, XML instructions.

I don't believe that's quite a match (although I agree that it sheds some doubt on 400 as an alternative). My interpretation is that 422 means "you've sent the wrong entity" where 409 is "you've sent the entity at the wrong time".

Put another way, 422 indicates an issue with the request message considered in isolation, where 409 indicates that the request message conflicts with the current state of the resource.

Ben Nadal's discussion of 422 may be useful to consider.