Cheat Sheet
One-page Cypher cheat sheet: schema, edge directions, traversal chains, procedures with their columns, and query rules.
On this page (16)
- Endpoint
- Node labels
- Edge types — with directions
- DNS & web
- BGP, routing & RPKI
- WHOIS & registration
- Geo
- Threat intel & egress
- Email security (SPF / DMARC / DKIM)
- Physical infrastructure & CT
- Direction landmines (memorize these)
- Must-know traversal chains
- Threat properties on a node
- Procedures — one-liners
- Query rules (one line each)
- Send one
Cheat Sheet Documentation
WhisperGraph is the internet as a queryable database — DNS, BGP/RPKI, WHOIS/RDAP, GeoIP, email posture, certificate transparency, threat intel, and the physical internet, all pre-joined. This page is the dense quick-reference: labels, edges with directions, the traversal chains worth memorizing, and procedure one-liners with their exact columns. If you already know Cypher, this is the WhisperGraph-specific part you need. Deeper detail lives in the Graph Schema, Procedures, and the Workflows.
Scale: 7.5B nodes · 39.8B edges · 42 labels · 55 edge types · 52 procedures · 134 intelligence feeds (112 of them threat feeds) · 32 categories. GET /api/query/stats returns live totals.
Endpoint
POST https://graph.whisper.security/api/query
Content-Type: application/json
User-Agent: whisper-client/1.0 # send an explicit UA to avoid a WAF 403
X-API-Key: <your-key> # sign in to get one — see /docs/whisper-graph/getting-started
{"query": "MATCH (h:HOSTNAME {name:\"www.google.com\"})-[:RESOLVES_TO]->(ip) RETURN ip.name LIMIT 3"}
Bind $name placeholders through a sibling "parameters": {...} object; ;-separated statements run as a batch and come back as a results array. MCP for agents at https://mcp.whisper.security. See MCP setup. Full API details: HTTP API.
Node labels
There is no Domain or FQDN label — every name is a HOSTNAME, and a legacy label (Domain, IpAddress, Certificate) is rejected with an error naming the replacement. Every node has a name property except ROA. Names are lowercase with no trailing dot. Labels marked (computed) are synthesized at query time — reach them through an edge from an anchored node, never via an unanchored scan.
Core DNS & addressing — HOSTNAME (2.8B), IPV4 (622M), IPV6, PREFIX, ANNOUNCED_PREFIX (computed), REGISTERED_PREFIX (computed; .rir, .country), TLD, URL (computed; phishing-kit paths — anchor by {name}, which holds the path; {path} and {id} scan the label)
Routing & org — ASN (AS13335; the registry is on .autNumSourceRir), ASN_NAME (computed), ORGANIZATION (raw registrant strings; fold with SAME_ORG_AS), TLD_OPERATOR, RIR (node-only; nothing joins to it)
WHOIS & registration — REGISTRAR, EMAIL, PHONE, RDAP_ENTITY (reached from PREFIX or ASN over REGISTERED_TO_ENTITY)
Geo & DNSSEC — CITY, COUNTRY, DNSSEC_ALGORITHM
Threat intel — FEED_SOURCE (134, computed; .name is the slug, .displayName the label), CATEGORY (32, computed), THREAT_TAG (MISP-galaxy, via TAGGED_AS), THREAT_SIGNAL_TYPE (each signal belongs to one label, for example prefix-age-anomaly and toxic-neighborhood on PREFIX; bulletproof-hosting, critical-infrastructure, ddos-mitigation and satellite-network on ASN; hidden-primary-soa, subdomain-takeover, wildcard-dns and infrastructure-staging on HOSTNAME), ACTOR (APT28, case-sensitive; .aliases holds vendor names), ATTACK_PATTERN (MITRE ATT&CK; anchor by {name: "T1003"}, which the name index resolves by T-number, or filter {kind: "technique"}; the T-number reads back as .id), DWI_DOMAIN (unjoinable; .onion watch entries)
RPKI & routing observations — ROA (no name; reach it via ROA_AUTHORIZES_ORIGIN / ROA_AUTHORIZES_PREFIX, read .prefix, .asn, .maxLength; .authSource is rpki-roa for an RPKI ROA or irr-route for a routing-registry route object), BGP_PATH_OBSERVATION (name is the hyphen-joined AS path, origin last; via BGP_PATH)
Physical infrastructure — FACILITY, INTERNET_EXCHANGE, SUBMARINE_CABLE, CABLE_LANDING, CDN_POP (akamai:peeringdb:164; group on .operator, .city, .countryCode, not .name), DNS_ROOT_INSTANCE (unjoinable), CLOUD_REGION (thin) (aws:eu-west-1)
Egress, fingerprint & transparency — VENDOR (zoom, okta), TOR_RELAY (keyed by fingerprint), TLS_FINGERPRINT (thin) (ja3:<hash>, jarm:<hash>), CT_OBSERVATION (thin) (certificates live here, never on a Certificate label), DMARC_RECIPIENT
Three markers change how you plan a traversal:
- (unjoinable) — the nodes exist and list, and no edge of any type touches them.
DNS_ROOT_INSTANCEandDWI_DOMAINare in this state: you canMATCHthem, you cannot traverse to or from them. - (thin) — the plane is real and most seeds will miss it.
- (computed) — synthesized at query time; anchor the stored end and walk outward.
A zero-row result on a thin or unjoinable plane means Whisper holds no observation — never that the host has none.
Edge types — with directions
Directions are strict: a wrong-way traversal returns zero rows with no error. The arrow below is the stored direction; traverse backwards with <-[:EDGE]-.
DNS & web
| Edge | From → To | Notes |
|---|---|---|
RESOLVES_TO | HOSTNAME → IPV4/IPV6 | Forward only. Reverse DNS: (ip)<-[:RESOLVES_TO]-(h). There is no PTR edge |
ALIAS_OF | HOSTNAME → HOSTNAME | CNAME |
CHILD_OF | HOSTNAME/EMAIL → HOSTNAME/TLD | child → parent (var-length walks reach the TLD) |
NAMESERVER_FOR | HOSTNAME → HOSTNAME | server → domain. A domain's NS: (d)<-[:NAMESERVER_FOR]-(ns) |
MAIL_FOR | HOSTNAME → HOSTNAME | server → domain. A domain's MX: (d)<-[:MAIL_FOR]-(mx) |
LINKS_TO | URL → HOSTNAME | This phishing-kit path is served by this host. Anchor the URL by {name} (the path) or bound it with WITH u LIMIT n first. Hostname-to-hostname links are a small sample, not a web layer |
BGP, routing & RPKI
| Edge | From → To | Notes |
|---|---|---|
BELONGS_TO | IPV4/IPV6 → PREFIX | RIR allocation (also FEED_SOURCE → CATEGORY) |
ANNOUNCED_BY | IPV4/IPV6 → ANNOUNCED_PREFIX | computed. The IP → prefix step on the way to the origin AS |
ROUTES | ASN → ANNOUNCED_PREFIX/PREFIX | computed; matches in either direction |
HAS_NAME | ASN → ASN_NAME | asn.name is the AS number; the network name is on ASN_NAME (computed) |
BGP_NEIGHBOR | ASN ↔ ASN | Peering adjacency. Use this, not PEERS_WITH (an older alias). Write it undirected, filter WHERE n <> a; works inside [*1..N] |
BGP_PATH | BGP_PATH_OBSERVATION → ASN | An observed AS path traverses this network; the only route to BGP_PATH_OBSERVATION |
CONFLICTS_WITH | ANNOUNCED_PREFIX → ASN | MOAS conflict (computed) |
ROA_AUTHORIZES_ORIGIN | ROA → ASN | RPKI authorizes origin AS |
ROA_AUTHORIZES_PREFIX | ROA → PREFIX | RPKI authorizes prefix |
OPERATES | TLD_OPERATOR → TLD | Registry operator |
WHOIS & registration
| Edge | From → To | Notes |
|---|---|---|
HAS_REGISTRAR | HOSTNAME → REGISTRAR | Current registrar |
PREV_REGISTRAR | HOSTNAME → REGISTRAR | Historical — track transfers |
HAS_EMAIL | HOSTNAME → EMAIL | WHOIS contact |
HAS_PHONE | HOSTNAME → PHONE | WHOIS contact |
REGISTERED_BY | HOSTNAME/ASN/REGISTERED_PREFIX → ORGANIZATION | Registrant / owning org |
SAME_ORG_AS | ORGANIZATION → ORGANIZATION | Folds a raw registrant string to its canonical company |
Geo
| Edge | From → To | Notes |
|---|---|---|
LOCATED_IN | IPV4/IPV6 → CITY | Chain HAS_COUNTRY for the country |
HAS_COUNTRY | ANNOUNCED_PREFIX/ASN/CITY/IPV4/IPV6/ORGANIZATION/PHONE/PREFIX/REGISTERED_PREFIX → COUNTRY | Country code |
Threat intel & egress
| Edge | From → To | Notes |
|---|---|---|
LISTED_IN | IPV4/IPV6/HOSTNAME → FEED_SOURCE | Carries firstSeen/lastSeen/weight (computed) |
TAGGED_AS | IPV4/IPV6/HOSTNAME/ASN → THREAT_TAG | Malware/campaign family; the only route to THREAT_TAG |
HAS_SIGNAL | IPV4/IPV6/HOSTNAME/ASN/PREFIX → THREAT_SIGNAL_TYPE | Curated infra signal — check which label carries the signal you want |
ATTRIBUTED_TO | HOSTNAME/IPV4/PREFIX → ACTOR | Indicator attributed to a named adversary; read the caveat below |
OPERATES_EXIT_NODE | IPV4 → TOR_RELAY | Tor-exit identity |
DELEGATED_TO | PREFIX/IPV4/VENDOR → VENDOR | Cloud/SaaS operator (distinct from WHOIS owner) |
USES_TECHNIQUE | ACTOR → ATTACK_PATTERN | The curated MITRE ATT&CK mapping — 9,256 edges. Not Whisper's own attribution |
USES_TACTIC | ATTACK_PATTERN → ATTACK_PATTERN | A technique grouped under its tactic |
Email security (SPF / DMARC / DKIM)
| Edge | From → To | Notes |
|---|---|---|
SPF_INCLUDE | HOSTNAME → HOSTNAME | include: (chainable) |
SPF_IP | HOSTNAME → IPV4/IPV6/PREFIX | ip4:/ip6: |
SPF_A / SPF_MX / SPF_EXISTS / SPF_REDIRECT | HOSTNAME → HOSTNAME | Other SPF mechanisms |
DMARC_REPORTS_TO | HOSTNAME → DMARC_RECIPIENT | Where DMARC reports go |
DKIM_SIGNED_BY | HOSTNAME → VENDOR | Mail vendor whose key signs the domain |
EMITS_TLS_FINGERPRINT | IPV4 → TLS_FINGERPRINT | JA3/JARM |
Physical infrastructure & CT
| Edge | From → To | Notes |
|---|---|---|
AS_PRESENT_AT | ASN → FACILITY | Network in a datacenter |
IX_MEMBER | ASN → INTERNET_EXCHANGE | Network at an IXP |
IX_HOSTED_AT | INTERNET_EXCHANGE → FACILITY | IXP's building |
CABLE_LANDS_AT | SUBMARINE_CABLE → CABLE_LANDING | Subsea cable landing |
LANDING_NEAR | CABLE_LANDING → FACILITY | Landing near a facility |
CDN_POP_AT | CDN_POP → FACILITY | CDN PoP in a facility |
FIBER_SEGMENT | FACILITY → FACILITY | Fiber link (traverse undirected for both ends) |
PREFIX_IN_REGION | PREFIX → CLOUD_REGION | Prefix in a cloud region |
SEEN_IN_CT | HOSTNAME → CT_OBSERVATION | Subdomain/SAN discovery (anchor the host) |
Computed edges:
ROUTES,HAS_NAME,CONFLICTS_WITH,ANNOUNCED_BY,LISTED_IN,BGP_NEIGHBOR, andBELONGS_TO → CATEGORYare synthesized at query time. They work inside a variable-length[*1..N]when one endpoint is anchored by name: keep the range tight, filterWHERE n <> aon peering walks, and for a wide fan-out write explicit single hops joined withWITH ... LIMIT. Read per-type counts fromCALL db.relationshipTypes() YIELD type, count, never fromcount(r)over unanchored endpoints.
Direction landmines (memorize these)
| Edge | Right way |
|---|---|
RESOLVES_TO | HOSTNAME → IPV4. No PTR edge — reverse with (ip)<-[:RESOLVES_TO]-(h) |
MAIL_FOR / NAMESERVER_FOR | server → domain. A domain's MX/NS: (d)<-[:MAIL_FOR]-(mx) |
CHILD_OF | child → parent |
ANNOUNCED_BY then ROUTES | IP → origin AS: (ip)-[:ANNOUNCED_BY]->(ap)<-[:ROUTES]-(asn). Never join ROUTES and BELONGS_TO in one pattern |
BGP_NEIGHBOR | symmetric in practice — (asn)-[:BGP_NEIGHBOR]-(peer) WHERE peer <> asn |
LINKS_TO | URL → HOSTNAME; anchor the URL first |
LOCATED_IN | IPV4 → CITY, then CITY-[:HAS_COUNTRY]->COUNTRY; an IP also carries HAS_COUNTRY directly |
Must-know traversal chains
Host → network owner (attribution) — who hosts a domain and on whose AS (ROUTES matches in either direction):
MATCH (h:HOSTNAME {name:"github.com"})-[:RESOLVES_TO]->(ip:IPV4)
-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)-[:ROUTES]->(a:ASN)-[:HAS_NAME]->(n:ASN_NAME)
RETURN ip.name, ap.name, a.name AS asn, n.name AS network LIMIT 5
To bound each stage, put the routing leg in a CALL { WITH ip ... } subquery, or give each WITH stage one computed hop (ANNOUNCED_BY, then ROUTES). A walk this long needs an account, so sign in before you run it.
IP → jurisdiction — (:IPV4)-[:LOCATED_IN]->(:CITY)-[:HAS_COUNTRY]->(:COUNTRY)
IP → origin AS — MATCH (ip:IPV4 {name:"1.1.1.1"})-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)<-[:ROUTES]-(a:ASN) RETURN ap.name, a.name LIMIT 5
Blast radius (one indicator → the campaign) — pivot on shared infra:
MATCH (ip:IPV4 {name:"172.67.75.10"})<-[:RESOLVES_TO]-(h:HOSTNAME)
WITH h LIMIT 25
OPTIONAL MATCH (h)-[:HAS_EMAIL]->(e:EMAIL)<-[:HAS_EMAIL]-(sibling:HOSTNAME)
RETURN h.name, e.name, collect(DISTINCT sibling.name)[..20] AS shared_registrant
Bound the co-tenant set with WITH h LIMIT 25 before the registrant pivot. A trailing LIMIT will not do it: the sibling collect still runs against every co-tenant first, and the [..20] slice is applied only after the whole list is built.
Domain → mail / name servers (reverse traversal) — MATCH (d:HOSTNAME {name:"github.com"})<-[:MAIL_FOR]-(mx) RETURN mx.name LIMIT 10
MOAS / possible BGP hijack — MATCH (p:ANNOUNCED_PREFIX {name:"216.168.228.0/24"})-[:CONFLICTS_WITH]->(a:ASN) RETURN p.name, collect(a.name) LIMIT 25
IP → feeds → categories — (:IPV4)-[:LISTED_IN]->(:FEED_SOURCE)-[:BELONGS_TO]->(:CATEGORY)
ASN → physical footprint — (:ASN)-[:AS_PRESENT_AT]->(:FACILITY) and (:ASN)-[:IX_MEMBER]->(:INTERNET_EXCHANGE)-[:IX_HOSTED_AT]->(:FACILITY)
ASN → RPKI — MATCH (a:ASN {name:"AS13335"})<-[:ROA_AUTHORIZES_ORIGIN]-(r:ROA) WHERE r.authSource = "rpki-roa" RETURN r.prefix, r.asn, r.maxLength LIMIT 10 (a ROA has no name)
Unclassified token → typed entity — CALL whisper.search("1.1.1.1") YIELD kind, name, matchType RETURN kind, name, matchType LIMIT 5
Actor → ATT&CK — MATCH (a:ACTOR {name:"APT28"})-[:USES_TECHNIQUE]->(t:ATTACK_PATTERN) RETURN t.name LIMIT 25
That chain reads the curated MITRE ATT&CK knowledge base — 9,256
USES_TECHNIQUEedges and 872USES_TACTICedges across 1,944 actors and 712 techniques. It is a reference layer, not Whisper's own attribution. The edge that joins it to live infrastructure is sparse:ATTRIBUTED_TO, a published attribution from an indicator to a named group, holds 302 edges. The chain returns technique and tactic rollups. It does not attribute anything.
The Workflows have copy-paste recipes per workflow.
Threat properties on a node
Threat-listed IPV4 / IPV6 / HOSTNAME nodes carry the verdict inline, so one anchored read gives the whole posture — no extra hops:
MATCH (ip:IPV4 {name:"185.220.101.1"})
RETURN ip.threatScore, ip.threatLevel, ip.isThreat, ip.isTor, ip.isAnonymizer LIMIT 1
threatScore(numeric),threatLevel(NONE…CRITICAL), and the flagsisThreat,isTor,isAnonymizer.- Name the properties you read.
RETURN ipmay leave the reconciled verdict fields out for speed and say so with aprojection-verdict-omittedadvisory;projectionFull: trueon the request returns the full surface. - For the scored reasoning — feeds, weights, factors — call
explain(). ANONE/clean read means "not listed at this granularity," not "safe."
Read
coveragebeforeband. Only known-clean — coverage: known-clean. In coverage, no malicious evidence. licenses the word "clean"; no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. means unknown, which is a different thing again; malicious-evidenced — coverage: malicious-evidenced. In coverage, with positive evidence of malice. and ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways. mean there is evidence, whatever the band says. Full contract: Coverage — what we looked at.
Procedures — one-liners
Call from Cypher with CALL. Quote every argument, and YIELD exact names: a column a procedure does not emit is rejected, not ignored. Full signatures and the complete census (52 procedures) are in Procedures.
| Procedure | Exact YIELD columns | Notes |
|---|---|---|
explain("indicator") | indicator, type, found, score, level, explanation, factors, sources, coverage | IP, host, ASN, CIDR, file hash or CVE id; read coverage before level. Multi-shape, so YIELD * is rejected; sources[] carry feedId, weight, firstSeen, lastSeen |
whisper.assess("host") / whisper.assess([hosts]) | host, label, band, sub_labels, signals, coverage, evidence, verdictScore, isThreat, threatSources, company_apex, company_linkage, company_name, company_sector, company_hq_country, pl_generation | Verdict plus coverage (what we looked at) and evidence[] (why). Single string or list; a URL folds to its host. The last six columns describe the company behind the host; without company data on your access, company_name, company_sector and company_hq_country come back empty |
whisper.assessUrl(urls) | url, host, path, apex_band, path_band, band, coverage, evidence | Single string or list |
whisper.enrich([indicators]) | name, owner, country, asn, band, prevalence, coverage, company_apex, company_linkage, company_name, company_sector, company_hq_country, pl_generation | Rows are de-duplicated by name, not aligned to your input: join by name. owner is a network attribution |
whisper.identify("host") | host, vendor_id, canonical_name, category, confidence, roles, host_class, band, company_apex, company_linkage, company_name, company_sector, company_hq_country, pl_generation | Whose infrastructure this is |
whisper.walk("host"[, depth, budget]) | host, no_atlas_match, nearest_known_vendors, coverage | Structural neighbourhood when identify has no direct match |
whisper.origins("domain"[, options]) | ip, confidence, methods, asnName, kind, category, truncated | Real origin IPs behind a CDN/proxy |
whisper.variants("domain") | variant, method, exists, nodeId, label, confidence, confidenceLabel | exists: true means registered, not malicious |
whisper.resolve("host") | host, a, aaaa, coverage | |
whisper.search("token"[, options]) | query, kind, name, matchedField, matchType, warning | Bounded lookup of an unclassified token; options types, mode, suffix, limit |
whisper.history.whois("domain") | indicator, registrableDomain, registrar, registrant, country, createDate, updateDate, expiryDate, nameServers | Stable WHOIS shape; a subdomain folds to its apex with a whois-parent-fold advisory |
whisper.history.bgp("ip|asn|prefix") | indicator, type, origin, prefix, startTime, endTime, visibility, peersSeing, cached | Stable routing shape (note the spelling peersSeing) |
whisper.history("indicator") | one shape or the other | Multi-shape: YIELD within one shape, or call a single-shape variant above |
whisper.lookupTlsFingerprint("hash") | indicator, found, kind, hash, category, label, family, vendor, client, sourceCount, firstSeen, lastSeen | ja3:/jarm: prefix optional; a hostname returns found: false |
whisper.lookupTorRelay("ip") | indicator, found, fingerprint, exitAddresses, exitAddressCount, exitAddressesV6, exitAddressCountV6, source, ingestedAt | |
whisper.asnThreatDensity("AS13335") | asn, listedIps, announcedIpv4, routedPrefixes, densityRatio, coverage | |
whisper.topAsnsByPrefixCount(n) | asn, prefixCount | Integer argument |
whisper.psl.tldPlusOne("host") / whisper.psl.isPublicSuffix("name") | apex / result | Registrable apex and public-suffix test |
db.labels() / db.relationshipTypes() / db.schema() | label / type, count, sourceLabels, targetLabels, … / schema rows | Introspect the live schema before anchoring — cheap, and they answer immediately. The column is type, not relationshipType |
explain()auto-detects the indicator type.exists: truefromwhisper.variants()means registered, not malicious — pivot the hit throughexplain()for a verdict. A successful response may also carry a top-leveladvisories[]array (whois-parent-fold,enrich-semantics,null-pagination-param,projection-verdict-omitted, …): read it rather than parsing rows.
Query rules (one line each)
| Do this | Not that |
|---|---|
Anchor on name: MATCH (h:HOSTNAME {name:"example.com"}) | MATCH (h:HOSTNAME) WHERE h.name CONTAINS "example" |
| Lowercase the value in your code | toLower() around the anchor |
Prefix match: WHERE h.name STARTS WITH "mail." | Regex: WHERE h.name =~ "^mail\\..*" |
Suffix match: WHERE h.name ENDS WITH ".example.com" | Broad ENDS WITH "example.com" (scan) |
CALL whisper.search("token") for an unclassified token | CONTAINS across an unanchored label |
Always add LIMIT, including on CALL ... YIELD ... RETURN | Open-ended traversal on a billion-node label |
WITH x LIMIT n before the fan-out, then collect | A trailing LIMIT after an unbounded expansion |
UNWIND [...] AS n MATCH (h:HOSTNAME {name: n}) | One request per indicator |
GET /api/query/stats for global counts | MATCH ()-[r]->() RETURN count(r) |
OPTIONAL MATCH for sparse WHOIS fields | Mandatory MATCH (drops rows silently) |
-[:BGP_NEIGHBOR]-(n) WHERE n <> a | PEERS_WITH |
count(DISTINCT p) across an announced-prefix chain | RETURN DISTINCT over the same chain |
Confirm with CALL db.labels() before anchoring | Guessing a Domain/fqdn that doesn't exist |
CALL whisper.identify("host") (quoted) | CALL whisper.identify(host) |
CALL explain(ip) for scoring | Manual ASN→PREFIX→IP→LISTED_IN walks on a large network |
Speed guide: anchored point lookups = instant · STARTS WITH / narrow ENDS WITH ".dom" = fast · anchored multi-hop with OPTIONAL MATCH = seconds · unanchored label scans, unanchored URL expansion, and regex =~ over HOSTNAME = avoid.
Send one
Sign in to get a key, then send it in X-API-Key. Getting Started walks the setup.
curl -s -A "whisper-client/1.0" https://graph.whisper.security/api/query \
-H "Content-Type: application/json" \
-H "X-API-Key: $WHISPER_API_KEY" \
-d '{"query":"MATCH (h:HOSTNAME {name:\"www.google.com\"})-[:RESOLVES_TO]->(ip) RETURN h.name, ip.name LIMIT 3"}'
See also the Threat Feeds & Categories reference (134 feeds / 32 categories) and the HTTP API for the full endpoint reference.