Adverse media
Transparency

Adverse media — exactly how it works, and what it does not do.

Adverse-media screening looks for negative news about your subject: financial crime, regulatory action, cyber offences, organised crime. This page documents the whole pipeline — where the articles come from, how a subject is bound to an article, what evidence you get back, what the Unavailable status means, and the limits we have measured and not hidden.

The short version. The source is GDELT, an open global news-monitoring project. Evidence is indexed from GDELT's own 15-minute bulk files by a background tailer and historical backfill, so a screening request never calls the news source over the network. A hit comes back with a headline, a publisher, a date, a category and the person entity that matched. If our index has no evidence for a subject, we say Unavailable — we did not check — instead of returning a clean pass.
This is continuous monitoring, not point-in-time screening. Everything else on this page follows from one property: evidence is gathered before your screen, never during it. That makes the control fast, outage-proof and honest — and it makes it useless on a name we have not met yet. Screen someone today and you will very likely get Unavailable; put the same subject under monitoring and the coverage accrues behind them. Judge the feature against the job it actually does: ongoing monitoring of a known book of subjects. If your workflow is onboard a stranger and decide in this request, adverse media will return Unavailable essentially every time, and you should not price it into that decision.

What a row means — read this before you read a row. A row says one thing: this article mentions this name, and this article carries this theme. GDELT links names to themes across a whole document, so the name and the theme need never appear near one another — the person may be quoted as a commentator, or named in an unrelated paragraph. That binding is document-level co-occurrence. A row is not an allegation, and it is not a finding about the person.

Read the theme sceptically too. 54.5% of the records that clear the gate are admitted by a catch-all rule and stamped FinancialCrime, so that theme on its own tells you very little. Two rows carrying FinancialCrime can have arrived by different routes: one because GDELT tagged the article with a specific financial-crime theme, the other because it tagged nothing more precise and the catch-all admitted it. The label does not record which. Do not total themes into a per-person verdict — the API withholds the keys such a total would need: no name identifier, no article identifier, no count, no score.

1 · Where the data comes from

We use GDELT — the Global Database of Events, Language and Tone — a free, open news monitoring project that indexes worldwide news coverage. We use three of its surfaces:

SurfaceWhat it gives usHow we use it
GDELT GKG 2.0
data.gdeltproject.org
The entities GDELT itself extracted from each article — including the person entities named in it — plus themes and a document tone score. Evidence. Tells us whether GDELT's own extractor identified your subject as a person named in that article.
GDELT bulk 15-minute slices
data.gdeltproject.org
Every GKG record GDELT published in a 15-minute window — roughly 3 MB per slice, downloaded whole. Forward coverage. Tails the live news firehose and matches each slice against the subjects we already know, so new coverage lands without asking the rate-limited search API for anything.

GKG is what makes the feature work at all. DOC matches an article's full text but only hands back the headline — and real adverse coverage very often does not name the subject in the headline. A genuine hit like “Goldman Sachs settles shareholder lawsuit over 1MDB scandal” names no person at all. Reading GDELT's extracted person entities recovers exactly those articles.

Why GDELT — the licence, not just the price. GDELT — the Global Database of Events, Language and Tone — is a public, near-real-time index of world news. We chose it substantially for its licence. Most news and risk-data sources forbid reselling results derived from them, which disqualifies them outright for a product we license to other companies: adverse-media-expansion.md §2.2 rejected OpenSanctions' bulk data on exactly that ground (CC BY-NC 4.0, no exemption for commercial users). GDELT's terms are the opposite, and they are worth quoting rather than paraphrasing — see below. The obligation that comes back with that freedom is attribution, which is why a GDELT citation travels with the evidence into the API response, the PDF report and the regulator pack rather than sitting in a footnote. One boundary we hold ourselves to: the licence covers GDELT's own metadata and extracted entities, not the underlying article text. So we store URL, publisher, date, the bound person entity, theme and tone — and never article prose.

Using GDELT

“The GDELT Project is an open platform for research and analysis of global society and thus all datasets released by the GDELT Project are available for unlimited and unrestricted use for any academic, commercial, or governmental use of any kind without fee.”

Redistributing GDELT

