Whisper · Docs
API reference

Graph & cognition

An agent about to fetch a URL has about a millisecond to decide whether it should — and "run it through a blocklist" isn't an answer, it's a guess with a timestamp.

A static feed is hours behind a fast-flux C2 domain; whois tells you who registered a name, not who's serving traffic behind the CDN in front of it; and none of it comes back with the receipts a human, an auditor, or another agent needs to trust the call. Whisper answers differently: one call against a live graph of the internet — DNS, BGP, WHOIS, TLS, hosting, threat-intel, fused and current — that hands back a verdict and the exact evidence behind it, in under 300ms.

The question every agent asks before it acts

Three shapes of the same question come up constantly, and none of them are answerable from a flat list:

Whisper's cognition surface is the graph-first answer to all three, and it's the same graph that powers graph-first resolution — the policy engine that decides whether a query even leaves an agent's /128 runs these exact verbs internally, on your behalf, before every answer.

The surface: named verbs over one graph

Every verb below is a read-only CALL against the shared graph. They group into four jobs:

Group Verb What it answers
Attribution identify(target) Who really operates a host or IP — operator, category, and the resolution chain that gets you there, even behind a CDN
asset(id) The full graph record for one node — hostname, IPv4/IPv6, ASN, or organization — by identifier
psl(domain) The registrable domain boundary for a name, per the Public Suffix Listsub.example.co.ukexample.co.uk
Risk assess(targets[]) A risk verdict for one or more indicators — verdict, severity, category (Tor exit, bulletproof hosting, scanner, …)
explain(target) The reasoning behind an assess verdict — which feeds fired, the score, how fresh the observation is
threatintel(indicator) Direct hits against the fused threat-intelligence feeds for one indicator, unscored
lookupTorRelay(ip) Whether an address is a current Tor relay or exit node
lookupTlsFingerprint(ja3\|ja4) Known clients/tooling associated with a TLS client fingerprint
Topology walk(node, depth) Traverse outward from a node along graph edges — infrastructure clusters, shared-hosting genealogies
origins(prefix) BGP origin history for a prefix — who has announced it, and when
topAsnsByPrefixCount() Internet-wide aggregate: ASNs ranked by announced prefix count
variants(domain) Look-alike / typosquat domain variants for a brand or name
Monitoring watch(target) Register a standing watch on an entity so future graph changes surface as events
history(target) The historical timeline of DNS, WHOIS, and ownership changes for a target
submit(indicator) Contribute an observation into the shared graph

Every verb is live today. A caller may also run arbitrary read-only CypherMATCH patterns of your own — directly against the shared subgraph; the named verbs are just the common queries pre-packaged as one call.

How to ask: two doors, one graph

Both doors run the identical query engine and return the identical shape. The only difference is how the caller is authenticated.

With your key, from anywhere, over the gateway:

curl -s https://graph.whisper.security/api/query \
     -H 'X-API-Key: whisper_live_…' \
     --data-urlencode "q=CALL whisper.assess(['185.220.101.1'])"

The read verbs need no key at all: assess, identify, explain, variants, walk, psl, origins, history, asset, lookupTorRelay, and dbSchema answer for anyone, from anywhere. The X-API-Key is required only for the control plane (whisper.agents ops), arbitrary raw Cypher, and submit.

Keyless, from inside a connected agent — the query rides the agent's own routable address, and that /128 is the credential, so no key travels on the wire at all:

curl -s https://[2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4]/api/query \
     --data-urlencode "q=CALL whisper.identify('api.stripe.com')"

The same keyless surface is exposed as one plain HTTP endpoint per verb, right on the agent's own /128, so the single most common cognition question ("is the thing I'm about to connect to safe?") is a single GET with no key and no Cypher to write:

curl -s "https://[2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4]/identify?q=api.openai.com"
# -> rows:[{"host":"api.openai.com","category":"cdn","canonical_name":"Cloudflare",
#           "evidence":["RESOLVES_TO->IPV4->DELEGATED_TO->VENDOR:cloudflare"]}]

The address is the credential: the request lands on the agent's own routable /128, so the answer is already scoped to that identity and no key ever travels on the wire. Every read verb answers the same way from its own path (/explain?q=…, /walk?q=…, /history?q=…, and the rest); assess takes a list, so pass ?q=a&q=b or a JSON body. Off the agent plane the identical verdict is one keyless --data-urlencode "q=CALL whisper.identify(…)" at the gateway shown above.

