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?

  • 3
    I hesitate to post my simple opinion as an answer. In short: I generally avoid using HTTP status codes to represent business errors. They should pertain only to the status of the communication between client and server. A suited status code for returning validation or business logic errors would be 400 Bad Request. The reason for this separation is because future systems, devs or readers of documents may be confused by your deviation of the world-wide standard. – Ivo Coumans Feb 7 '17 at 14:18
  • @IvoCoumans: please make this ^ an answer so I could upvote it. 400 Bad Request as a blanket HTTP code seems best to cover business logic errors as a class. – 9000 Feb 7 '17 at 17:58
  • Personal preference, but I tend to use 400 Bad Request when data is missing or can't be read/parsed. I.e. the request data itself is bad in some way. – Kasey Speakman Feb 7 '17 at 22:02
  • Please expand on what you mean by "business logic error." That's extremely vague. Do you mean input that's invalid accord to the (correct) data validation checks, input that might be valid at some other point in time but that is invalid in the current state, or a bug in the business logic where it's rejecting a valid input? – jpmc26 Sep 25 '17 at 22:31
  • @jpmc26 I was being intentionally vague as it was a general question. The servers coped with the request just fine but decided that the query / assertion didn't make sense. – Joe Shanahan Sep 26 '17 at 10:01

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.

  • 8
    "you are adapting your domain to look like a document store" -- I'd love to see people read this, and fully understand all the conseqences and implications before they decide for (or against) REST. Really. – JensG Feb 8 '17 at 17:37
  • "Business logic errors" sounds like "invalid input," to me, which is usually generally a straight 400, as there isn't a more specific error code for it. If it's not the input that's invalid, then the server has a bug and 500 is appropriate. Your answer seems to assume too much about the nature of a "business logic error." – jpmc26 Sep 25 '17 at 22:28
  • I would use 422. Input is valid so 400 is not the right error code to use – Konrad Apr 29 '19 at 9:14
  • 409 is for HTTP protocol, like if you PUT but ETag is outdated. 422 is a better choice as it doesn't mess with HTTP semantic. HTTP spec is messy, quotation doesn't show real intention by RFC designers. – gavenkoa Mar 15 '20 at 20:49

In my experience, HTTP error codes are insufficient to represent business errors. However, they are useful to represent classes of errors.

So, my recommendation would be to use HTTP error codes for categories of errors, but choose a specific error for business logic failures (e.g. 409 Conflict... 200 OK would be misleading here) and include data in the response indicating the specific business error. Make sure this is part of the response content and not status text because some browsers ignore custom status text. The language I like to use has Union Types which are convenient for representing messages. But you could also define string constants for error cases.


// error with text response
409 Conflict "safety_lock_engaged"
409 Conflict "customer_not_eligible_for_selected_discount"
// warning with JSON response
202 Accepted { "backorderedProductIds": [ 37, 476 ] }
  • The 4xx status codes above 400 have very specific meanings. Choose 400 if your case isn't specifically covered by the others. – jpmc26 Sep 25 '17 at 22:32
  • @jpmc26 If I don't use the status codes this way, then they are never used. But feel free to use them the right and proper way in your answers. – Kasey Speakman Sep 25 '17 at 22:55
  • In some sense, you're right. They're never used. The meanings chosen for the specific codes don't seem to be very common use cases for most applications. That's okay. There are lots of exceptions we never write code to catch, either. – jpmc26 Sep 25 '17 at 22:57
  • @jpmc26 Well exceptions are not the greatest model to follow. But sure, go ahead. – Kasey Speakman Sep 25 '17 at 23:06
  • Not throwing or catching for specific exception types is the analog to not ever using specific HTTP codes or any other error codes. I assumed this would be obvious. Whether an exception model is "the best" is utterly irrelevant to the point. – jpmc26 Sep 26 '17 at 16:25

You can use "Bad Request" and include the id of the violated business rule plus some more details on the body of the response.

  • I don't know whey this was down voted. This is how may companies return business exceptions. – Skadoosh Sep 27 '18 at 18:03

Generally, I would avoid using HTTP status codes to represent specific business logic errors. This is because they already have a semantic meaning that is defined by the world-wide standard. Other systems, new devs and so on will be confused by your deviation of the standard.

What I found in recent, similar, research, was that it is generally accepted to use 400 Bad Request in case of validation errors and such. This way, you use one status code for all business logic errors.

Incidentally, 409 should be used when a resource has changed while you were editing it and tried saving it again.

Not the answer you're looking for? Browse other questions tagged or ask your own question.