“You may redistribute, rehost, republish, and mirror any of the GDELT datasets in any form. However, any use or redistribution of the data must include a citation to the GDELT Project and a link to this website (https://www.gdeltproject.org/).”

Quoted verbatim from gdeltproject.org/about.html, fetched 2026-07-20 and recorded in our own feasibility study rather than paraphrased from memory.

2 · Our own index — a screen never calls the network

Adverse media is not fetched live during your request. A background GKG-file tailer plus a one-time historical backfill populate our own index; the screening request reads that index and nothing else. The screening-time provider has no HTTP client at all — it is structurally incapable of making a network call.

  1. Background sweep

    Two background workers build our own index. The slice tailer downloads every new 15-minute GKG file and scans it for subjects; the historical backfill indexes past GKG files once, to extend coverage further back. Neither runs inside your request.

  2. Bind the article to a person

    An article is only attributed to your subject if GDELT's own extractor named a person whose tokens match the subject as an ordered, adjacent span within a single extracted person entity. Never a bag of tokens pooled across the article.

  3. Score and store

    What survives the relevance filter is stored as evidence: headline, publisher, date, category, matched person entity, and GDELT's tone (captured for display, never for a decision).

  4. Your screen reads the cache

    POST /v1/screenings/check performs one indexed read against the evidence cache. The screening-time provider is constructed without an HTTP client at all — it is structurally incapable of making a network call.

The first screen of any new person returns nothing. Plan for it. A screen performs one indexed read of our own corpus index (GdeltAdverseMediaProvider.cs:112). A name the system has no coverage for is not fetched during the screen — it is only covered once the slice tailer or a historical backfill reaches it, and the screen returns Unavailable immediately. That is precisely the onboarding moment, which is the worst possible time for a control to be silent, so we would rather you designed around it than discovered it: an applicant's very first adverse-media screen is expected to be Unavailable, and it is not a clean pass.
How fast a new name actually gets covered. There is no per-subject fetch to pace. The 15-minute slice tailer picks up every name mentioned in each new GDELT file as it publishes, so coverage for freshly-published news arrives within one tail cycle. Reaching a name's back-history depends on the historical backfill — a separate, on-demand pass over older GDELT files, not a queued per-subject fetch — so a large book's back-history still takes a backfill run, not minutes.
How far back our own index reaches. Depth is no longer a per-request search parameter — there is no adjustable lookback window in this design. It is a property of what has actually been indexed: our GKG-file index's coverage begins 2026-05-28, plus whatever a historical backfill run has added further back. A subject's older press coverage is only visible once a backfill has reached that period; a screen reports honestly what our index actually holds, never an assumed depth.
The live look-up switch is retired: a screen never calls GDELT. AdverseMedia:LiveFallbackEnabled is obsolete and has no effect. Since 2026-09-11 a screen answers only from our own GDELT corpus index, which the historical backfill and the 15-minute slice-tail fill: no screen makes a network call and no screen reads a prefetch cache. The key is still bound so an old config file keeps loading, and the service logs a boot warning when it is set to anything but its default. A screen with no hits reports Ok only when the index window reaches back to the retention start; over a shorter window it reports Unavailable and states the window it did check.
Why the evidence is indexed ahead of time instead of searched during your screen. GDELT's GKG bulk feed is a file server, not a name-searchable API — it publishes one file every 15 minutes, addressable only by time window, never by a person's name. There is no way to ask it “give me articles about this person” inside a screening request. So evidence has to be built ahead of time: a background tailer downloads each new file as it publishes and a historical backfill works through past files, both indexing the person entities they find. A screen then performs one read against that index — no network call, no per-request search. Historical note: an earlier design used a separate GDELT search API for live per-name lookups. That API rate-limited by IP — one request every five seconds, and refused indistinguishably from “nothing found” under HTTP 200 — and has since been retired in favour of the index-only design described here.
How our own index is built. GDELT publishes a complete batch every 15 minutes of everything it indexed in that window — each article plus the entities its own extractor pulled out of it: people, organisations, locations, themes and a tone score. It is plain HTTP off a static file server (data.gdeltproject.org, GkgOptions.cs:20), no key, no rate limit — measured across multiple sampling runs without a single 429 (GkgSliceClient.cs:22-31). That is 96 batches a day, each a measured mean of ~2.4 MB compressed carrying ~587 articles, ~77% of which name at least one person. Two workers build our index from this feed: a tailer takes every new batch as it publishes, checks each article against the names already in the monitoring book, keeps what binds, and discards the rest — nothing from a slice is stored beyond the matched rows (GkgSliceClient.cs:28-31, GkgRecord.cs:33-38); and a historical backfill works back through the same free, unmetered archive — verified reachable years back over the same free HTTP (a June 2019 slice fetched clean: HTTP 200, 7.9 MB, no key, no throttle) — to extend our index's coverage further back than the tailer alone would reach. State the consequence plainly: we are not short of data. We receive all of it and filter down to names we already track. Our index's coverage is exactly what the tailer and the backfill have actually reached, never an assumed depth — see the coverage statement for the current floor.
Nothing here is metered. GDELT's bulk feed is a static file, published on a fixed schedule, and nobody rate-limits reading it. That is the only route our index uses: a tailer reads each new file as it publishes, and a historical backfill reads older files on the same free, unmetered path. A tenant that brings their own GDELT-compatible endpoint configures it independently of this index — a separate, optional source, not a fallback for it.
What one 15-minute batch actually looks like. Readers are repeatedly told we ingest GDELT's bulk files; here is one. The whole addressing scheme is that the filename is the timestamp. 20260720124500.gkg.csv.zip is the window beginning 2026-07-20 12:45 UTC — change the digits and you have another slice, which is exactly what GkgSliceClient.cs:72 does when it builds {BaseUrl}/{sliceStamp}.gkg.csv.zip against http://data.gdeltproject.org/gdeltv2. There is no index to crawl and nothing to authenticate against: a tiny pointer file, lastupdate.txt, always names the three newest files with their sizes and MD5s. GDELT publishes three files per window — .export. (events), .mentions. and .gkg. — and we take only .gkg., the one carrying extracted entities. It is a static file server, not an API (GkgSliceClient.cs:20): no key, no quota, and no query — you cannot ask it for a name, only for a time window.

Measured on 2026-07-20, the slice above was 4.7 MB zipped, 15 MB of CSV, 1,074 articles — tab-separated, one article per row. Slices vary in size; this one runs roughly double the ~2.4 MB / ~587-article mean quoted earlier, which is the normal spread of a news day rather than a discrepancy.

A single real row from that slice, trimmed to the fields we actually read:

URL       https://www.intelligencer.ca/news/controversial-conference-presentation-sparks-protest…
SOURCE    intelligencer.ca
THEMES    PROTEST;TAX_FNCACT;EDUCATION;SOC_POINTSOFINTEREST_UNIVERSITY;EPU_POLICY…
PERSONS   person-a; person-b; person-c; person-d; person-e; person-f   (6 extracted — REDACTED)
ORGS      hebrew university;instagram;goodes hall at queen university…
TONE      -3.2590051457976,2.57289879931389,5.83190394511149,…   (7 values; the first is overall tone)

The PERSONS field is redacted and is the only altered field. The real row names six identifiable living people who have done nothing wrong; publishing them in our own documentation would sit badly beside the Article 10 position further down this page. The URL, source, themes, organisations and tone values are verbatim. Trailing … marks a field trimmed for width, never edited.

The extraction is already done before we see the row. This is the property the whole design rests on. GDELT has already read the article, pulled out the people and organisations, classified the themes against its own controlled vocabulary and scored the tone. We are not running NLP over article prose — we could not, since we never hold the prose. We parse a row that arrives carrying structured entities, and our job is the far narrower one of deciding whether one of those extracted person entities is your subject. That is why matching against the whole firehose is tractable at all, and why an own-index option is tractable too: the expensive part was done upstream, for free, and republishing it is permitted.
And note what this row is not: a hit. Its tone is −3.26 — distinctly negative. A naive design would surface it. It carries no offence theme, so we do not, and that is the correct outcome: it is a campus protest story, not adverse media. Negative tone alone is never a hit. It takes an offence theme and a bound name match — which is the same argument made in the sentiment section below, here in a row you can look at.

3 · Unavailable is not a pass — read this before you integrate

“We did not check” and “we checked and found nothing” are different answers. Every screening response carries an adverseMediaStatus. An empty media result with status Unavailable means the check did not happen. Your integration MUST NOT treat it as a clean pass. A control that silently reports “no adverse media” when it never looked is 100% false negatives while still looking like coverage — that is the single worst failure mode this feature can have, so we surface it explicitly instead of hiding it.
adverseMediaStatusMeaningIs an empty result a clean pass?
Skipped You opted out for this screen ("adverseMedia": false), or the feature is disabled. No — we did not look.
Ok Evidence was read successfully. Yes — an empty result is trustworthy.
Stale Evidence was served from an older cached lookup. It may be out of date. Defined in the contract; not currently produced. Qualified — treat with the age in mind.
Unavailable No evidence exists yet for this subject in our index. No. Adverse media was not checked.

The status is persisted with the screening and rendered in the PDF report and the regulator pack, so an auditor can see, months later, whether a given screen actually checked the news.

4 · How a subject is bound to an article

Naive name matching on news text is how an adverse-media feature earns an analyst's contempt and gets switched off. Two controls stand between a name and a hit.

Ordered, adjacent name binding

A name must appear as an ordered, adjacent span — not a bag of tokens. A token-set match would let “Adam Smith and John Maynard Keynes” resolve to the subject “John Smith”. It cannot, here.

The rarity gate

A common name can never clear the bar on the name alone. For a high-collision surname, an extracted person entity is not accepted as identification, and we fall back to stronger headline evidence. The surname list is generated from our own corpus of 490,072 names — every surname shared by 25 or more distinct individuals.

The reason the second control exists is worth stating plainly: a better entity signal tells you an article is about a person of that name. It never tells you which one. Name ambiguity sets the bar; evidence has to clear it.

The rarity gate is also a fairness control, not only a precision lever. Common names concentrate in particular ethnic and national groups, so a matcher that floods on common names effectively discriminates by surname.

5 · What you get back with a hit

An adverse-media match is not a bare hyperlink. Every hit carries the evidence an analyst needs to triage it without clicking out, and an auditor needs to resolve it after the link has rotted.

FieldWhat it is
headlineThe article headline.
publisherThe publishing outlet's domain, for source credibility.
publishedAtWhen the article was published/indexed. A 2009 allegation and a 2026 conviction are not the same signal.
externalIdThe article URL.
adverseMediaCategoryThe FATF-style category (see below).
matchedPersonThe person entity that bound the subject to the article. This is the “why” behind a hit whose headline names nobody. Null when the subject was identified from the headline alone.
exculpatoryOutcomeAn outcome term in the headline suggesting the subject was cleared rather than accused (“acquitted”, “charges dropped”). See below.
articleToneGDELT's own published tone score. Display-only. See §7.

We never store article bodies. Adverse media on a natural person is criminal-conviction and offence data, so retention is kept to the metadata above. GDELT hands us every person it extracted from an article — typically several — and we store only the one that binds your data subject. The other names belong to people who never asked to be screened.

Exculpatory headlines are down-ranked, never suppressed

An article saying “Smith cleared of money laundering” puts the subject and a crime word in the same headline — which naive scoring reads as maximum adverse, for an article that exonerates him. Such a match is now down-ranked and flagged with the outcome term. It is not hidden: auto-suppressing it would trade a visible false positive for an invisible false negative, and an acquittal is still information an analyst wants to see.

6 · Categories

A hit is categorised from the financial-crime term that matched, so a tenant can set a different decision per category. The categories are:

CategoryClassified from
Financial crimemoney laundering, fraud, corruption, bribery, embezzlement, Ponzi, scam
Cybercrimecyber, hacking, ransomware, data breach
Regulatoryregulator, sanction, fine, censure, enforcement
High riskterror, trafficking, organised crime, smuggling, racketeering
Unknownmatched adverse coverage that fits none of the above rules

Classification runs over two inputs: the headline keywords, and — where the headline carries no crime word at all — the GDELT GKG themes attached to the article, run through the same table. Themes are language-independent; the keyword list is English.

The mapping is a keyword table, on purpose. It is auditable, and it can be shown to a regulator and contested by a data subject. A black-box classifier can be neither.

7 · Why we do not use sentiment analysis

The obvious idea is to score how negative an article is and flag the negative ones. We measured it, and it does not work — so tone is captured for display and is barred from every decision path (a build-time architecture test fails if tone ever reaches the scoring engine).

Negative ≠ relevant

An article can be scathing about a politician's dress sense — very negative, zero AML relevance. On a live sample of 1,440 articles, the most negative items were a prison riot, a depression story and a celebrity death. Zero predicate offences.

Relevant ≠ negative

A money-laundering indictment is often reported in clinical, neutral prose. A tone gate at −5 was measured to discard 44% of genuinely AML-relevant articles — an embezzlement suspension scores only −3.21.

Adversity is established by what the article is about — it matched a financial-crime term in its full text and the subject is named in it — not by how cross the journalist sounds. “The headline contains 'acquitted'” survives a regulator. “Tone: −1.8” does not.

8 · What it actually catches — measured

Against our adverse-media golden benchmark, replayed through the real filter, binder and emitter, at the shipped relevance threshold (0.7):

38.9% recall

14 of 36 benchmark positives — subjects with known, dated adverse media — are caught. Reading the PERSON entities GDELT already extracted, instead of the headline alone, lifted this from 16.7% (6 of 36), with no change to the threshold.

100.0% precision on common names

15 of 15 clean common-name subjects (the false-positive stress set) produce no hit. This is the number that decides whether an analyst keeps the feature switched on — and it is unchanged by the recall work above: the recall was bought with better evidence, not a lower bar.

Read these numbers with their caveats. The benchmark is 36 positives and 15 common-name negatives. That is a small corpus: the percentages are indicative, not a measurement anyone should tune a compliance control on. Recall is measured only over positives GDELT actually returned articles for — a subject with an empty source response is a coverage gap, not a filter miss. At the same threshold the rare-name negatives came out at 1 of 2 clean — n = 2, far too small to express as a percentage, and a real false positive we are not waving away. Corpus generated 2026-07-14T21:35:39Z. These figures are regenerated by the test suite and injected here by a generator — they are never hand-written.
Coverage of a real applicant population is the honest weak point, and it is small. The numbers above measure the matcher's quality on evidence we already hold. They are not the coverage you get on an arbitrary name. Measured against a 2,042-name applicant corpus on 2026-07-15: 2,038 of 2,042 real applicants (99.8%) returned Unavailable — we had never fetched evidence for them. Adverse media actually ran for 4 of them, roughly 1 in 500. The 15-minute slice tailer (shipped after that measurement) exists precisely to fix this, and it is enabled in our hosted deployment. But its effect on coverage is unmeasured, and we will not quote a projection as though it were a result. A benchmark run confirmed the mechanism works — the evidence store went from 0 to 1,530 lookups, the tailer's subject book from 0 to 1,317, at about 2 hits per slice — but that is proof of function, not of coverage. A cold one-shot batch cannot measure the improvement, because the batch that measures coverage is the same batch that enrols the subjects: every name is being met for the first time as it is counted. A real figure has to accrue over days of live tailing, and none is recorded. Treat the coverage figure as 0.2% measured (2026-07-15), improvement unmeasured.
Two different numbers, and conflating them would flatter us badly. 38.9% is filter quality on evidence we already hold — of the benchmark subjects for whom GDELT did return articles, how many the matcher correctly catches. 0.2% is whether we hold any evidence at all for an arbitrary real applicant. The first says the matcher works; the second says it rarely gets the chance. A vendor quoting only the first number would be describing a different product from the one you would actually run.

9 · The limits — what this does not do

An honest limit is worth more to you than an inflated claim.

LimitDetail
Coverage of arbitrary names is low The measured figure is above and it is the single most important number on this page. A name nobody has screened before will very likely return Unavailable on its first screen. That is the honest failure mode, but it is a coverage gap, not a feature.
Matching is exact, not fuzzy Sanctions and PEP matching is fuzzy, phonetic and nickname-aware. Adverse-media binding is not. It compares normalized tokens with ordinal equality, so a transliteration, a diminutive or a misspelling in the news will not bind. This is a deliberate precision choice — fuzzy matching against an open news corpus floods — but it means we miss real coverage.
A common surname cannot self-identify On a high-collision surname — measured from our own 490,072-name corpus as a surname shared by 25 or more distinct individuals — a name match alone is never accepted as identification, and provenance is suppressed so no report claims a resolution it did not earn. This is a fairness control as much as a precision one: a matcher that floods on common names discriminates by surname.
Source-coverage gaps we cannot tune away Some subjects are missed because the source never names them. Our own benchmark still misses Roger Ng, a convicted 1MDB banker: GDELT returns on-topic articles, but its extractor names Jho Low and Tim Leissner and never him. No threshold change fixes that.
English retrieval terms The negative-keyword retrieval terms and the headline keyword classifier are English. GKG themes are language-independent and partly compensate, but non-English coverage is under-represented.
No article bodies, no story clustering We hold headlines and metadata, never article prose — it is unlicensed and we will not pretend otherwise. We also do not yet collapse one story syndicated across many outlets into a single event, so a widely-carried story can appear as several hits.
Discovery, not adjudication We can tell you an article is about a person with your subject's name. We do not confirm it is your subject. A commercial vendor sells human-curated, entity-resolved dossiers. This is not that, and we will not imply it is.

10 · Using it from the API

Adverse media runs by default. Send "adverseMedia": false to opt out of it for a screen (the status then comes back as Skipped).

# Screen with adverse media (the default)
curl -X POST https://wl.proovid.com/v1/screenings/check \
  -H "X-Api-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fullName": "Jho Low", "adverseMedia": true }'

