Evidence

Adverse media screening

Screen individuals and companies against real-time news. A GDELT DOC 2.0 fetch and Claude Haiku 4.5 classification return seven risk categories, per-article confidence scores, and human-readable reasoning in a single API call.

Endpoint

POST /v1/adverse-media/screen

Data source

GDELT DOC 2.0

LLM

Claude Haiku 4.5

Cost

~$0.009 / screening

From news article to risk category in ~2 seconds

The pipeline is four stages: query construction, article fetch, LLM classification, and response assembly.

  1. 1

    Query construction

    The entity name is escaped and wrapped in exact-phrase quotes. For COMPANY types, a near10 clause adds investigation-related keywords (lawsuit, fined, fraud, corruption). A tone<-3 filter ensures only negatively-toned articles are considered.

  2. 2

    GDELT DOC 2.0 fetch

    Up to 75 articles are fetched from GDELT with 3 retries and exponential backoff. Social media domains, press releases, and known content farms are filtered out. Results are cached in Redis for 24 hours.

  3. 3

    LLM classification

    Articles are truncated to 20 per screening and sent to Claude Haiku 4.5 in a single batch call. The model evaluates relevance, entity match confidence, and assigns a risk category with reasoning. LLM results are cached for 7 days.

  4. 4

    Response assembly

    A weighted risk score is computed from article classifications. The score maps to an overallRisk (CLEAR → CRITICAL). Per-article results, quota metadata, and caching status are returned.

POST /v1/adverse-media/screen

Authenticate with a Bearer token and provide the entity name, type, and optional context. The full schema is in the OpenAPI reference.

curl -X POST https://api.verifex.dev/v1/adverse-media/screen \
  -H "Authorization: Bearer vfx_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "entity": "Acme Corporation",
    "entityType": "COMPANY",
    "context": {
      "country": "US",
      "industry": "pharmaceuticals"
    }
  }'
entitystringrequired

The name of the individual or company to screen. Exact-phrase matched against article content.

entityTypeenumrequired

INDIVIDUAL or COMPANY. Affects query construction — COMPANY queries include near10 investigation keywords.

context.countrystring (ISO-3166-1 alpha-2)

Optional. Helps disambiguate entities with common names.

context.industrystring

Optional. Provides additional disambiguation context for the LLM classifier.

Structured risk assessment with per-article detail

Every response includes an overall risk level, a numeric score, flagged article count, and full per-article results with confidence and reasoning.

json
{
  "entityName": "Acme Corp",
  "screenedAt": "2026-05-15T10:31:00Z",
  "status": "COMPLETE",
  "coverage": { "gdelt": "ok", "classifier": "ok", "complete": true },
  "overallRisk": "MEDIUM",
  "riskScore": 42,
  "articlesFound": 20,
  "articlesFlagged": 3,
  "cached": false,
  "llmCost": 0.009,
  "promptVersion": "v1.0.0",
  "quota": { "used": 12, "limit": 50, "remaining": 38, "resetsAt": "2026-06-01T00:00:00Z" },
  "results": [
    {
      "title": "Acme Corp fined $2M for compliance violations",
      "url": "https://example.com/article/123",
      "domain": "reuters.com",
      "publishedAt": "2026-05-15T10:30:00Z",
      "riskCategory": "REGULATORY",
      "confidence": 0.91,
      "reasoning": "Article describes a regulatory fine directly tied to the entity.",
      "isRelevant": true,
      "sentiment": 0
    }
  ]
}

Contract

Outcome states

An upstream or classifier failure is never a clearance. When status is COMPLETE or NO_RELEVANT_MEDIA the screen ran fully and a CLEAR risk is trustworthy. Any other status (UPSTREAM_UNAVAILABLE, CLASSIFIER_UNAVAILABLE, RATE_LIMITED, PARTIAL, ERROR) means coverage was incomplete: overallRisk is UNKNOWN, riskScore is null, and the check does not consume quota. Always branch on status before trusting a clear.
statusstring

Outcome: COMPLETE, NO_RELEVANT_MEDIA, PARTIAL, UPSTREAM_UNAVAILABLE, CLASSIFIER_UNAVAILABLE, RATE_LIMITED, or ERROR. Only the first two are trustworthy clear results.

coverageobject

Per-provider state { gdelt, classifier, complete }. complete=true is the only condition under which a CLEAR is reliable.

