# API Reference

> HTTP reference for the Whisper query API: POST and GET /api/query and GET /api/query/stats, with authentication, the response envelope and errors.

*Source: https://www.whisper.security/docs/cypher-api/reference*

---
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`](/docs/cypher-api/reference/query-post) | The main query endpoint. Send Cypher as JSON, with parameter binding and batch statements. A form-encoded body is accepted too. |
| [`GET /api/query`](/docs/cypher-api/reference/query-get) | The same query from a URL parameter — for quick checks and browser-pasteable links. |
| [`GET /api/query/stats`](/docs/cypher-api/reference/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](https://console.whisper.security/sign-in?redirect_url=https%3A%2F%2Fwww.whisper.security%2Fdocs%2Fcypher-api%2Freference) 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](#advisories). |

A `;`-separated batch returns a `results` array instead of this envelope; [POST /api/query](/docs/cypher-api/reference/query-post#batch-statements) 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](/docs/cypher-api/reference/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. |