The response carries adverseMediaStatus alongside the matched entities. Check it before you treat an empty media result as clean:

{
  "decision": "Review",
  "adverseMediaStatus": "Ok",
  "matchedEntities": [
    {
      "sourceList": "ADVERSE_MEDIA",
      "rejectionTag": "adverse_media",
      "adverseMediaCategory": "FinancialCrime",
      "headline": "...",
      "publisher": "...",
      "publishedAt": "...",
      "matchedPerson": "..."
    }
  ]
}
A hit produces Review, never an automatic Fail. An adverse-media hit is tagged adverse_media and resolves to Review — a human dispositions it. The automatic-Fail branches key on sanctions and criminal matches only. A hit can still reach Fail, but only because a tenant configured it to — a per-category warning-type override, or an escalate-high-risk-band policy. It never happens on the strength of the news hit alone.

Bringing your own adverse-media endpoint is also supported — see Data providers.

One row per mention — this article, which mentions this name, carrying this theme — newest capture first. It reads the article corpus, not your screening history: the index carries no tenant column, and this query never joins tenant-scoped screening output. The caller must hold the Screening.Read policy.

curl -G https://wl.proovid.com/v1/adverse-media/mentions \
  -H "X-Api-Key: $API_KEY" \
  --data-urlencode "name=Jho Low" \
  --data-urlencode "from=2024-01-01T00:00:00Z" \
  --data-urlencode "pageSize=50"
