What does the application/graphql-response+json media type change about HTTP status codes?
answer
- Two names, two status regimes
- The rule is not about errors
- Ask whether execution ever began
- Presence of the data entry decides it
- Null data is still 200
basics
~20 sIt makes the status meaningful again for failures before execution. The GraphQL over HTTP draft keys the status to whether the response has a data entry: no data entry means 4xx or 5xx, while a data entry present — even null — means 200.
solid answer
~50 sThe GraphQL over HTTP working draft defines two response media types. `application/json` is the compatibility one: a well-formed GraphQL request answers 200 whatever happened, so the status carries no result information. `application/graphql-response+json` restores it, and the rule keys off a single observable fact — does the response carry a `data` entry at all? If it does, execution began, the client has a result to read, and the status is 200 even when `data` is null. If it does not, the request was rejected before execution — a parse failure, a validation failure, a variable that could not be coerced — and the server must answer 4xx or 5xx: 4xx when the caller's request was at fault, 5xx when the server was. So the newer type changes nothing about a resolver that threw; it only stops pre-execution rejections masquerading as successes.
code
json · 7 lines{
"errors": [
{
"message": "Variable $status cannot represent value IN_SERVICE for enum VehicleStatus"
}
]
}go deeper
Know that two response media types exist and that only the newer one lets a status code report a failure. You are not expected to recite the rule, but do not claim a GraphQL endpoint can never return 4xx.
Be able to state the rule as the draft states it: the status keys on whether the response has a data entry, not on whether it reports failures. Name the three pre-execution failures — parse, validation, variable coercion.
Show what the split buys operationally: client-fault requests become visible to dashboards, limiters and caches without body parsing, while resolver failures stay at 200 and still need body-level accounting.
Own the position that this is a working draft, and decide what your organisation standardises on: whether new clients are required to request the status-carrying type, and what your error taxonomy looks like when one class of failure lives in the status and another lives in the body.
## Why a second response media type exists at all The 200-for-everything behaviour was never a design decision anybody defended on its merits. It was what the first servers did, and once a large client population had been written against it, changing the status became a breaking change delivered by the server to clients it does not control. The GraphQL over HTTP working draft's answer is not to change the old behaviour but to give the new one a **different name**, so that a client can ask for it explicitly and a server can keep answering the old way to everyone else. That is the entire reason there are two response media types rather than one rule with a version flag. * `application/json` — the legacy, compatibility mode. Status is fixed at 200 for any well-formed GraphQL request. * `application/graphql-response+json` — the status-carrying mode, and the one the draft steers new clients toward. ## The rule, and the thing it keys on The distinction people expect is "errors mean 4xx". That is not the rule, and getting this right is most of the interview value of the question. The draft keys the status to **whether the response document contains a `data` entry**, because that single fact says whether execution ever began. | Response document | Meaning | Status under `application/graphql-response+json` | | --- | --- | --- | | has a `data` entry with a value | execution ran, result present | 200 | | has a `data` entry that is null | execution ran and the result collapsed | 200 | | has no `data` entry at all | execution never began | 4xx, or 5xx if the server was at fault | The middle row is the one candidates miss. A result that came back null is still a result — the client was handed the outcome of an execution that actually happened, and there is nothing about it the transport should be flagging. ## Which failures land in which row Three things can go wrong before execution begins, and all three produce a response with no `data` entry: * **Parsing.** The document is not valid GraphQL syntax. * **Validation.** The document parses but breaks a rule against this schema — a field that does not exist on the type, a fragment on the wrong type, an argument of the wrong shape. * **Variable coercion.** The document is valid, but the supplied variable values cannot be coerced to their declared types. A fleet telematics graph shows the third one nicely. A tablet in a depot sends `$status: VehicleStatus` with the value `IN_SERVICE`, an enum value the schema replaced months ago with finer-grained ones and that nobody removed from the tablet build. Coercion fails, no resolver runs, and the response has an `errors` entry and no `data` entry. Under `application/json` that comes back 200. Under `application/graphql-response+json` it is a 400, and the status finally tells the truth: the client sent something the server could not act on. Everything that goes wrong **after** execution begins — a resolver that threw, a timeout against a backend, a permission check inside a field — lands in the first or second row. The response has a `data` entry, and the status is 200 under both media types. The newer media type is not a way to surface resolver failures in the status line, and proposing it as one is a red flag. ## The 4xx-versus-5xx split Within the no-`data` row, the choice follows ordinary HTTP fault attribution. A document the client got wrong is a client error, conventionally 400. A server that could not even attempt the request for its own reasons — a schema that failed to build, an internal dependency needed before execution — is a 5xx. Authentication and authorization rejections, when they happen before the document is executed, are the usual 401 and 403 and are not specific to GraphQL. ## Why the split matters operationally Everything in the infrastructure between a client and the server reads the status line and nothing else. Under the newer media type: * a validation failure becomes visible to dashboards, rate limiters and alerting without anyone parsing bodies; * a client bug that sends a malformed document stops looking like a healthy request; * retry policies can distinguish "this request will never work" from "this might work next time"; * a response cache sees a rejection as a rejection. What it does **not** do is fix the observability problem for resolver failures, which remain 200s. A service that has moved to `application/graphql-response+json` still needs body-level or server-side error accounting; it has simply moved one whole class of failure — the class where the client is at fault — up into the transport where the rest of the stack can see it. ## A precision worth stating This is a **working draft**, not a ratified edition of the GraphQL specification. The two media types and the `data`-entry rule are what the draft says and what a growing number of servers implement, but a candidate who describes it as "the GraphQL spec" is overstating it. GraphQL itself is transport-agnostic and defines the response format, not the status code that carries it.
- A server answering application/graphql-response+json fails to build its schema at startup and cannot execute anything. What status fits?A 5xx. The response has no `data` entry, so it falls in the non-2xx row, and the fault attribution decides which side: the caller did nothing wrong, so a client-error status would be misleading. The 4xx side of that row is for requests the client got wrong — bad syntax, an invalid document, uncoercible variables.
- Why does a response with data set to null still return 200 rather than a 4xx?Because execution actually ran. The presence of the `data` entry is the signal that the server got as far as executing the document, and the client is being handed the genuine outcome of that execution. A 4xx would tell the client its request was unacceptable, which is false, and would push most HTTP stacks into discarding a body the client is supposed to read.
- Does adopting the newer media type remove the need to inspect response bodies for failures?No. It moves only pre-execution rejections into the status line. Every failure raised inside a resolver still comes back at 200 with a data entry, so body-level or server-side error accounting is still required. What you gain is that client-fault requests become visible to anything that reads the status — dashboards, limiters, caches, retry policies.
saying these in an interview costs you the question
- Says any response containing errors returns 4xx
- Thinks a resolver failure becomes 4xx under the newer type
- Believes data set to null forces a non-2xx status
- Calls the GraphQL over HTTP draft a ratified specification
- Assumes a server must pick one media type for all clients