Procedure Reference
The complete calling contract for every stored procedure: how CALL binds its arguments, and the exact YIELD columns of every family.
On this page (16)
Procedure Reference Documentation
The complete calling contract for every stored procedure the engine registers. Each entry gives the argument the procedure takes and the YIELD columns it emits, in the order the engine emits them, so you can write the YIELD clause without guessing. Seven procedures have a page of their own with worked examples; they are listed on Procedures. Several also have a matching tool on the MCP server.
Calling a procedure
CALL runs a procedure in three forms: standalone, with YIELD to pick and filter columns, or once per row when the argument comes from UNWIND, WITH or MATCH. A standalone call with no YIELD returns every column the procedure produces. Full clause syntax is in Syntax & Clauses.
CALL whisper.variants("paypal.com")
YIELD variant, method, exists, confidenceLabel
WHERE exists
RETURN variant, method, confidenceLabel
LIMIT 6
UNWIND ["google.com", "cloudflare.com"] AS d
CALL whisper.psl.tldPlusOne(d) YIELD apex
RETURN d, apex
Five rules cover nearly every failed call:
- Quote every argument.
CALL whisper.identify(ubuntu.com)is rejected as a bad argument, and an unquoted IPv6 literal is parsed as something else entirely and comes back as a syntax error. AlwaysCALL whisper.identify("ubuntu.com"). - Argument types matter as much as names.
whisper.topAsnsByPrefixCount(10)takes an Integer;whisper.explain.bundletakes one string and rejects a list;whisper.exporttakes exactly one map. YIELDcolumns are exact contracts. A column the procedure does not emit is rejected, not ignored, and the message names the valid columns.db.relationshipTypes()yieldstype, notrelationshipType.- Multi-shape procedures need named columns.
explainandwhisper.historychange their column set with the indicator, soYIELD *is rejected, and so is aYIELDthat mixes shapes. Name columns from one shape, or call the single-shape variant:whisper.explain.bundle,whisper.history.whois,whisper.history.bgp. - A URL folds to its host.
whisper.identify,whisper.assess,whisper.walkand the history procedures readhttps://host/path?q=1ashost.whisper.assess,whisper.assessUrl,whisper.identifyandwhisper.enrichtake a single string or a list.
The procedures
Every procedure the engine registers, grouped by what it answers. The YIELD columns are exact contracts: a column the procedure does not emit is rejected, not ignored.
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.whisper.explaindoes not returncoverageat all. Full contract: Coverage — what we looked at.
Threat & verdict
| Procedure | Argument | YIELD columns | Read it like this |
|---|---|---|---|
explain(indicator), also whisper.explain | one string: IP, hostname, ASN, CIDR, file hash or CVE id | indicator, type, available, cached, found, score, level, explanation, factors, sources, breakdown, advisory, verdictScore | Multi-shape; name the columns. sources[] entries carry feedId, weight, firstSeen, lastSeen |
whisper.explain.bundle(indicator) | one string, never a list | verdict | One map column that never shifts with indicator type; reach in with verdict.level, verdict.score, verdict.found, verdict.explanation |
whisper.assess(hosts) | a list or a single string; a URL folds to its host | host, label, band, sub_labels, signals, coverage, evidence, verdictScore | Read coverage before band |
whisper.assessUrl(urls) | a list or a single string | url, host, path, apex_band, path_band, band, coverage, evidence | coverage describes the path, not the host |
whisper.enrich(names) | a list or a single string | name, owner, country, asn, band, prevalence, coverage | Rows are de-duplicated by canonical name, so join back by name, never by position. owner is the network operator of the resolved IP's origin AS: a network attribution, never a threat attribution. prevalence is a popularity rank where lower is more prevalent and null is unranked |
History
| Procedure | Argument | YIELD columns | Read it like this |
|---|---|---|---|
whisper.history(indicator) | one string | WHOIS columns for a domain; routing columns for an IP, ASN or prefix | Multi-shape: YIELD within one shape, or call a variant below |
whisper.history.whois(domain) | one string; a URL folds to its host and a subdomain to its registrable apex | indicator, type, queryTime, createDate, updateDate, expiryDate, registrar, registrant, country, nameServers, cached, registrableDomain | A fold adds a whois-parent-fold advisory to the response |
whisper.history.bgp(indicator) | one string: an IP, ASN or prefix | indicator, type, origin, prefix, startTime, endTime, visibility, peersSeing, cached | Note the spelling of peersSeing |
Attribution & discovery
| Procedure | Argument | YIELD columns | Read it like this |
|---|---|---|---|
whisper.identify(hosts) | a list or a single string; a URL folds to its host | host, vendor_id, canonical_name, is_canonical, confidence, category, roles, band, host_class, evidence | Who runs the host, not whether it is legitimate. Large batches are rejected, not truncated |
whisper.walk(host[, depth, budget]) | a string, then optional Integers | host, no_atlas_match, nearest_known_vendors, siblings, coverage, arms | coverage is presence, not a verdict: structural-onlywalk — a whisper.walk value describing atlas adjacency. Not an assess coverage value. Do not gate on it., no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. or deadline-hit |
whisper.origins(domain[, options]) | a string, optional map {include_related: true} | ip, confidence, methods, asn, asnName, kind, category, truncated | confidence runs 0.0 to 1.0; passive, nothing touches the target |
whisper.resolve(host) | one string | host, a, aaaa, freshest_observation_ms, coverage | Current A and AAAA records from passive data |
whisper.search(token[, options]) | a string, optional map with types, mode, suffix, limit, timeoutMs | query, kind, name, matchedField, matchType, warning, score | The bounded front door for a token you cannot classify |
whisper.audit.malformedHostnames(zone) | one string | clean, malformed, total, samples, scope, truncated | Splits a zone's children into clean and malformed names |
whisper.variants(domain) | a string; optional node label or false as the filter | variant, method, exists, confidence, confidenceLabel | exists: true means registered, not malicious. Also callable as a function in expression position |
whisper.lookupTlsFingerprint(hash) | a string: a bare hash or kind:hash | indicator, found, kind, hash, category, label, family, vendor, client, trustTier, sourceCount, firstSeen, lastSeen, licensePosture | found: false is a populated row |
whisper.lookupTorRelay(ip) | a string: an exit IP or a relay fingerprint | indicator, found, fingerprint, exitAddresses, exitAddressCount, exitAddressesV6, exitAddressCountV6, source, ingestedAt | found: false is a populated row |
whisper.danglingCname(hosts) | a string or a list | host, target, target_apex, target_apex_state, observed_at | Zero rows on a clean host; target_apex_state: UNREGISTERED is the takeover signal |
CVE plane
| Procedure | Argument | YIELD columns | Read it like this |
|---|---|---|---|
whisper.cve.byPackage(cpe) | one full CPE 2.3 string | cve, band, kev, ransomware, epss, cvss, coverage | Always at least one row. A spec it cannot read, such as a Package URL, returns one row with coverage: "unsupported-spec" and everything else null |
whisper.vulnPosture(target) | a hostname, an ASN, or a map with cves, packages or cpes | openCveCount, scoredCount, critical, high, medium, low, kevCount, ransomwareCount, maxEpss, maxCvss, priority, coverage | Always exactly one row; read coverage before the counts |
Infrastructure & BGP
| Procedure | Argument | YIELD columns | Read it like this |
|---|---|---|---|
whisper.asnThreatDensity(asn) | one string, "AS13335" | asn, listedIps, announcedIpv4, densityRatio, routedPrefixes, coverage | Listed addresses against announced space |
whisper.asnCountries(n) | Integer | country, asns | ASN count per registration country |
whisper.topAsnsByPrefixCount(n) | Integer | asn, prefixCount | The networks announcing the most prefixes |
whisper.bgpDegreeDistribution() | none | inDegree, outDegree, asnCount | A histogram: one row per degree pair |
whisper.asSet(name) | one string, an IRR as-set name | asSetName, memberAsn, sourceRir | Membership of an IRR as-set, one row per member ASN |
Public Suffix List
| Procedure | Argument | YIELD columns | Read it like this |
|---|---|---|---|
whisper.psl.tldPlusOne(host) | one string | apex | The registrable apex (eTLD+1) |
whisper.psl.isPublicSuffix(name) | one string | result | A single boolean |
whisper.psl.affiliation(host) | exactly one string | found, suffix, submitterLogin, submitterOrg, evidenceKind, confidence | found: false is a populated row |
Threat-intel snapshot candidates
| Procedure | Argument | YIELD columns | Read it like this |
|---|---|---|---|
whisper.threatIntel.candidateCdnApex(n) | Integer | apex, subCount, certCount, wildcardCount, isOnPslPrivate, recommendation, computedAt | Precomputed CDN and multi-tenant apex candidates |
whisper.threatIntel.candidateMultiTenantApex(n) | Integer | name, nodeId, subCount, threatSources, threatScore, isOnDenyList, recommendation, computedAt | Zero rows means the snapshot holds no candidates of this class |
whisper.threatIntel.candidateSharedHostingIp(n) | Integer | ip, nodeId, hostCount, threatSources, threatScore, isAlreadyMarked, recommendation, computedAt | Same |
Bulk export
| Procedure | Argument | YIELD columns | Read it like this |
|---|---|---|---|
whisper.export(options) | exactly one map {label, limit, cursor}; label is required and is malicious, ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways. or benign-allowlisted | host, label, ip, cidr, asn, url_paths, cert_shas, tls_fingerprints, dns, last_seen, coverage, truncated, supersedes, look_alike_negatives, next_cursor | Always pass limit; page by feeding a row's opaque next_cursor back as cursor |
Your own context
| Procedure | Argument | YIELD columns | Read it like this |
|---|---|---|---|
whisper.quota() | none | key, value | One row per key describing your own service context: who the server takes you for and whether it recognised your key. Ask the key, never a page |
Every procedure
The tables above are the ones worth learning first. This one is the whole surface, generated from CALL db.procedures() — every procedure the engine registers, whether or not a page has been written about it. Mode is the engine's own, and it describes the ENGINE, not this API: READ answers a question; WRITE names a procedure that would change something, and that you cannot call from here. The public Cypher endpoint rejects mutating calls, and the MCP server exposes read-only tools only — it has no contribution or feedback tool. There is no route by which a reader of this page writes to the graph.
| Procedure | Mode | What it does |
|---|---|---|
db.functions | READ | List all available Cypher functions (this procedure) |
db.labels | READ | List all node labels with row counts |
db.procedures | READ | List all registered procedures (this procedure) |
db.propertyKeys | READ | List all property keys |
db.relationshipTypes | READ | List all relationship types with source/target labels |
db.schema | READ | Full schema description (labels + types + counts) |
db.schema.nodeTypeProperties | READ | Per-node-label property index |
db.schema.relTypeProperties | READ | Per-relationship-type property index |
db.schema.visualization | READ | Schema graph for visualization |
dbms.components | READ | Server component listing (Neo4j-driver compat) — one row {name='whisper-ng', versions=[<ver>], edition='community'}. |
explain | READ | Threat-assessment explanation for an indicator (IP, hostname, ASN, CIDR). level enumerates {NONE, INFO, LOW, MEDIUM, HIGH, CRITICAL}… |
whisper.asSet | READ | IRR as-set MEMBERSHIP lookup (NOT asset management) — one row per member ASN of the named as-set, served from the local IRR snapshot; no upstream call. |
whisper.asnCountries | READ | ASN count per country ((:ASN)-[:HAS_COUNTRY]->(:COUNTRY)); one row {country, asns} ordered by count DESC… |
whisper.asnThreatDensity | READ | Per-ASN threat density — one row {asn, listedIps, announcedIpv4, densityRatio, routedPrefixes… |
whisper.assess | READ | Maliciousness-verdict surface for a list of hosts — one row per host with {host, label, band, sub_labels[], signals[], coverage, evidence[], verdictScore, isThreat… |
whisper.assessUrl | READ | URL-scoped maliciousness-verdict surface for a list of URLs — one row per URL {url, host, path, apex_band, path_band, band, coverage… |
whisper.audit.malformedHostnames | READ | Per-zone HostnameValidator audit — partitions a bounded CHILD_OF scan into clean/malformed buckets. |
whisper.bgpDegreeDistribution | READ | Global BGP AS-adjacency degree DISTRIBUTION — one row per (in,out) degree bucket {inDegree, outDegree, asnCount} (all Long)… |
whisper.cve.byPackage | READ | Affecting-CVE listing for ONE package/cpe — whisper.cve.byPackage(spec) where spec is a cpe:2.3 string, {cpe:'cpe:2.3:...'}, or {name, os, osVersion} (distro key). |
whisper.danglingCname | READ | Host-anchored dangling-CNAME feed lookup — whisper.danglingCname(host |
whisper.enrich | READ | Batched endpoint enrichment for a list of names — whisper.enrich(name[]) returns ONE order-preserving row per canonical name {name, owner, country, asn, band, prevalence, coverage}… |
whisper.explain | READ | Alias for explain |
whisper.explain.bundle | READ | Threat-assessment as a single {verdict: Map} column (single-shape variant of explain) |
whisper.export | READ | Read-only bulk export of the threat corpus by label (malicious, ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways., benign-allowlisted), for classifier distillation. |
whisper.history | READ | Historical WHOIS / BGP data for an indicator (auto-pivot) |
whisper.history.bgp | READ | BGP routing history for IP / ASN / prefix (type-strict, single-shape) |
whisper.history.whois | READ | Domain WHOIS history (type-strict, single-shape) |
whisper.identify | READ | Host-first vendor attribution over the GOLD RESOLVES_TO->IPV4->DELEGATED_TO->VENDOR path, with an ORIGIN_AS org-graph long tail. |
whisper.lookupTlsFingerprint | READ | TLS handshake fingerprint lookup — probes all 8 kinds (ja3/ja4/ja4s/ja4h/ja4x/ja4t/ja4tscan/jarm) or accepts a kind:hash composite. |
whisper.lookupTorRelay | READ | Tor exit-relay lookup — dual-input (a 40-hex Ed25519 fingerprint OR a single exit IPv4/IPv6 address). |
whisper.origins | READ | Discover candidate origin IPs behind a CDN, scored by independent evidence |
whisper.psl.affiliation | READ | PSL submitter-affiliation lookup by private suffix or hostname. |
whisper.psl.isPublicSuffix | READ | True if the input matches a Public Suffix List entry. |
whisper.psl.tldPlusOne | READ | Registrable apex (eTLD+1) lookup via the Public Suffix List. |
whisper.quota | READ | Where the calling key stands right now. Ask the key, never a page. |
whisper.resolve | READ | Read-only DNS resolution for a single host — whisper.resolve(host). |
whisper.search | READ | Bounded analyst search — routes an untyped token to an exact index lookup (IPv4/IPv6/CIDR/ASN/hostname/exact ASN-name), a bounded FST prefix scan… |
whisper.submit | WRITE | Contribute an observation back — an indicator or a corroboration receipt. It is a write, so it needs a signed-in key. |
whisper.threatIntel.candidateCdnApex | READ | Top-K precomputed CDN / multi-tenant CA apex candidates from CT + PSL grouping. |
whisper.threatIntel.candidateMultiTenantApex | READ | Top-K precomputed multi-tenant apex candidates from the threat-intel snapshot. |
whisper.threatIntel.candidateSharedHostingIp | READ | Top-K precomputed shared-hosting IPV4 candidates from the threat-intel snapshot. |
whisper.topAsnsByPrefixCount | READ | Top-N ASNs ordered by announced-prefix count; served O(1) from a precomputed snapshot refreshed at BGP cadence. |
whisper.variants | READ | Lookup variants of a hostname/domain |
whisper.version | READ | Server version + build time — one row {version, buildTime}. |
whisper.vulnPosture | READ | SBOM/CVE-set posture — whisper.vulnPosture({cves:[...], packages:[{name,version,ecosystem}], cpes:[...], os, osVersion}). |
whisper.walk | READ | Structural-neighborhood fallback for a NOVEL host (whisper.walk(host[, depth[, budget_ms]])). |
whisper.watch | WRITE | Create, list and cancel subscriptions to a query, a verdict or an indicator. |
The
WRITErows are engine capabilities, not something you can call here.whisper.submitandwhisper.watchare registered by the engine and reported bydb.procedures(), which is why they appear in this table. They are not reachable through the MCP server, which exposes read-only tools and refuses write procedures by name, and not through the public Cypher endpoint, which rejects any mutating call. There is no contribution or feedback tool on the MCP surface. Treat this column as a statement about the engine, not an invitation.
Rows generated from CALL db.procedures() YIELD name, signature, description, mode RETURN name, signature, description, mode ORDER BY name against https://graph.whisper.security, fetched 2026-09-20T01:36:24Z.
Read the advisories channel
A successful response can carry a top-level advisories[] array beside columns, rows and statistics. Each entry has a kind, a human message and, where they apply, the queried input and the resolved value. It lives on the response envelope, not in a row, so it survives any YIELD or RETURN projection, and the key is omitted when there is nothing to say: test for its presence rather than expecting an empty array. The kinds you will meet on this surface:
kind | Emitted by | What to do |
|---|---|---|
enrich-semantics | whisper.enrich | Read it once. It restates how owner, prevalence and row de-duplication work |
whois-parent-fold | whisper.history.whois | queried was folded to resolved; the WHOIS shown belongs to the registrable parent |
explain-verdict-axis-unavailable | explain on an ASN | score and level are placeholders on that row. Read breakdown.reputationScore and breakdown.reputationCategory, and do not compare them with a threat band |
explain-score-unavailable | explain | The score column holds no usable value for that row. Read level, explanation and factors[] |
origins-all-candidates-withheld | whisper.origins | Every candidate was contextual CDN or shared-provider infrastructure. Re-run with {include_related: true} to see them, labelled with the reason |
projection-verdict-omitted | a query that projects a node without its verdict fields | If the verdict is what you need, project verdictLevel and verdictCoverage, or call whisper.assess |
When to prefer a procedure over a traversal
Reach for the procedures first. They answer the hardest questions in one call, usually faster and cleaner than a hand-written deep traversal.
- The logic runs server-side.
explain()computes a score from feed count, feed weights, recency and the age of the listings, and hands back the arithmetic infactors[]with the named feeds insources[]. Reproducing that by walkingLISTED_INedges yourself takes more hops and gives you less evidence. - A procedure replaces a slow scan. Where a hand-written query walks the graph itself, a procedure does the same work in one call, which makes it the standard fix for a query that runs long — alongside anchoring the query and adding a
LIMIT. See Best Practices. - The output is decision-ready. A procedure returns labeled columns you can paste straight into a ticket.
One caveat: BGP routing history over a large network is slow. Keep a LIMIT on whisper.history.bgp() calls and expect a longer round trip.
Access
Some of these calls need an API key. Pass it in the X-API-Key header; sign in to get one — there is no card to enter.
Schema introspection
The db.* procedures describe the live schema, so you can confirm a label or edge exists before you anchor on it.
CALL db.labels() YIELD label RETURN label ORDER BY label LIMIT 12
db.labels() lists every node label with its count. db.relationshipTypes() lists every edge type as type (not relationshipType) with its source and target labels, and flags an edge that is declared but currently empty. db.propertyKeys() lists every property name in use. db.schema() returns a structured overview of the whole graph and accepts a format argument ("json", "markdown", or "details"), which collapses the result into a single schema column; db.schema.nodeTypeProperties() and db.schema.relTypeProperties() list the properties on each label and edge type. db.functions() and db.procedures() list the callable surface itself. The full label, edge, and property model is on the Graph Schema pages.
| Procedure | Argument | YIELD columns |
|---|---|---|
db.labels() | none | label, count |
db.relationshipTypes() | none | type, count, sourceLabels, targetLabels, aliasOf, declaredButEmpty, sparseSourceLabels |
db.propertyKeys() | none | propertyKey |
db.schema() | none, or one of "json", "markdown", "details" | without an argument type, name, count, description, example, sourceLabels, targetLabels, fastPatterns, slowPatterns, bestPractices; with a format, a single schema column |
db.schema.nodeTypeProperties() | none | nodeType, nodeLabels, propertyName, propertyTypes, mandatory |
db.schema.relTypeProperties() | none | relType, propertyName, propertyTypes, mandatory |
db.schema.visualization() | none | schema |
db.functions() | none | name, signature, description, category |
db.procedures() | none | name, signature, description, mode |
dbms.components() | none | name, versions, edition |
whisper.version() | none | version, buildTime |