Query parameterWhat it does
nameMentioned name. Trigram-matched against the normalized name held in the index, so spelling variants match.
fromInclusive lower bound on capture time.
toExclusive upper bound on capture time.
cursorOpaque continuation token, copied from the previous page’s nextCursor. A token this API did not issue returns 400.
pageSizeDefault 50, maximum 100. A larger request is clamped to 100 and served, not rejected.
A name and a capture window are the only filters. There is no title filter, no source or domain filter, and no theme filter. The index is built for two access paths — capture time, which leads its primary key, and the mentioned name, which carries a trigram index — and nothing else on that table is indexed. A title, source or theme predicate would read the largest table this service owns from end to end on every request, so it is not offered.

The page carries its rows, the cursor that continues it, the size actually served, and the statement of what a row means. That last field travels on the payload so a client cannot render rows without also being handed the caveat:

{
  "rows": [
    {
      "capturedAt": "2026-02-14T09:15:00Z",
      "url": "https://...",
      "title": "...",
      "domain": "...",
      "mentionedName": "...",
      "matchedTheme": "FinancialCrime"
    }
  ],
  "nextCursor": "MTc3...",
  "pageSize": 50,
  "disclosure": "Each row means: this article mentions this name and carries this theme. GDELT binds names to themes at document level, so a row is co-occurrence within one article - not an allegation against the person named, and not a person-level finding."
}
Paging is keyset, and there is no total. Follow nextCursor until it comes back null; that is the end of the result set. No row count is returned, for two reasons: a keyset page does not need one, and a count of mentions is exactly the per-person tally the caveat above says this data cannot support. OFFSET paging is not offered either — on a table heading for roughly 1.3 million rows, page 40 would cost forty times page 1, while a cursor seek costs the same at any depth.
StatusWhen
200One keyset page of mentions. An empty rows array with a null cursor means the filters matched nothing.
400The continuation cursor could not be read.
403The caller does not hold Screening.Read.

