Why was the decision made that data and errors can't coexist?

I’m interested in finding a good and thorough standard for JSON-based APIs to help my team stay on the same page with all of our API responses and facilitate communication. To that end, JSON API seems to be the most popular, or at least the most widely linked, on the internet. But off the bat at the very top of the specification I noticed something that just seems weird to me.

The members data and errors MUST NOT coexist in the same document.

I’m interested in understanding the reason for this decision. At the surface is seems reasonable but I can think of two really good reasons that this requirement doesn’t make sense:

  1. Statically typed programming languages don’t have a way to represent two members which are mutually exclusive. This means any C++, Java, C#, TypeScript, or similar SDK trying to ingest a JSON API based endpoint will run into issues. The Document object must contain both a data member and an errors member, but attempting to serialize such an object will produce an invalid document.
  2. Because a REST API could sit in front of ANY source of data, and not just a database, it’s possible that valid data may be returned and errors may exist – particularly with collections. Consider an endpoint which pings several servers and returns their response times. If one of the servers is offline then you have an error, but the existence of that error doesn’t invalidate the data from the other servers.

I imagine the intention was to make it trivial to test for the existence of an error using something like:

fetch("/api/endpoint").then(resp => resp.json()).then(resp => {
    if (resp.errors) {
        // Handle errors
    } else {
        // Utilize data
    }
});

However this could just as easily be accomplished with:

    if (resp.errors.length > 0) {

So could someone explain the reason that “MUST NOT” was included in the specification here? Would it be unreasonable for version 1.1 to replace this with just “it is RECOMMENDED that these two members not coexist”, or similar?

To your point #1: I have a JSON:API library written in Go. That as never been a problem. The Document struct has both a Data field and an Errors field. If len(Errors) > 0, I know I have to marshal some errors, otherwise I marshal the data.

The shape of the document and your data structure are different things. They don’t have to be the same. If you need them to be the same because that what’s the library you’re using for JSON marshaling expects, then that’s your limitation.

To your point #2: I think you are confusing different kinds of errors. In your example, nothing went wrong. You pinged some servers as expected, and for each ping you got a successful result. Here’s the thing, if a server is offline, then you still successfully managed to get the result, which is offline instead of something like 42ms.

Your payload then successfully returns a bunch of results in a collection, like 42ms, 8ms, 71ms, and offline.

Errors are for the request between the client and the server. An error occurs when the client or the server can’t properly do their job. For example, the client wants the server to ping an/invalid.domain or the server had an unexpected error.