Errors & Rate Limits

The problem+json error format, every HTTP status code, rate-limit windows and headers, and the depth and quota caps for each tier.

Updated July 2026

Errors & Rate Limits Documentation

When a request to the Whisper API fails, the response is an application/problem+json object that tells you what went wrong and, for query errors, usually how to fix it. This page covers the error format, the status codes you can hit, the rate-limit windows and headers, and the depth and quota caps for each plan tier.

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 application/problem+json with a type, title, status, and detail, and often a suggestions array that proposes a rewrite. Some error types add machine-readable fields of their own. A depth-exceeded error looks like this:

{
  "type": "https://whisper.security/errors/query-depth-exceeded",
  "title": "Query Depth Exceeded",
  "status": 400,
  "detail": "Query depth 3 exceeds maximum allowed depth of 2. Reduce traversal hops or upgrade your plan for deeper queries.",
  "actualDepth": 3,
  "maxAllowedDepth": 2
}

HTTP status codes

StatusConditionWhat to do
400Cypher parse errorFix the syntax. The detail names the position and suggestions proposes a fix. See Syntax & Clauses.
400Query depth exceededYour plan caps traversal depth (anonymous is 2 hops, a free key is 3). Reduce hops or use a key; see the tier caps below.
400Malformed JSON bodyCheck that the request body is valid JSON. Request fields are listed in the API Reference.
401Invalid key (rare)Most invalid keys degrade to anonymous rather than returning 401; if you see this, re-check the header format.
403WAF rejected the requestProduction sits behind a WAF that rejects some default programmatic user agents. Send an explicit User-Agent header; any descriptive string works.
408Query exceeded the timeoutThe query hit the time cap. Anchor it, add a LIMIT, or replace a scan with a procedure.
429Rate limit or capacity ceilingBack off and retry. The response carries a Retry-After header.
500Internal engine errorRetry once; if it persists, report the query (see below).
504Upstream timeoutA backing service was briefly unavailable. Retry after a short wait.

The API is read-only. Write clauses (CREATE, MERGE, SET, DELETE) are rejected with a 400 parse error; nothing you send can modify the graph.

The invalid-key gotcha

An unknown or malformed API key does not normally fail the request. It degrades gracefully to the anonymous tier, so the symptom is not a 401: it is a query-depth-exceeded error on a 3-hop query that worked yesterday, or thinner rate limits than you expect. When results look wrong, run CALL whisper.quota() and check the isAnonymous value — if it is true while you are sending a key, the key is not being read. Header formats are on the Cypher API page.

Rate limits and quota

Quota is enforced on an hourly and a daily window. A 429 carries a Retry-After header; wait it out and retry. Authenticated responses also include running counters on every response, not just on 429s, so you can watch your remaining quota without a separate call:

  • X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset — the hourly window.
  • X-DailyLimit-Limit / X-DailyLimit-Remaining / X-DailyLimit-Reset — the daily window.

From inside Cypher, whisper.quota() returns your plan tier, usage, and caps:

CALL whisper.quota() YIELD key, value RETURN key, value
[
  {"key": "plan", "value": "FREE"},
  {"key": "isAnonymous", "value": false},
  {"key": "hourlyLimit", "value": "500"},
  {"key": "hourlyUsed", "value": 12},
  {"key": "hourlyRemaining", "value": "488"},
  {"key": "dailyLimit", "value": "5000"},
  {"key": "maxQueryDepth", "value": "3"},
  {"key": "maxResponseLimit", "value": "5000"}
]

Metadata calls are free: whisper.quota() itself and the db.* introspection procedures (db.labels(), db.relationshipTypes(), db.propertyKeys(), db.schema()) do not count against your quota.

Tier caps

TierDepth capAlso
Anonymous (no key)2 hopsLower rate limits; no advanced procedures such as whisper.history().
Free key3 hopsHigher rate limits and the full procedure set. Pass the key as X-API-Key: whisper-YOUR_API_KEY, or Authorization: Bearer/ApiKey.
Paid plansDeeperDeeper traversals, higher rates, larger result sets. The pricing page is the source of truth.

A query with more hops than your tier allows returns HTTP 400 with a query-depth-exceeded error before it runs. When a chain exceeds your tier, split it into anchored single hops joined with WITH, or reach for a procedure: explain() collapses a deep threat traversal into one call.

Timeouts

Queries are time-capped. A query that runs too long returns 408 with a query-timeout error. The request body accepts a timeout field in milliseconds, clamped to your plan maximum and a global ceiling (see the API Reference).

If you hit the cap, the fix is almost always the same: anchor the query on an indexed name, add a LIMIT, bound intermediates with WITH ... LIMIT, or replace the scan with a procedure. One special case never finishes at any tier: a global edge count like MATCH ()-[r]->() RETURN count(r). Use GET /api/query/stats instead — the totals there are precomputed and instant. The full set of habits is in Best Practices.

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

When opening a support ticket, include:

  1. The full request URL and request body
  2. The full response, headers and body
  3. The request_id from the response, if present
  4. The time the request was made (UTC)

Open a ticket at console.whisper.security/support or email support@whisper.security.