overallRiskstring

CLEAR, LOW, MEDIUM, HIGH, CRITICAL — or UNKNOWN when coverage was incomplete. Never CLEAR on a failure.

riskScorenumber (0-100) | null

Weighted numeric score, or null when no assessment could be made.

articlesFoundnumber

Total articles retrieved from the news index for this entity.

articlesFlaggednumber

Count of articles the classifier marked as relevant adverse coverage.

cachedboolean

True if result was served from cache (Redis or PostgreSQL).

llmCostnumber

Estimated USD cost of the LLM call. 0 when the classifier was not run.

quotaobject

Used, limit, remaining, and resetsAt for the billing period. A failed screen does not consume quota.

Seven risk categories

The classifier assigns every article exactly one riskCategory — the type of adverse content. This is a different axis from overallRisk, which is the screening’s severity (UNKNOWN, CLEAR, LOW, MEDIUM, HIGH, CRITICAL). Six categories describe adverse content; NONE means none was found.

FRAUD

Securities fraud, wire fraud, accounting fraud, embezzlement, and Ponzi schemes.

CORRUPTION

Bribery, kickbacks, extortion, and misuse of public office.

SANCTIONS

Sanctions evasion, trade restrictions, and embargo violations.

CRIME

Violent crime, drug trafficking, organized crime, arrests, and convictions.

REGULATORY

SEC enforcement, FINRA actions, banking violations, and data breaches.

REPUTATIONAL

Major scandals, boycotts, and significant public backlash.

NONE

No negative information, or negative information about a different entity. Unrecognized values returned by the model are coerced to NONE.

Quota-gated by plan

Adverse media screening is available on Growth, Pro, and Enterprise plans. Free and Starter plans receive a 403 response.

Growth

Quota
50 / month
Overage
$0.15 / check

Pro

Quota
200 / month
Overage
$0.10 / check

Enterprise

Quota
Unlimited
Overage
Included

Quota is enforced atomically via Redis Lua script. Cache hits (Redis or PostgreSQL) consume zero quota. LLM failures also consume zero quota. Quota resets monthly with the billing cycle.

Expected errors and how to handle them

403plan_access_denied

Adverse media screening is not available on your plan. Upgrade to Growth or higher.

https://verifex.dev/pricing
429quota_exceeded

You've used all your adverse media checks this month. Upgrade or wait for the next billing cycle.

https://verifex.dev/pricing
429rate_limit_exceeded

You've hit the rate limit. Slow down or upgrade your plan for higher limits.

https://verifex.dev/docs
503gdelt_unavailable

The GDELT news service is temporarily unavailable. Retry after a short delay.

https://verifex.dev/docs/adverse-media
400validation_error

entity and entityType are required. entityType must be INDIVIDUAL or COMPANY.

https://verifex.dev/docs/adverse-media

Official SDKs for common backends

The adverse media endpoint is accessible via the same SDKs as the rest of the Verifex API.

Node.js
import { Verifex } from "verifex";

const verifex = new Verifex({ apiKey: "vfx_your_api_key" });

const result = await verifex.adverseMediaScreen({
  entity: "Acme Corporation",
  entityType: "COMPANY",
  context: { country: "US" },
});

console.log(result.overallRisk, result.articlesFlagged);
Python
from verifex import VerifexClient

client = VerifexClient("vfx_your_api_key")

result = client.adverse_media_screen(
    entity="Acme Corporation",
    entity_type="COMPANY",
    context={"country": "US"},
)

print(result.overall_risk, result.articles_flagged)
Go
import (
    "context"
    verifex "github.com/Verifex-dev/verifex-go-sdk"
)

client := verifex.New("vfx_your_api_key")

result, err := client.AdverseMediaScreen(context.Background(), verifex.AdverseMediaRequest{
    Entity:     "Acme Corporation",
    EntityType: "COMPANY",
})

fmt.Println(result.OverallRisk, result.ArticlesFlagged)
Rust
use verifex::{Verifex, AdverseMediaRequest};

let client = Verifex::new("vfx_your_api_key");

let result = client
    .adverse_media_screen(AdverseMediaRequest {
        entity: "Acme Corporation".into(),
        entity_type: "COMPANY".into(),
        ..Default::default()
    })
    .await?;

println!("{} — {} flagged", result.overall_risk, result.articles_flagged);