43

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?

5
  • 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. Commented Feb 7, 2017 at 14:18
  • 1
    @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
    Commented Feb 7, 2017 at 17:58
  • 1
    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. Commented Feb 7, 2017 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
    Commented Sep 25, 2017 at 22:31
  • 1
    @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. Commented Sep 26, 2017 at 10:01

5 Answers 5

45

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.

(Update: this is now part of the HTTP Semantics specification.)

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.

5
  • 13
    "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
    Commented Feb 8, 2017 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
    Commented Sep 25, 2017 at 22:28
  • 2
    I would use 422. Input is valid so 400 is not the right error code to use
    – Konrad
    Commented Apr 29, 2019 at 9:14
  • 1
    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
    Commented Mar 15, 2020 at 20:49
  • 1
    I would expect to use 412 Precondition Failed when a request condition (like If-Match: ETag) evaluates to false. Commented Jun 30, 2022 at 12:35
25

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.

Examples

// 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 ] }
7
  • The 4xx status codes above 400 have very specific meanings. Choose 400 if your case isn't specifically covered by the others.
    – jpmc26
    Commented Sep 25, 2017 at 22:32
  • 1
    @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. Commented Sep 25, 2017 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
    Commented Sep 25, 2017 at 22:57
  • @jpmc26 Well exceptions are not the greatest model to follow. But sure, go ahead. Commented Sep 25, 2017 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
    Commented Sep 26, 2017 at 16:25
10

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

2
  • I don't know whey this was down voted. This is how may companies return business exceptions.
    – Skadoosh
    Commented Sep 27, 2018 at 18:03
  • It is a “bad request” if that is in the nature of the request. For example “Buy -13 items with item number 1234 for 5 yard 11 inches each”. That’s a bad request. “Buy item number 1234” when 1234 is not in the item catalogue is not a bad request, because tomorrow after the item is added to the catalogue the request would be accepted.
    – gnasher729
    Commented Nov 27, 2022 at 18:02
8

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.

1

In my opinion http status codes should be used to inform you about the status of a request and nothing else. In practice, a request produces an error if there was no meaningful connection to the server (failed https = connection, but not meaningful), or a status code if you received a status code from the server).

Business rule errors should be returned under “response”. For example I put a request to give some customer 30% rebate; the status code should tell me that the request was received and processed; if ther is a business rule that doesn’t allow 30% rebate then the status code should be “success” and the response should tell me that the rebate was rejected and why.

1
  • This is an approach that used to be taken with SOAP web service calls, where the HTTP response codes were only considered transport related, and the XML payload would indicate success or failure, so you could get a 200 OK but the request failed. REST APIs explicitly choose not to do this though, and require you to think in terms of HTTP resources and verbs, with errors being based entirely around HTTP errors. So what you're suggesting is a legitimate approach, but not strictly RESTful.
    – ChrisC
    Commented May 31, 2023 at 7:13

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