API Reference
The Whisper API in three endpoints — POST /api/query, GET /api/query, GET /api/query/stats. Start here, then open the one you need.
In this section
- POST /api/queryThe main query endpoint: send read-only Cypher as JSON, bind parameters, run batch statements. Reference with code in five languages.
- GET /api/queryRun a Cypher query from a URL parameter, for quick checks and pasteable links. Reference with code in five languages.
- GET /api/query/statsGraph-wide node and edge counts, threat-intel summary, and per-layer freshness and coverage. No key needed. Code in five languages.
On this page (4)
API Reference Documentation
The Whisper query API is three HTTP endpoints on https://graph.whisper.security. This page covers what they share — authentication, the response envelope, advisories — and links to a dedicated page for each endpoint with request and response details and ready-to-run code in five languages.
The endpoints
| Endpoint | Use it for |
|---|---|
POST /api/query | The main query endpoint. Send Cypher as JSON, with parameter binding and batch statements. A form-encoded body is accepted too. |
GET /api/query | The same query from a URL parameter — for quick checks and browser-pasteable links. |
GET /api/query/stats | Graph-wide node and edge counts, the threat-intel summary, and the freshness and coverage of every computed layer. No key needed. |
Authentication
Authentication works the same way on both query endpoints; the stats endpoint needs no key. Some queries run without a key; the deeper cross-layer traversals and the full procedure set need one. Pass it in the X-API-Key header; Authorization: Bearer <key> and Authorization: ApiKey <key> are also accepted. Sign in to get a key — there is no card to enter.
A missing, mistyped or unrecognized key does not fail the request. The API runs the query with reduced access and answers 200, so when a result is thinner than you expected, check the key before you debug the query. Confirm it was accepted with CALL whisper.quota(): the isAnonymous row must be false.
Response envelope
Every successful single-statement response has the same shape, whichever endpoint produced it:
| Field | Type | Description |
|---|---|---|
columns | string[] | Column names in RETURN order. |
rows | object[] | One object per row, keyed by column name. |
statistics.rowCount | number | Number of rows returned. |
statistics.executionTimeMs | number | Server-side execution time. Excludes network latency, so your measured round trip will be larger. |
statistics.cached | boolean | Present only when the answer came from the result cache. |
statistics.cachedExecutionTimeMs | number | Present only on a cache hit: how long the original computation took. |
rewrittenQuery | string | Present only when the engine rewrote your query to current label or edge names before running it. Update your query to the text it holds. |
advisories | object[] | Present only when the engine has a non-fatal note about how it interpreted the query. See Advisories. |
A ;-separated batch returns a results array instead of this envelope; POST /api/query shows that shape.
Advisories
An advisory is a note on a successful response: the query ran, but the engine interpreted something in a way you should know about. Each entry carries a kind slug, a human-readable message, and, where a value was substituted, queried (what you asked for) and resolved (what was applied). Branch on kind; the message is written for a person and may change.
kind | When you see it | What to do |
|---|---|---|
null-pagination-param | A parameter bound to SKIP or LIMIT resolved to null, so the clause was applied as SKIP 0 or as no limit at all. | Pass a numeric value. |
skip-past-cardinality | SKIP moved past the last row, so the page is empty. | You have reached the end; stop paging. |
projection-verdict-omitted | A whole-node projection (RETURN n, keys(n), properties(n)) left out the reconciled threat-verdict properties. | Send "projectionFull": true, or project the properties you need by name. |
enrich-semantics | whisper.enrich() ran. The note explains its columns: owner is a network attribution, not a threat attribution, and rows are de-duplicated by name. | Join results back to your input by name, never by position. |
whois-parent-fold | A WHOIS lookup on a subdomain was answered from its registrable parent domain; queried and resolved show both names. | Read the record as the parent's. |
schema-drift-rewrite | The query named a label or edge type by an older name and was rewritten; rewrittenQuery holds the text that ran. | Update your query to the current names. |
vdp-plane-empty | A computed layer the query relies on holds no data right now. | Do not read an empty result from it as a clean absence. Check the layer's coverage in GET /api/query/stats and retry later. |
vdp-anchor-empty | The computed layer holds nothing for your anchor right now. | Same as above: an absence here means "not available", not "none". |
origins-all-candidates-withheld | whisper.origins() found candidate origin addresses but withheld every one of them. | Read the message for the reason before drawing a conclusion. |
explain-verdict-axis-unavailable, explain-score-unavailable | Part of a verdict could not be computed for this call. | Treat the missing part as unknown, not as clean; retry later. |
advisories-truncated | More advisories were produced than were returned. | Fix the ones you can see and run again. |