Operator configuration — the knobs that matter

These are the settings that change what this feature is, rather than tuning it at the margin. Everything below is read from configuration at startup; there is no AdverseMedia section in appsettings.json, so every value comes either from the C# option defaults or from your deployment's environment.

KnobDefaultWhat moving it does
AdverseMedia:Prefetch:* — Retired 2026-09-15, no effect. The DOC-based per-subject prefetch is removed; a set value now only logs a boot warning.
AdverseMedia:LiveFallbackEnabled false Retired, no effect. A screen reads only the corpus index. Any non-default value logs a boot warning.
AdverseMedia:LookbackYears — Retired 2026-09-15, no effect. Coverage depth is now whatever the GKG slice tail + historical backfill have indexed, from 2026-05-28. A set value now only logs a boot warning.
AdverseMedia:SliceTail:Enabled false in code The 15-minute forward tailer. Not rate-limited, but forward-only on its own — back-history comes from the separate historical backfill. It ships OFF: turning it on is a deliberate, separately-approved action, never a side effect of a deploy. On our own hosted deployment this key is ON, alongside the historical backfill, giving index coverage from 2026-05-28. A self-hosted install that leaves the key unset collects nothing until it sets it.
Per-tenant BYO provider unset Point a tenant at their own adverse-media endpoint/credentials, independent of our own index.

