Revisiting HTTP status code

My interpretation of the RFC and why “successfully failed” is not as crazy as you think

Hajime Yamasaki Vukelic
5 min readJun 26, 2023
200OK: Bad request

Recently I ran into this long and rather aggravated debate about the HTTP status codes revolving around the (probably) well-known meme.

The meme itself shows something that’s clearly wrong: returning a 200 OK response which contains an entirely different status code. Wrong, ludicrous, blasphemy! However, is it really wrong to return an error in a 200 OK response? What does “OK” even mean in this context? And what code do we use for errors?

Now, before you react to this article, please keep in mind that I’m conveying my own interpretation of the RFC. You may have your own, and that’s fine. At the end of the day, how your interpret your RFC has little bearing on the service’s ability to perform as required.

Oh and if you know what LinkedIn debate I am referring to and you followed it… well, you’ve probably already read most of what I’m about to say. So… 303 See Other.

Two sorts of outcomes

Now, before we start, let’s first talk about errors themselves. Errors returned by an HTTP service are broadly categorized as one of two errors:

  • Service-level errors
  • Business errors

Service-level errors are those that prevent the service from fulfilling the request. Things like “Oops, I ran out of memory”, or “Dude, I said JSON, not XML.”

Then we also have business errors like “Your credentials do not match” or “You’re missing this parameter”.

Similarly, “success” can also be classified as either a service-level success or a business success.

Service-level success means the service successfully processed your request. For instance, the form data was understood and processed without technical issues (it was not malformed).

A business success means the success at the application level. The form validated, and the result of processing the form data was a success as defined by the application.

Interpreting the RCF

Let’s look at the RFC 9110 and see whether the distinctions we made in the previous section make sense.

In section 15.3, the RFC talks about the 2xx status codes. It starts with this paragraph:

The 2xx (Successful) class of status code indicates that the client’s request was successfully received, understood, and accepted.

As we can see, it does not prescribe any application-specific behavior. Further below, it talks about the 200 OK status.

Now for GET requests, we normally all agree that it should return the requested resource, and there’s no ambiguity about that in the RFC either. Where most debates start are the other verbs. So what does the RFC actually say about it? In relation to the POST request, for example, the 200 OK is supposed to mean:

the status of, or results obtained from, the action

We again see that there’s no prescription of any business-specific behavior. It says “the status of, or result”, without further qualification as to whether this status or result must be positive.

Therefore, it is actually perfectly reasonable to say that the 200 OK response can contain an error message.

But then someone says “But what about the 400?”

So we skip ahead to the section 15.5 where the RFC talks about the 4xx codes. It opens up with the following paragraph:

The 4xx (Client Error) class of status code indicates that the client seems to have erred. Except when responding to a HEAD request, the server SHOULD send a representation containing an explanation of the error situation, and whether it is a temporary or permanent condition. These status codes are applicable to any request method. User agents SHOULD display any included representation to the user.

Other than the “client seems to have erred”, it does not provide much detail, so let’s look at the 400 Bad Request description to see if there are more clues:

The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).

How are we to understand this? If we take, for instance “missing parameter”, which is a business error, it does not fall under any of the “malformed syntax”, “invalid request message framing”, or “deceptive request routing”. Of course, those are only some examples, and it isn’t an exhaustive list, but if we look at what those failure have in common, we notice that they are all service-level issues.

Therefore, the 400 Bad Request, in my opinion, cannot be understood to mean a business level failure such as failure to validate a form.

Another interesting topic that pops up from time to time is what status code the server should return when a user fails to authenticate. So we look at the two codes most API designers typically use: 401 and 403.

The status code 401 Unauthorized is described as follows:

The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource. The server generating a 401 response MUST send a WWW-Authenticate header field (Section 11.6.1) containing at least one challenge applicable to the target resource. […]

As the name itself suggests, this is an authorization-related status code, not an authentication related one.

Authorization is related to user permissions. A user could be authenticated (meaning the service knows who they are), but may still be unauthorized (meaning lacking permission) to access some resource.

The status 403 Forbidden is defined like this:

The 403 (Forbidden) status code indicates that the server understood the request but refuses to fulfill it. […] If authentication credentials were provided in the request, the server considers them insufficient to grant access.

So this status code actually sounds like it would be the correct response in case the client fails to provide valid credentials. But bear in mind that this status code is not specific to authentication. The RFC further remarks to that effect:

However, a request might be forbidden for reasons unrelated to the credentials.

Conclusion

After reading the RFC and thinking about it, here’s what I think is the “correct” way to use status code. In my opinion it’s perfectly fine to return business error messages in the 200 OK payload. After all, it’s a perfectly valid result of successfully processing your request. “OK” means “OK” as in “OK, gotcha. Now listen up…” There is no status code for business errors. That’s because HTTP status code are for service-level outcomes, not business ones.

In heated online debates, it’s common that you come across something that, on the surface, sounds quite ludicrous. “Impossible! That’s stupid!” you may think. However, spending a bit of time researching the topic may sometimes reveal that the case is not as clear-cut as it originally seems.

Many best practices, principles, and patterns are like that. They were invented in some context, by some people, and then passed on from one developer to another until it becomes somewhat of a dogma. But when you actually dive into it and try to figure it out, you may be surprised.

My take-away from this status code debate was RTFM. If I keep an open mind, I might actually learn something new…

--

--

Hajime Yamasaki Vukelic

Helping build an inclusive and accessible web. Web developer and writer. Sometimes annoying, but mostly just looking to share knowledge.