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.
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.400 Bad Request
as a blanket HTTP code seems best to cover business logic errors as a class.400 Bad Request
when data is missing or can't be read/parsed. I.e. the request data itself is bad in some way.