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.
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
| Status | Condition | What to do |
|---|---|---|
400 | Cypher parse error | Fix the syntax. The detail names the position and suggestions proposes a fix. See Syntax & Clauses. |
400 | Query depth exceeded | Your plan caps traversal depth (anonymous is 2 hops, a free key is 3). Reduce hops or use a key; see the tier caps below. |
400 | Malformed JSON body | Check that the request body is valid JSON. Request fields are listed in the API Reference. |
401 | Invalid key (rare) | Most invalid keys degrade to anonymous rather than returning 401; if you see this, re-check the header format. |
403 | WAF rejected the request | Production sits behind a WAF that rejects some default programmatic user agents. Send an explicit User-Agent header; any descriptive string works. |
408 | Query exceeded the timeout | The query hit the time cap. Anchor it, add a LIMIT, or replace a scan with a procedure. |
429 | Rate limit or capacity ceiling | Back off and retry. The response carries a Retry-After header. |
500 | Internal engine error | Retry once; if it persists, report the query (see below). |
504 | Upstream timeout | A 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
| Tier | Depth cap | Also |
|---|---|---|
| Anonymous (no key) | 2 hops | Lower rate limits; no advanced procedures such as whisper.history(). |
| Free key | 3 hops | Higher rate limits and the full procedure set. Pass the key as X-API-Key: whisper-YOUR_API_KEY, or Authorization: Bearer/ApiKey. |
| Paid plans | Deeper | Deeper 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:
- The full request URL and request body
- The full response, headers and body
- The
request_idfrom the response, if present - The time the request was made (UTC)
Open a ticket at console.whisper.security/support or email support@whisper.security.