Should HTTP status codes be used to represent business logic errors on a server?

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?

May 14, 2025 - 15:26
 0

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?