Skip to contentSkip navigation

Exporting at volume

whisper.export pulls the reconciled threat corpus a page at a time, by tier, with a cursor — for training a classifier or seeding a local store.

On this page (8)

Exporting at volume Documentation

whisper.export is the bulk read. It walks the reconciled threat corpus by tier and hands back one row per indicator, paginated by cursor.

Key concepts: Threat Intelligence, Reconciled Verdict, Indicator of Compromise.


What it is for

Distilling a classifier, seeding a local indicator store, or taking a periodic snapshot of the corpus at a given tier. It is not a substitute for a query: if you want indicators matching a condition, write Cypher. export is for the whole tier.


The label is a closed set

cypher · runnablegraph.whisper.securitySign in to run
CALL whisper.export({label: "malicious", limit: 2})
YIELD host, label, ip, asn, last_seen, coverage, truncated, next_cursor
RETURN host, label, ip, coverage, truncated

label takes one of three values: malicious, ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways., benign-allowlisted.

unknown is rejected, and the reason is worth knowing: it would be an unbounded scan of the whole graph rather than a read of a corpus.


Every row is reconciled before it is emitted

A candidate is emitted only when its reconciled tier matches the label you asked for. That is what keeps a URL-scoped listing on a multi-tenant apex out of the malicious export — it reconciles to ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways. and lands there instead.

Two of the columns are forward projections rather than facts about the indicator itself:

  • supersedes — the host's superseded registrar-lineage targets.
  • look_alike_negatives — confusable-but-clean neighbours. These are the hard negatives, and they are the reason the export is usable for distillation rather than only for blocklisting.

tls_fingerprints entries are family-tagged: ja3:, ja4: or jarm: followed by the hash.

CSR-derived fields are confirmed-only and come back null when absent, rather than guessed.


Cursor pagination

Each page carries an opaque next_cursor. Pass it back to continue:

cypher
CALL whisper.export({label: "malicious", cursor: "<the next_cursor from the previous page>"})
YIELD host, label, coverage, next_cursor
RETURN host, label, coverage, next_cursor

The cursor is opaque. Do not parse it, do not construct one, and do not assume it survives a schema change — treat it as a token you received and hand back.


truncated is replicated on every row

truncated: true means the page you are holding is a prefix, not the whole answer. It is set on every row of a truncated page rather than once at the end, so a consumer that streams rows and never sees the last one still knows.

Resuming: keep the last next_cursor you successfully processed, not the last row. A cursor identifies a position; a row does not.


Choosing a page size

limit shapes the page. Smaller pages mean more round trips and a shorter deadline per call; larger pages mean fewer round trips and a longer one. Start small enough that a single call comfortably completes, then increase it until the round-trip count stops mattering. The call is deadline-bounded, so an over-large page comes back truncated rather than failing — which is the behaviour to design for rather than to tune around.


When to use the graph API instead

You wantUse
Every indicator at a tierwhisper.export
Indicators matching a pattern, a layer or a pivotCypher — see Recipes
One verdict for one indicatorwhisper.assess
A verdict for a URLwhisper.assessUrl