Under the hood: what "evidence" actually means

The query language is openCypher, the same declarative graph pattern language used across the property-graph ecosystem. A query is passed as a q= form parameter (or ?q= in the URL), or as a JSON {"query": …} body, and answered as JSON rows. A verb like identify is a named, parameterized Cypher procedure; running it is equivalent to writing the underlying MATCH pattern yourself, without needing to know the schema.

What matters for trust is what comes back alongside the verdict. Every attribution or risk answer carries an evidence field that is the literal traversal the graph made to reach its conclusion — a chain of typed nodes and relationships, not a black-box score:

identify('api.openai.com')
  -> operator=Cloudflare · category=cdn
  -> evidence: (HOSTNAME)-[:RESOLVES_TO]->(IPV4)-[:DELEGATED_TO]->(ORGANIZATION:Cloudflare)

assess(['185.220.101.1'])
  -> a risk verdict: band + category (illustrative, e.g. a Tor exit)
explain('185.220.101.1')
  -> the feeds that fired, a score, and recency (illustrative)

identify walked RESOLVES_TO (a DNS answer) into DELEGATED_TO (a WHOIS/RDAP organization edge) and stopped at an ORGANIZATION node — that's the whole chain, reproducible, and disprovable if it's wrong. explain unpacks an assess verdict the same way: which of the fused threat-intel feeds contributed, the resulting score, and the age of the freshest observation behind it. Nothing here is a magic number; it's a graph traversal you can read.

Dual example: attribution, the hard way and the easy way

With stock tools — chase the resolution chain yourself and hope the last hop tells you something:

dig +short api.openai.com                          # CNAME/A chain, ends on a CDN edge IP
whois -h whois.arin.net 104.18.0.0                  # org on the IP -- usually the CDN, not the tenant
curl -sL https://rdap.org/ip/104.18.0.0 | jq .name
# best case: "CLOUDFLARENET" -- correct, but you still don't know who's BEHIND Cloudflare

None of those three commands tells you the tenant behind the edge — CDNs exist specifically to hide that. identify says "Cloudflare, cdn" honestly rather than guess further; for names it fully resolves and owns, the chain terminates at the real organization instead.

With Whisper — the same investigative chain, pre-walked, in one round trip:

curl -s https://graph.whisper.security/api/query -H 'X-API-Key: whisper_live_…' \
     --data-urlencode "q=CALL whisper.identify('api.openai.com')" | jq .
# {"host":"api.openai.com","operator":"Cloudflare","category":"cdn",
#  "evidence":"RESOLVES_TO -> IPV4 -> DELEGATED_TO -> cloudflare"}

Dual example: risk, the hard way and the easy way

With stock tools, checking one IP against Tor and whatever abuse databases you have accounts for means several disjoint tools that don't agree on a scale or a timestamp:

curl -s https://check.torproject.org/torbulkexitlist | grep -qx 185.220.101.1 && echo "tor exit"
whois -h whois.abuseipdb.example 185.220.101.1 2>/dev/null   # only if you have an abuse-DB account

With Whisper, one verdict and one explanation, on the same fused graph the resolver itself queries:

curl -s https://graph.whisper.security/api/query -H 'X-API-Key: whisper_live_…' \
     --data-urlencode "q=CALL whisper.assess(['185.220.101.1'])"        # -> a risk verdict: band + category (illustrative)
curl -s https://graph.whisper.security/api/query -H 'X-API-Key: whisper_live_…' \
     --data-urlencode "q=CALL whisper.explain('185.220.101.1')"         # -> feeds fired, score, recency (illustrative)

Latency, and what happens when the graph is slow

Cognition calls target under 300ms end to end, including the graph traversal. That budget matters beyond the API itself: graph-first resolution calls assess on the resolver's hot path before letting a DNS query proceed, under the same strict timeout — and if the graph doesn't answer inside it, the resolver treats that as no opinion and fails open, never a resolution outage. A slow cognition call degrades gracefully everywhere it's used; it never becomes the single point of failure for traffic that has nothing to do with it.

A verdict without evidence is an opinion. Every assess, identify, and explain response here returns the graph rows behind the answer — the same rows you could walk yourself with raw Cypher — so the decision holds up outside the call that made it.

Next