Skip to content
HTTP API
Skip navigation
HTTP API

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 problem+json body the API sends, and the machine-readable extras each one carries.

Published

View as Markdown
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 document, sent as Content-Type: application/problem+json: a type URI under https://whisper.security/errors/, a title, a status, a detail, an instance and a timestamp. A query error adds a suggestions array that proposes a rewrite, and 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.

An unbound variable, captured from the live API on 2026-09-02:

json
{
  "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.",
  "instance": "/api/query",
  "timestamp": "2026-09-02T16:14:54.587509203Z",
  "suggestions": [
    {
      "kind": "undefined_variable",
      "rationale": "The query references 'nosuchvar', which was never bound by a preceding MATCH / WITH / UNWIND / YIELD in scope. Bind it first, or reference a variable that is 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

StatustypeConditionWhat to do
400query-errorThe Cypher is malformed, names an unbound variable or an unsupplied $parameter, 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.
400query-validation-errorThe 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.
400query-unservableThe engine cannot plan the shape. reason says which: global_edge_count (an edge count with both endpoints bare) or unanchored_virtual_edge_scan (a computed edge with neither endpoint labelled or anchored).Label or anchor at least one endpoint of a computed edge. For a per-type edge total, CALL db.relationshipTypes() YIELD type, count is precomputed and instant; for the global total, read GET /api/query/stats.
400query-depth-exceededThe pattern is deeper than your access allows.Sign in, or decompose the chain: run the first leg, anchor the next request on what came back, or let a procedure such as whisper.enrich() do the join in one call. Best Practices shows how to stage a traversal.
400missing-query-parameterThe GET or form-encoded form was called with no q.Send the query as ?q= or a form field named q, or use POST with a JSON body.
400malformed-request-bodyThe request body is not valid JSON.Check the body. Request fields are listed in the API Reference.
403(not a problem document)A browser page sent a cross-origin request; the API does not answer those.Call the API from a server or a script, and put your own backend between a browser front end and the API.
408query-timeoutThe query ran past its time budget. The body adds timeoutMs and elapsedMs.Narrow it: anchor on a name, bound the wide hop with WITH ... LIMIT, and stage the traversal.
415unsupported-media-typeThe Content-Type is neither JSON nor form-encoded.Send Content-Type: application/json on POST.
429query-quota-exceededYou sent more requests at once, or more in a row, than your access allows right now.Wait, then retry. Put a list of indicators into one UNWIND query instead of one request each.

Every slug above is stable; key your handling on the slug and read detail for the specifics. The API is read-only: write clauses (CREATE, MERGE, SET, DELETE, REMOVE, DROP) are refused as a query-error before anything runs, with a readonly_engine suggestion whose safeToAutoRetry is false. Nothing you send can modify the graph. For any status not in this table, retry once, then report it with the request id.

Confirm the key was accepted

A missing, mistyped or unrecognised API key does not fail the request. The query runs with reduced access and answers 200, so the symptom of a bad key is never an authentication error: it is a query that is refused, or a result thinner than the one you expected. Before you debug the query, run CALL whisper.quota() and check that the isAnonymous row is false. If it is true, re-check the header format on the HTTP API page.

The MCP connector behaves differently: it rejects an unrecognised key outright with 401. A working MCP session therefore says nothing about the header your HTTP client sends, and the reverse; confirm each surface on its own.

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. 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 make a request findable after the fact: X-Request-Id identifies the request, and X-Served-By identifies where it was answered. Both are present on every response, signed in and signed out. Quote both when you report a problem; the request id is how support finds your request.

When opening a support ticket, include:

  1. The full request URL and request body
  2. The full response, headers and body
  3. The X-Request-Id and X-Served-By headers from the response
  4. The time the request was made (UTC)

Start at Support, or email support@whisper.security.