I have been doing things the same way for a while. In my RPC API everything is POST (even for "getting" something), and everything returns 200.

I don't like the design of REST. I don't think status codes should have meaning to an application. Why? Because there is not a defined status code for every type of problem.

One thing I have often run into is: what status code are you supposed to use when business rules aren't followed? Let's say the resource exists, so it's not a 404, and the request is properly formatted as JSON and all the fields have valid values, so it's not a 400. But the user is trying to do an invalid action, like booking an appointment slot that is already full. What status code are you going to use?

For this reason, I've seen two types of code bases that use status codes. The first one is error handling soup:

if (res.statusCode == 404) {
// Display a "not found" error to user
} else if (res.statusCode == 400) {
// There will probably be some validation errors in the body, so display those
} else if (res.statusCode == 403) {
// show a "forbidden" error to user
} else if (res.body.error) {
// Aha! We have some error that doesn't have a defined status code.
// This block will contain a completely different type of error handling,
// based on information found in the body.
}

And it's just a mess. The second one is a bit better, where they only care about 200 or not-200, and pass error information through the body:

if (res.statusCode != 200) {
// Error handling reading information from res.body
}

But why put in the work to use correct status codes on the server side if it essentially comes down to a boolean value?

So my solution is to always have a boolean value called "ok", and if "ok" is not true there's always a human-readable error you can show to the user.

if (!res.body.ok) {
// Show res.body.error to the user
}

There's a bit more to it since I also account for passing back field-by-field validation errors, but the point is that I'm always returning 200 and I am always reading error information out of the body. If some resource doesn't exist, the error message will say that. If the user is forbidden from doing something, the error message will say that. If the appointment slot is full, the error will say that.

There are only two places where I use status codes.

If some unexpected error happens (like the database is down), I return 500. If my frontend ever sees 500 it sends the error to my error reporting system.

If the user is not authenticated I return 401. If the frontend ever sees 401 it automatically redirects the user to the login screen.

Importantly, both of these things are hidden away in a library I wrote, so my application code never thinks about them. It just thinks about !ok.

Status codes work for things that are generic from the point of view of the client, in the sense that the client doesn't care if my database is down or if I misconfigured something or if I ran out of memory. It only cares if "something bad happened that needs to be reported to the devs", which is 500, or "this user is no longer logged in so they need to log in", which is 401.

For everything else, the client does care about the specifics of what the error means, so I need to pass it that information. Status codes don't work for that.