Seeding the index — what you will see while it fills

A fresh deployment starts with an empty GKG index and fills it by replaying GDELT’s 15-minute slices backwards through the retention window. The figures below are measured on our own node.

MeasureValue
Backfill rate~6,194 slices per hour.
A full five-year seed175,296 slices — 1,826 days at 96 slices a day. At the rate above that is roughly 28 hours, not an afternoon.
Ingest passes48–68 minutes each, during which the backfill does not advance.
A backfill that looks stuck is usually the ingest job holding the runner slot. The service runs one background job at a time per pod, and it does not interleave them. When ingest claims the slot it holds it for 48–68 minutes, and for that whole window the backfill’s progress counter does not move at all — same phase, same done-of-total, no errors. That is the scheduler doing what it is designed to do. Before reporting a stalled seed, open Background jobs and check which job is currently running: if it is ingest, the backfill is queued, not broken.
Adverse media on a natural person is GDPR Article 10 data. Information relating to criminal convictions and offences — or to alleged offences — is Article 10 data under the GDPR, processed under stricter conditions than ordinary personal data. Adverse-media screening of natural persons falls in that category. We state this as a fact about the data, not as advice, and we make no claim that using this feature makes you compliant. Establishing a lawful basis for the processing, and handling data-subject rights over that evidence, are the operator's responsibility and should be settled with your own legal counsel before the feature is switched on in production. On retention, be aware of a current product limitation: the built-in retention purge is off unless explicitly enabled, and even when enabled it covers screening requests only — it does not age out the stored adverse-media article evidence, which therefore persists indefinitely by default. The cache-hours settings control when an entry is re-fetched, not when it is deleted. A retention and erasure capability for that evidence is in development; until it ships, factor the current behaviour into your DPIA rather than assuming a retention window is being enforced for you.

Attribution

Adverse-media results are derived from The GDELT Project (gdeltproject.org). Headlines, publishers and links are the property of their respective publishers. This citation is a condition of GDELT's terms, not a courtesy, so it travels with the data: it is rendered alongside adverse-media results in the API response, the PDF report and the regulator pack. Screening assists your compliance programme; it is not legal advice, and an adverse-media hit is an indication to review, not a finding of wrongdoing.