Errors
Every error you can cause by writing a wrong query, keyed on the type slug rather than the prose — the string that is the stable API surface, the body shape the API actually sends, and the machine-readable extras each one carries.
On this page (5)
Errors Documentation
When a request to the Whisper API fails, the response body tells you what went wrong and, for query errors, usually how to fix it. This page covers that body, the type slugs the engine returns, and what to do about each one.
One thing that is not on this page: a query that succeeds but returns zero rows. That is almost always a wrong label or edge name, not an error. Check CALL db.labels() and CALL db.relationshipTypes() first, and see Best Practices.
Error format
Errors come back as an RFC 7807 problem object: a type, a title, a status, a detail and a timestamp, usually with a suggestions array that proposes a rewrite. Some types add machine-readable fields of their own — query-unservable carries a reason, for instance.
The type slug is the stable surface. Branch on it, and never on the prose in detail, which is written for a human and changes.
Read the body, not the Content-Type header. The body is problem+json-shaped and the response is sent as application/json, so a client that branches on the content type will not recognise its own errors.
An unbound variable:
{
"type": "https://whisper.security/errors/query-error",
"title": "Query Error",
"status": 400,
"detail": "Variable 'nosuchvar' is not defined in this scope. Bind it with MATCH (nosuchvar:LABEL …), WITH … AS nosuchvar, UNWIND … AS nosuchvar, or YIELD nosuchvar.",
"timestamp": "2026-08-09T19:12:47.704324212Z",
"suggestions": [
{
"kind": "undefined_variable",
"rationale": "The query references 'nosuchvar', which was never bound by a preceding MATCH / WITH / UNWIND / YIELD in scope.",
"rewrite": "Introduce every RETURN/WHERE variable with a preceding MATCH (var:LABEL ...) / WITH var / UNWIND ... AS var / YIELD var.",
"confidence": "high",
"safeToAutoRetry": false
}
]
}
Each suggestions entry carries a kind, a rationale, a rewrite, a confidence, and safeToAutoRetry — the field an automated client should read before re-running anything on your behalf. suggestions is sometimes present and empty; treat that the same as absent.
Status codes and type slugs
type slugsEvery row below was reproduced against production on 2026-08-09.
| Status | type | Condition | What to do |
|---|---|---|---|
400 | query-error | The Cypher is malformed, names an unbound variable, or calls a procedure the engine does not have. Also what a write clause returns. | Fix the query. detail names the position, the variable or the procedure, and suggestions proposes a rewrite. See Syntax & Clauses. |
400 | query-validation-error | The request carried no statement — an empty query field, or a body with no query in it. | Send a query field with a statement in it. |
400 | query-unservable | The engine refused to plan the shape at all. reason says which: global_edge_count (an edge count with both endpoints bare) or unanchored_virtual_edge_scan (a synthesized edge with neither endpoint labelled or anchored). | Anchor or label an endpoint. For a per-type edge total, CALL db.relationshipTypes() YIELD type, count is precomputed and instant. |
400 | missing-query-parameter | The GET form was called with no q. | Send the query as ?q= or a form parameter, or use POST with a JSON body. |
400 | malformed-request-body | The request body is not valid JSON. | Check the body. Request fields are listed in the API Reference. |
403 | (edge, not engine) | The WAF rejected the request. Production sits behind one, and it rejects some default programmatic user agents. | Send an explicit User-Agent header; any descriptive string works. |
415 | unsupported-media-type | The Content-Type is neither JSON nor form-encoded. | Send Content-Type: application/json on POST. |
500 | (not reproduced) | The engine faulted while serving the query. | Retry once; if it persists, simplify the query — Best Practices covers the shortestPath case — or report it (see below). |
503 | (not reproduced) | A backing data service was briefly unreachable. | Retry after a short wait. |
This set is measured, not exhaustive. The engine defines more type slugs than the six reproduced above; internal-error and schema-drift are two that could not be forced from a client and so are not documented here with a body shape. Read the type you get, not a list.
The API is read-only. Write clauses (CREATE, MERGE, SET, DELETE) are rejected as a query-error before anything runs, with a readonly_engine suggestion; nothing you send can modify the graph.
The invalid-key gotcha
An unknown or malformed API key does not fail the request. The query API never returns 401 — an unrecognised key is treated as though you had sent no key at all, so the symptom is never an auth error. It is a query that ran yesterday and comes back refused today, or a result thinner than the one you expect. When results look wrong, re-check the header format on the Cypher API page.
The MCP connector behaves the other way round. There an unrecognised bearer is rejected outright: HTTP 401 {"error":"Invalid API key"}. Same key, two surfaces, two behaviours — so a working MCP session proves nothing about the header your HTTP client is sending, and the reverse.
When a backing service is briefly unavailable
explain() and whisper.history() read from a live threat-intelligence backend at call time. If that backend is briefly unreachable, the procedure returns available: false with a retryAfter value instead of a score; the HTTP request itself still succeeds. Respect the retry interval and call again. Note that batching indicators with UNWIND ... CALL explain(...) makes one backend call per item, so runtime grows with the list; keep unwound lists short.
Reporting issues
Two response headers exist to make a request findable after the fact: X-Request-Id identifies the request, and X-Served-By names the replica that answered it. Both were present on every response, signed in and signed out. Quote them when you report a problem — without the request id nobody can find your request in the logs.
When opening a support ticket, include:
- The full request URL and request body
- The full response, headers and body
- The
x-request-idheader from the response - The time the request was made (UTC)
Start at Support, or email support@whisper.security.