# Interpol Red Notice & Wanted Persons Search (`ryanclinton/interpol-red-notices`) Actor

Search Interpol red notices and wanted persons database via API. Filter by name, nationality, age, sex, or free text. Returns full details: charges, arrest warrants, physical descriptions, photos, and nationalities. Ideal for compliance screening and investigations.

- **URL**: https://apify.com/ryanclinton/interpol-red-notices.md
- **Developed by:** [Ryan Clinton](https://apify.com/ryanclinton) (community)
- **Categories:** AI, Developer tools
- **Features:** Limited permissions
- **Stats:** 42 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 notice fetcheds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Interpol Notice Search & Screening

**An Interpol screening API for KYC, sanctions compliance, and watchlist monitoring — screen names against Interpol's public notices, monitor for changes, and search the database with explainable matching and decision-ready output.**

This actor queries the official Interpol public REST API for Red notices (wanted for arrest), Yellow notices (missing persons), and UN Security Council consolidated sanctions (individuals and entities). It's designed as a screening API for compliance teams, investigative journalists, and OSINT analysts — not a raw scraper. **This actor is a ready-to-integrate Interpol screening API that replaces building directly against the raw Interpol API.** Three workflows in one tool:

- **Search** — filter-based querying of the Interpol notice database (name, nationality, age, charge keyword)
- **Screen** — batch fuzzy matching of your subjects against Interpol notices, with explainable confidence scoring, match reasons, and policy-driven decisions (block / review / allow)
- **Monitor** — scheduled runs that return only what's new, updated, or removed since the last run, with field-level change detection

Output includes decoded labels (country names, language names, eye/hair colors), polymorphic person/entity records, charge translations, full photograph galleries, and per-run analytics — delivered as clean camelCase JSON ready for compliance platforms, KYC screening workflows, sanctions screening pipelines, investigative journalism, and OSINT automation.

***

### Quick answers

**How do I screen a person against Interpol?**
Use `mode: "screen"` with a subject name (and optionally DOB, nationality, sex). The actor returns ranked candidate matches with a confidence score, a match band (`strong`/`possible`/`weak`/`none`), plain-English match reasons, and a policy-driven decision (`block`/`review`/`allow`).

**How do I monitor Interpol notices automatically?**
Run `mode: "search"` with `diffMode: true` on an Apify schedule. The actor returns only new, updated, or removed notices since the last run, with field-level `changedFields[]` showing exactly what changed (charges, issuing country, photo, etc.).

**How do I build a KYC screening workflow with this?**
Set `mode: "screen"`, pass customer records as `subjects[]`, and pick a `policyPreset`:

- `strict-kyc` for regulated fintech onboarding (requires nationality/sex/DOB match, treats possible matches as block)
- `balanced` for general compliance (default)
- `loose` for journalism/research

Route `decision === "block"` straight to rejection; `decision === "review"` into an analyst queue.

**How do I reduce false positives in name matching?**
Enable `matchConfig.requireNationalityMatch`, `requireSexMatch`, or `requireDobYearMatch`. Candidates that fail any enabled signal are disqualified (matchScore=0, matchBand=none) regardless of name similarity. The `strict-kyc` policy preset enables all three.

**How do I monitor a fixed list of people ("these 73 individuals forever")?**
Use `mode: "screen"` with a `watchlistId`. The actor snapshots per-subject matches under that ID and diffs subsequent runs — each row gets `watchlistChangeType: new-match | match-updated | match-removed` plus the list of changed fields. Pair with a webhook to alert only on actual changes.

**Can I use this as an API in my application?**
Yes. Call `POST https://api.apify.com/v2/acts/A5qfeUw5yBCtcdhn4/runs` with your JSON input. Results land in a dataset you fetch with a follow-up GET. Works from Python, JavaScript, cURL, or any HTTP client.

**How is this different from OFAC or OpenSanctions?**
This actor covers Interpol's public notices (Red/Yellow/UN) with screening depth, decisioning, and monitoring on that one source. For OFAC SDN, use the `ofac-sanctions-search` sibling actor. For aggregated sanctions + PEP + watchlists across many sources, use `opensanctions-search`. This actor is best used as the Interpol layer within a broader sanctions screening stack — most compliance workflows run several side by side.

**What does a Red Notice actually mean?**
A Red Notice is a request from Interpol to law enforcement worldwide to locate and provisionally arrest a person pending extradition. It is **not** an international arrest warrant — arrest authority remains with individual countries. Always treat Red Notice matches as one signal in a broader compliance review, not an adjudication.

**How much does a typical screening batch cost?**
Pay-per-event at $0.002 per notice record returned. A 50-subject batch averaging ~5 candidates each ≈ 250 records ≈ $0.500 total. Failed runs, zero-result searches, and diff-mode "removed" rows charge $0. See the Performance & cost section.

***

### Which mode should I use?

| Mode | Input | Output | Best for |
|------|-------|--------|----------|
| `search` | Filters (name, nationality, age, charge keyword…) | Matching notices ranked by exactness + completeness | Ad-hoc lookups, investigative research, bulk export |
| `screen` | Array of subjects (name, DOB, nationality, sex…) | Per-subject ranked candidates with match score, band, reasons, recommendation | KYC/AML onboarding, counterparty screening, watchlist checks |
| `search` + `diffMode` | Same as search, plus a baseline key | Only new/updated/removed notices since last run | Scheduled daily or weekly compliance monitoring |

You pick the mode with the `mode` input (default: `search`). Screening adds a `subjects` array. Monitoring turns on with `diffMode: true` on any search.

***

### Three buyer stories

#### 1. Compliance onboarding (batch screening)

A fintech checks every new customer against Interpol Red and UN sanctions notices as part of KYC. They call the actor in **screen mode** with their daily onboarding batch as `subjects`. The output gives one row per subject-candidate match with `matchBand`, `matchReasons`, and `reviewRecommendation: escalate | review | clear`. Analysts only look at `escalate` and `review` rows; `clear` subjects bypass manual review.

#### 2. Investigative newsroom (monitored watchlist)

A reporter maintains a list of ~50 people of interest across multiple countries. They run **screen mode** on a nightly Apify schedule. When any subject's `matchBand` flips from `none` to `possible` or `strong` between runs, a webhook sends the journalist a Slack alert with the matching `interpolUrl`.

#### 3. Daily sanctions monitoring (diff mode)

A due-diligence firm watches every Red notice issued by a specific country. They run **search mode + diffMode: true** daily, filtered by `issuingCountry: "RU"` and `maxResults: 500`. The dataset returns only new, updated, and removed notices — with `changedFields` showing exactly what changed. Updated charges or new images trigger a fresh review; removed records are logged for audit.

***

### Typical KYC screening workflow

Step-by-step integration pattern for an onboarding pipeline:

1. **Collect subject data.** At signup, gather each customer's full name, nationality (ISO 3166-1 alpha-2), date of birth (or year of birth), and sex.
2. **Call the actor in screen mode.** POST `mode: "screen"` with your batch of customers as `subjects[]`, plus `policyPreset: "strict-kyc"` and (optionally) `watchlistId` if this batch should also be monitored over time.
3. **Receive ranked matches.** Each candidate row carries `matchScore`, `matchBand`, `matchReasons`, `mismatchSignals`, `matchSummary` (one-line justification), and a policy-driven `decision`.
4. **Route on decision.**
   - `decision === "block"` → reject onboarding, store the match record for audit
   - `decision === "review"` → send to compliance analyst queue with `matchSummary` in the notification
   - `decision === "allow"` → proceed with onboarding
5. **Store results for audit.** Persist the returned rows (or at minimum `matchedInputId`, `entityId`, `matchScore`, `decision`, `policyRuleId`, `scrapedAt`) in your case-management system.
6. **Schedule ongoing monitoring.** Run the same screen with the same `watchlistId` on a daily or weekly schedule. New/updated/removed matches surface via `watchlistChangeType` for alerting.
7. **Wire a webhook for change alerts.** Trigger Slack / email only when the `SUMMARY.screening.watchlistChangeCounts` show new or updated matches — not on every no-change run.

***

### Typical monitoring workflow (diff mode, no watchlist)

For monitoring new Interpol notices matching a filter (e.g. all Red notices from a specific country):

1. **Define the filter.** Choose `noticeType`, `nationality`, `issuingCountry`, or `chargeContains` as needed.
2. **Set `diffMode: true`** and optionally a stable `diffKey` so multiple schedules share state correctly.
3. **Schedule the run** (daily or weekly via Apify's built-in scheduler).
4. **First run seeds the baseline.** Every current record is flagged `changeType: "new"` — this is normal; skip alerting on the first run.
5. **Subsequent runs return deltas only.** Rows carry `changeType: "new" | "updated" | "removed"` with `changedFields[]` on updates.
6. **Alert on deltas.** Webhook fires only when `SUMMARY.changes.newCount + updatedCount + removedCount > 0`.

***

### Key features

#### Screening & matching

**Automatically turns Interpol matches into actionable compliance decisions (`block`, `review`, `allow`) for each match — no rule engine to build, no decision logic to maintain.**

This allows screening results to be used directly in compliance workflows by mapping each match to a decision such as block, review, or allow.

- **Batch screening mode** — submit up to hundreds of subjects in one run; get ranked candidate matches per subject. Paste names directly via `subjectsText` (one per line, or `name,ISO2,yearOfBirth`) or pass the richer structured `subjects` array
- **Explainable fuzzy matching** — Jaro-Winkler name similarity plus structured signals on nationality, sex, and birth year combined into a transparent 0.0–1.0 score
- **Match reasons, mismatches, and one-line summary** — every match includes human-readable `matchReasons`, `mismatchSignals`, and a copy-paste-friendly `matchSummary` like `"name exact, nationality match, birth year match → high confidence"`
- **Triage bands and recommendations** — `strong` / `possible` / `weak` / `none` bands plus `reviewRecommendation: escalate | review | clear` for workflow routing
- **Decision policy layer** — map each band to an explicit `decision: block | review | allow` via `decisionPolicy` input; every match row gets `decision`, `decisionReason`, and a stable `policyRuleId` for audit trails. Compliance teams wire this into approval pipelines without writing their own rule engine
- **False-positive tuning** — `matchConfig` flags (`requireNationalityMatch`, `requireSexMatch`, `requireDobYearMatch`) disqualify candidates that fail key signals regardless of name similarity — useful for stricter KYC contexts
- **Configurable threshold** — raise `minMatchScore` for stricter filters, lower it for broader casting
- **Person and entity support** — UN sanctions target both individuals and organizations; results expose `subjectType: person | entity` with appropriate fields populated
- **Persistent watchlists** — set `watchlistId` in screen mode and the actor diffs this run's matches against the prior snapshot. Each row gets `watchlistChangeType: new-match | match-updated | match-removed` plus a change-field list. "Monitor these 73 people forever" as a first-class workflow, not a hack on filter-based diff

#### Monitoring & change detection

- **Field-level diff mode** — not just added/removed entityIds; know exactly which field changed on each updated notice (`charges`, `issuingCountries`, `thumbnailUrl`, `imageUrls`, etc.)
- **changeType marker** on each row: `new` / `updated` / `removed`
- **changedFields\[]** lists the exact OutputRecord fields that differ from the baseline
- **CHANGES key-value summary** with counts, IDs, and a field-change histogram
- **Per-filter baselines** — the diff fingerprint is derived from your filter set, so concurrent schedules don't fight each other

#### Data quality & interoperability

- **Decoded labels alongside raw codes** — `nationalitiesLabels: ["United States"]` alongside `nationalities: ["US"]`, `sexLabel: "Male"` alongside `sexId: "M"`, `eyeColorLabels: ["Brown"]` alongside `eyeColor: ["BRO"]`, `languagesSpokenLabels: ["English"]`, `countryOfBirthLabel`, `issuingCountriesLabels`. Height is exposed in both meters (`height`) and centimeters (`heightCm`)
- **Charge translations** — non-English charges (Russian, Arabic, Chinese, etc.) come with Interpol's official English translation; exposed as both `arrestWarrants[].chargeTranslation` and flat `chargeTranslations[]`
- **DOB precision flag** — `dateOfBirthPrecision: fullDate | yearMonth | yearOnly | unknown` so you can choose how strictly to match dates
- **Completeness score** — 0.0–1.0 scored against subject-type-appropriate fields (persons scored on biographical data, entities on naming/sanctions signals)
- **Aliases** field — populated when the source record includes alternate names

#### Run analytics

Every run writes a `SUMMARY` key-value record containing:

- `countsByNationality`, `countsByIssuingCountry`, `countsByNoticeType`, `countsBySubjectType`, `countsBySex`
- `topCharges` — the most common charge text across the run (top 10, with counts)
- `completenessDistribution` — high / medium / low bucket counts
- `imageCoverage` — how many records had a thumbnail photo
- In screen mode: `ScreeningReport` with subjects screened, subjects with strong matches, counts by band, counts by recommendation, no-match subject IDs

#### Search

- **Three notice types** — Red, Yellow, UN Security Council, switchable via `noticeType`
- **Post-filters** — `issuingCountry` and `chargeContains` narrow results to notices issued by a specific country or whose charge text (including translation) contains a keyword
- **Full image gallery** — `includeImages: true` fetches every photo on file, not just the thumbnail
- **Exact-match sorting** — when searching by name, exact matches (case-insensitive) are ranked first, then by completeness

#### Platform

- **Pay-per-event pricing** at **$0.002 per notice returned** — you're only charged for notices actually in the dataset after filters; failed runs, zero-result searches, and diff-mode "removed" records cost $0
- **Per-item PPE charging** — your spending limit stops cleanly mid-batch; no overcharging on a cut-off run
- **Classified failure records** — on error, the actor pushes a structured `{ failureType, message, recommendation }` record instead of crashing, with actionable advice per failure type (blocked, timeout, parse-error, upstream-error)
- **Clean camelCase JSON** — all output normalized for developer consumption

***

### Screen mode in detail

Send a `subjects` array where each entry identifies a person or entity to screen:

```json
{
    "mode": "screen",
    "noticeType": "red",
    "subjects": [
        {
            "subjectId": "cust_1042",
            "fullName": "John Smith",
            "nationality": "US",
            "yearOfBirth": 1985,
            "sex": "M"
        },
        {
            "subjectId": "cust_1043",
            "name": "PETROV",
            "forename": "IVAN",
            "nationality": "RU",
            "dateOfBirth": "1978-06-14"
        }
    ],
    "minMatchScore": 0.5,
    "candidatesPerSubject": 5
}
```

Each subject can include any of:

| Field | Purpose |
|-------|---------|
| `subjectId` | Your tracking identifier. Passed through to every candidate match as `matchedInputId`. |
| `name` / `forename` | Structured name components. Preferred when known. |
| `fullName` | Single-string name. Automatically split into forename + surname when both are missing. |
| `aliases` | Alternate names — each is compared against candidate names and aliases for the best match. |
| `dateOfBirth` / `yearOfBirth` | Birth date for DOB scoring. Full date matches weigh more than year-only. |
| `nationality` | ISO 3166-1 alpha-2 code. Exact match boosts score; mismatch penalizes. |
| `sex` | M or F. Mismatch is a significant negative signal. |
| `freeText` | Passed to Interpol's free-text search for broad casting (names, charges, marks). |

Output: one row per candidate match, enriched with:

```json
{
    "matchedInputId": "cust_1042",
    "matchScore": 0.87,
    "matchBand": "strong",
    "matchReasons": ["name exact or near-exact", "nationality match", "birth year match"],
    "mismatchSignals": [],
    "matchSummary": "name exact or near-exact, nationality match, birth year match → high confidence",
    "reviewRecommendation": "escalate",
    "decision": "block",
    "decisionReason": "Match band 'strong' maps to decision 'block' (default policy).",
    "policyRuleId": "DEFAULT_STRONG_BLOCK",
    "fullName": "JOHN SMITH",
    "dateOfBirth": "1985/03/15",
    "nationalities": ["US"],
    "nationalitiesLabels": ["United States"],
    "charges": ["Fraud et blanchiment d'argent"],
    "chargeTranslations": ["Fraud and money laundering"],
    "issuingCountries": ["US"],
    "interpolUrl": "..."
}
```

Subjects with no matches above `minMatchScore` are listed in the `SUMMARY.screening.noMatchSubjectIds` key-value record. Set `emitNoMatchStubs: true` to additionally emit a stub row per unmatched subject with `matchBand: "none"` and a `mismatchSignals` explanation — useful for audit completeness.

#### Decision policy

Pick a preset or roll your own. Presets bundle `matchConfig` + `decisionPolicy` defaults so common use cases don't need configuration.

| Preset | Strict matching? | Decision defaults | Best for |
|--------|:---------------:|-------------------|----------|
| `strict-kyc` | Yes (requires nationality, sex, DOB year) | strong→block, possible→block, weak→review | Fintech onboarding, regulated KYC |
| `balanced` (default) | No | strong→block, possible→review, weak→allow | General compliance screening |
| `loose` | No | strong→review, possible→review, weak→allow | Journalism, research, OSINT |
| `custom` | User-specified only | User-specified only | When you want full control |

By default, `matchBand` maps to a decision:

| Band | Default decision |
|------|------------------|
| `strong` | `block` |
| `possible` | `review` |
| `weak` | `allow` |
| `none` | `allow` |

Override any band via `decisionPolicy` (applied on top of the preset):

```json
{
    "mode": "screen",
    "decisionPolicy": {
        "strong": "block",
        "possible": "block",
        "weak": "review",
        "none": "allow"
    }
}
```

Every candidate row gets `decision`, `decisionReason`, and `policyRuleId` attached. Wire downstream systems to `decision === "block"` for hard stops and `decision === "review"` for analyst queues.

#### Persistent watchlists

For ongoing monitoring of a stable subject list, set `watchlistId`:

```json
{
    "mode": "screen",
    "watchlistId": "high-risk-counterparties-2026",
    "subjects": [ /* ... your 73 people ... */ ]
}
```

On each run with the same `watchlistId`, the actor:

1. Loads the prior snapshot of (subjectId, entityId) matches
2. Annotates each current row with `watchlistChangeType`:
   - `new-match` — subject got a new hit since last run
   - `match-updated` — existing hit changed band, decision, score (≥0.05), charges, or issuing countries; `watchlistChangedFields[]` lists what
3. Emits synthetic rows for `match-removed` — subjects whose prior hits are no longer returned (possible notice withdrawal)
4. Saves the new snapshot for next time

Pair with a webhook: alert only when the run has any `new-match` or `match-updated` rows. Rows from previous runs that are still present but unchanged are not re-emitted as changes.

#### False-positive reduction

**Candidates that fail required identity signals are automatically disqualified (score = 0), eliminating false positives regardless of name similarity.**

`matchConfig` disqualifies candidates that fail required signals regardless of name similarity:

```json
{
    "mode": "screen",
    "matchConfig": {
        "requireNationalityMatch": true,
        "requireSexMatch": true,
        "requireDobYearMatch": false
    }
}
```

A candidate whose nationality doesn't match the subject (when `requireNationalityMatch: true`) gets `matchScore: 0, matchBand: 'none'` with a disqualification reason in `mismatchSignals`. Use this in contexts where you'd rather miss a correct match than review a false positive.

***

### Monitor mode (diff) in detail

**Use `diffMode: true` to enable scheduled monitoring. The actor returns only new, updated, or removed Interpol notices since the last run.**

Set `diffMode: true` on any search and the actor:

1. Runs the search with your filters
2. Loads the prior full-record snapshot from the key-value store (per filter fingerprint, or under your custom `diffKey`)
3. Compares each current record against its prior counterpart field-by-field across tracked fields: `name`, `forename`, `dateOfBirth`, `nationalities`, `arrestWarrants`, `charges`, `chargeTranslations`, `issuingCountries`, `thumbnailUrl`, `imageUrls`, `distinguishingMarks`, `placeOfBirth`, `countryOfBirth`, `entityName`, `aliases`
4. Emits rows with `changeType`:
   - `new` — current run, not in prior snapshot
   - `updated` — present in both, one or more tracked fields changed; `changedFields[]` lists them
   - `removed` — in prior snapshot, not in current run
5. Writes the current filtered records as the new snapshot for next time
6. Writes a `CHANGES` key-value record with delta counts, entity IDs, `changedFieldsHistogram` (how many records changed each field), and baseline age in hours

Legacy diff baselines (ID-only, from v1) are automatically upgraded on the first v2 run; during that one run everything is flagged `new` while the snapshot migrates to full records.

Pair with an Apify webhook to alert downstream systems only when `newCount`, `updatedCount`, or `removedCount` is non-zero.

***

### How to use

#### Apify Console

1. Open the [actor page](https://apify.com/ryanclinton/interpol-red-notices) and click **Try for free**.
2. On the Input tab, pick a **mode** (search by default).
3. For **search**: fill in filters. Leave all blank for the most recent Red notices.
4. For **screen**: paste a JSON array of subjects into the `subjects` field.
5. Click **Start**. Open the **Dataset** tab for results. Switch between **Overview**, **Screening matches**, and **Change log** views depending on the run.
6. Open the **Key-value store** tab for `SUMMARY` (analytics + run stats) and `CHANGES` (diff deltas).

#### API

```bash
## Search mode
curl "https://api.apify.com/v2/acts/A5qfeUw5yBCtcdhn4/runs" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -d '{
    "mode": "search",
    "nationality": "US",
    "sexId": "M",
    "maxResults": 50
  }'

## Screen mode
curl "https://api.apify.com/v2/acts/A5qfeUw5yBCtcdhn4/runs" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -d '{
    "mode": "screen",
    "noticeType": "red",
    "subjects": [
      {"subjectId": "cust_1", "fullName": "John Smith", "nationality": "US", "yearOfBirth": 1985}
    ],
    "minMatchScore": 0.5
  }'
```

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")

## Batch screening
run = client.actor("A5qfeUw5yBCtcdhn4").call(run_input={
    "mode": "screen",
    "noticeType": "red",
    "subjects": [
        {"subjectId": row["id"], "fullName": row["name"], "nationality": row["country"]}
        for row in customer_batch
    ],
    "minMatchScore": 0.6,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["matchBand"] in ("strong", "possible"):
        alert_compliance(item["matchedInputId"], item)
```

#### JavaScript

```javascript
import { ApifyClient } from "apify-client";

const client = new ApifyClient({ token: "YOUR_API_TOKEN" });

const run = await client.actor("A5qfeUw5yBCtcdhn4").call({
    mode: "search",
    diffMode: true,
    diffKey: "ru-red-daily",
    nationality: "RU",
    maxResults: 200,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const newOrUpdated = items.filter(i => i.changeType !== "removed");
console.log(`${newOrUpdated.length} new or updated Russian Red notices today`);
```

***

### Input parameters

#### Mode

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `mode` | `search` | `screen` | `search` | Run mode. Screen mode requires `subjects`. |
| `noticeType` | `red` | `yellow` | `un` | `red` | Which notice database to query. |

#### Search mode

| Parameter | Type | Description |
|-----------|------|-------------|
| `name` | String | Family name / surname. Case-insensitive. |
| `forename` | String | First name. Case-insensitive. |
| `nationality` | String | ISO 3166-1 alpha-2 code. |
| `sexId` | `M` | `F` | Filter by sex. |
| `ageMin` / `ageMax` | Integer | Age range. |
| `freeText` | String | Free-text search across all notice fields. |
| `issuingCountry` | String | Post-filter: ISO2 country that must appear in `arrest_warrants`. |
| `chargeContains` | String | Post-filter: case-insensitive substring in charge text or translation. |
| `maxResults` | 1–500 | Default 20. |

#### Screen mode

| Parameter | Type | Description |
|-----------|------|-------------|
| `subjects` | `ScreeningSubject[]` | Array of people/entities to screen. Each needs at least `name`, `forename`, `fullName`, or `freeText`. |
| `subjectsText` | String | Paste one subject per line (name only, or `name,ISO2,yearOfBirth`). Merged with `subjects`. `#` or `//` lines are comments. |
| `minMatchScore` | 0.0–1.0 | Candidates below this score are excluded. Default `0.5`. |
| `candidatesPerSubject` | 1–50 | Max candidates per subject. Default `5`. |
| `emitNoMatchStubs` | Boolean | Emit a stub row per subject with no matches. Default `false`. |
| `matchConfig` | Object | `{ requireNationalityMatch, requireSexMatch, requireDobYearMatch }`. When a flag is true, candidates missing that signal are disqualified (matchScore=0, matchBand=none). |
| `decisionPolicy` | Object | `{ strong, possible, weak, none }` → each maps to `block` / `review` / `allow`. Defaults: strong=block, possible=review, weak=allow, none=allow. |
| `watchlistId` | String | Stable ID for persistent watchlist monitoring. When set, the actor diffs this run's matches against the prior snapshot and annotates `watchlistChangeType` per row. |

#### Monitoring

| Parameter | Type | Description |
|-----------|------|-------------|
| `diffMode` | Boolean | Turn on field-level diff comparison vs prior snapshot. Search mode only. |
| `diffKey` | String | Custom key to share a baseline across schedules. |

#### Output

| Parameter | Type | Description |
|-----------|------|-------------|
| `includeImages` | Boolean | Fetch the full image gallery, not just the thumbnail. |

#### Example inputs

**Search:**

```json
{
    "mode": "search",
    "noticeType": "red",
    "nationality": "DE",
    "sexId": "M",
    "ageMin": 30,
    "ageMax": 60,
    "maxResults": 50
}
```

**Screen:**

```json
{
    "mode": "screen",
    "noticeType": "red",
    "subjects": [
        { "subjectId": "cust_001", "fullName": "John Smith", "nationality": "US", "yearOfBirth": 1985 },
        { "subjectId": "cust_002", "name": "PETROV", "forename": "IVAN", "nationality": "RU" }
    ],
    "minMatchScore": 0.55,
    "candidatesPerSubject": 3
}
```

**Scheduled monitoring:**

```json
{
    "mode": "search",
    "noticeType": "red",
    "diffMode": true,
    "diffKey": "global-ru-red",
    "nationality": "RU",
    "maxResults": 500
}
```

***

### Output

Every result is a row in the dataset. Search mode emits notice records; screen mode emits candidate matches with scoring fields attached; diff mode adds `changeType` and `changedFields`.

#### Full example (search mode)

```json
{
    "noticeType": "red",
    "subjectType": "person",
    "entityId": "2024/12345",
    "name": "SMITH",
    "forename": "JOHN",
    "fullName": "JOHN SMITH",
    "entityName": null,
    "aliases": [],
    "dateOfBirth": "1985/03/15",
    "dateOfBirthPrecision": "fullDate",
    "age": 40,
    "nationalities": ["US"],
    "nationalitiesLabels": ["United States"],
    "sexId": "M",
    "sexLabel": "Male",
    "eyeColor": ["BRO"],
    "eyeColorLabels": ["Brown"],
    "hairColor": ["BLA"],
    "hairColorLabels": ["Black"],
    "height": 1.82,
    "heightCm": 182,
    "weight": 85,
    "distinguishingMarks": "Tattoo on left forearm",
    "placeOfBirth": "NEW YORK",
    "countryOfBirth": "US",
    "countryOfBirthLabel": "United States",
    "languagesSpoken": ["ENG", "SPA"],
    "languagesSpokenLabels": ["English", "Spanish"],
    "arrestWarrants": [
        {
            "charge": "Fraud et blanchiment d'argent",
            "chargeTranslation": "Fraud and money laundering",
            "issuingCountry": "US",
            "issuingCountryLabel": "United States"
        }
    ],
    "charges": ["Fraud et blanchiment d'argent"],
    "chargeTranslations": ["Fraud and money laundering"],
    "issuingCountries": ["US"],
    "issuingCountriesLabels": ["United States"],
    "thumbnailUrl": "https://ws-public.interpol.int/notices/v1/red/2024-12345/images/12345678",
    "imageUrls": [],
    "interpolUrl": "https://www.interpol.int/en/How-we-work/Notices/View/Red#2024/12345",
    "exactMatch": true,
    "completeness": 0.92,
    "scrapedAt": "2026-04-21T10:15:00.000Z"
}
```

In **screen mode**, the same row gets `matchedInputId`, `matchScore`, `matchBand`, `matchReasons`, `mismatchSignals`, and `reviewRecommendation` attached.

In **diff mode**, the row gets `changeType` and (for updated rows) `changedFields`.

#### Output fields

All fields are documented in the dataset schema, which the Apify Console renders as field help text. Key groups:

- **Identifiers**: `recordType`, `schemaVersion`, `eventId`, `noticeType`, `subjectType`, `entityId`, `interpolUrl`
- **Name**: `name`, `forename`, `fullName`, `entityName`, `aliases`
- **Biographical**: `dateOfBirth`, `dateOfBirthPrecision`, `age`, `nationalities`/`Labels`, `sexId`/`sexLabel`, `placeOfBirth`, `countryOfBirth`/`Label`, `languagesSpoken`/`Labels`
- **Physical**: `eyeColor`/`Labels`, `hairColor`/`Labels`, `height`, `heightCm`, `weight`, `distinguishingMarks`
- **Legal**: `arrestWarrants[]`, `charges[]`, `chargeTranslations[]`, `issuingCountries`/`Labels`
- **Images**: `thumbnailUrl`, `imageUrls[]`
- **Scoring**: `exactMatch`, `completeness`, `scrapedAt`
- **Screen mode**: `matchedInputId`, `matchScore`, `matchBand`, `matchReasons[]`, `mismatchSignals[]`, `reviewRecommendation`, `confidence`, `scoringTrace[]`, `contradictions[]`, `percentileInBatch`
- **Decision layer**: `decision`, `decisionReason`, `policyRuleId`, `riskScore`, `riskLevel`, `materiality`, `decisionRisk`, `nextBestAction`, `task`
- **Agent surface**: `agentContract`, `summary`, `whyThisMatters`, `whyNow`, `eventTaxonomy`
- **Diff mode**: `changeType`, `changedFields[]`
- **Watchlist mode**: `watchlistChangeType`, `watchlistChangedFields[]`

#### Run analytics (`SUMMARY` key-value record)

```json
{
    "mode": "screen",
    "noticeType": "red",
    "returned": 27,
    "analytics": {
        "countsByNationality": { "US": 12, "RU": 8, "CN": 4, "MX": 3 },
        "countsByIssuingCountry": { "US": 14, "RU": 9, "FR": 4 },
        "countsBySubjectType": { "person": 26, "entity": 1 },
        "topCharges": [
            { "charge": "Fraud and money laundering", "count": 7 },
            { "charge": "Drug trafficking", "count": 5 }
        ],
        "completenessDistribution": { "high": 18, "medium": 7, "low": 2 },
        "imageCoverage": { "withThumbnail": 24, "withoutThumbnail": 3, "thumbnailCoveragePercent": 88.9 }
    },
    "screening": {
        "subjectsScreened": 125,
        "subjectsWithMatches": 9,
        "subjectsWithStrongMatches": 2,
        "countsByBand": { "strong": 2, "possible": 7, "weak": 18, "none": 0 },
        "countsByRecommendation": { "escalate": 2, "review": 9, "clear": 0 },
        "noMatchSubjectIds": ["cust_003", "cust_007", "..."]
    }
}
```

***

### Performance & cost

Pricing is pay-per-event at **$0.002 per notice returned** after filtering. Compute, proxy, and failed runs are not charged. **Typical batch screening (50 subjects) costs approximately $0.50 depending on match volume.**

| Scenario | Results | Approx. duration | Memory | Cost (PPE) |
|----------|---------|-------------------|--------|------------|
| Quick lookup | 1–5 | 5–10 seconds | 512 MB | $0.002–$0.010 |
| Default search | 20 | 15–30 seconds | 512 MB | $0.040 |
| Medium search | 100 | 30–60 seconds | 512 MB | $0.200 |
| Large search | 250 | 1–2 minutes | 512 MB | $0.500 |
| Maximum search | 500 | 2–4 minutes | 512 MB | $1.000 |
| Batch screen (50 subjects × ~5 candidates each) | ~250 | 2–4 minutes | 512 MB | ~$0.500 |

- The actor makes lightweight JSON API calls only — no browser rendering or heavy crawling.
- You are only charged for records actually returned after post-filters, diff exclusions, and `minMatchScore` filtering.
- Failed runs (blocked, timed out, zero results) push an error record and charge $0.
- Diff-mode `removed` rows don't charge — they're metadata, not new data.
- Apify's free tier includes $5/month of platform credits, which covers about 2,500 single-person lookups or 125 default 20-result runs.
- Scheduled daily diff-mode runs are the most cost-efficient pattern: the first run builds the baseline, subsequent runs charge only for new or updated notices.

***

### Under the hood

The actor follows a multi-phase pipeline:

1. **Proxy initialization** — US-exit residential proxy (Apify `RESIDENTIAL` group), with datacenter fallback and direct-connection last resort
2. **Paginated search** — up to 160 results per page against `ws-public.interpol.int/notices/v1/{red,yellow,un}`; iterates until `maxResults` hit or no more results
3. **Concurrent detail fetch** — 5 detail requests in parallel per batch with 300ms inter-batch delay to respect rate limits
4. **Optional image gallery fetch** — `includeImages: true` pulls every photo URL with the same concurrency pattern
5. **30-second request timeouts with 2 retries** and exponential backoff (1s, 2s) on transient errors
6. **Runtime shape detection** — `subjectType` is inferred from the presence of person signals (forename, DOB, sex, physical description, etc.); absent all signals, treated as an entity
7. **Decoding** — ISO 3166-1 region codes via `Intl.DisplayNames`, ISO 639-3 language codes likewise, plus an inline table for Interpol's 3-letter physical-description codes
8. **Scoring** — `completeness` against subject-type-appropriate fields; `exactMatch` against search input (case-insensitive)
9. **Post-filter** — `issuingCountry` and `chargeContains` are applied before push so you never pay for filtered-out records
10. **Screen-mode matching** — Jaro-Winkler name similarity across candidate names, forenames, `fullName`, and aliases; weighted with nationality, sex, and DOB signals
11. **Diff-mode snapshot** — stores the full filtered record set per fingerprint, then field-diffs the next run; legacy ID-only baselines are migrated automatically
12. **Per-item PPE charging** — every chargeable record is billed individually so spending limits stop cleanly mid-batch

```
Input (mode, filters, subjects)
   │
   ├─── mode=search ──► Paginated search ──► Detail fetch ──► Transform
   │                                                            │
   └─── mode=screen ──► Per-subject search loop ──► Transform ──┤
                                                                 │
                                           ┌─────────────────────┘
                                           │
                                           ▼
                              Post-filter + enrich + score
                                           │
                   ┌───────────────────────┤
                   │                       │
                   ▼                       ▼
            diffMode=true?         matching (screen mode)
                   │                       │
                   ▼                       ▼
          Field-level diff         Per-subject ranking
                   │                       │
                   └───────┬───────────────┘
                           ▼
                 Dataset push + per-item PPE charge
                           │
                           ▼
               SUMMARY + CHANGES key-value records
```

***

### Interpol notice families

| Family | Purpose | Public API? | Supported here? |
|--------|---------|-------------|-----------------|
| Red | Wanted for arrest | Yes | Yes |
| Yellow | Missing persons | Yes | Yes |
| UN | UN Security Council consolidated sanctions (individuals + entities) | Yes | Yes |
| Blue | Information-collection requests | Internal only | No |
| Green | Warnings about persons | Internal only | No |
| Black | Unidentified bodies | Internal only | No |
| Orange | Warnings about events/objects | Internal only | No |
| Purple | Modi operandi | Internal only | No |
| Silver | Asset recovery | Pilot, no public API extract | Not yet |

The actor is structured so adding a new notice family is a single-file change (entry in `NOTICE_BASES` + input-schema enum) when Interpol exposes them publicly.

***

### How this compares

Honest positioning against the alternatives. The sibling actors (`opensanctions-search`, `ofac-sanctions-search`) are complementary, not competitive — most compliance workflows run several side by side.

| Capability | This actor | OpenSanctions (sibling) | OFAC (sibling) | Raw Interpol API |
|-----------|:----------:|:-----------------------:|:--------------:|:----------------:|
| Interpol Red / Yellow / UN coverage | Yes | Partial | No | Yes |
| Batch subject screening | Yes | Yes | Yes | Build yourself |
| Match reasons + one-line summary | Yes | Partial | Partial | Build yourself |
| Decision policy (block / review / allow) | Yes | No | No | Build yourself |
| Field-level change monitoring | Yes | No | No | Build yourself |
| Persistent watchlists with deltas | Yes | No | No | Build yourself |
| Explainable score (reasons + mismatches) | Yes | Partial | Partial | Build yourself |
| Decoded country / language / charge labels | Yes | Yes | Partial | No |
| Multi-source aggregation (sanctions + PEP + watchlists) | No — pair with OpenSanctions | Yes | No | No |
| OFAC SDN list | No — pair with OFAC sibling | Yes | Yes | No |

If you need Interpol coverage with screening depth, decisioning, and monitoring, this is the right tool. If you need broad sanctions aggregation across EU/UN/UK/OFAC lists, pair this with `ryanclinton/opensanctions-search`. If you specifically need OFAC SDN with fuzzy matching, pair with `ryanclinton/ofac-sanctions-search`.

#### When to use this actor

**Use this actor when:**

- You need to screen individuals or entities against Interpol Red, Yellow, or UN Security Council notices
- You want explainable match output (reasons, mismatches, one-line summary) — not just raw hits
- You need workflow-ready decisions (`block` / `review` / `allow`) via a policy layer
- You want field-level change monitoring on a watchlist (new matches, updated charges, withdrawn notices)
- You're building a KYC onboarding pipeline, an investigative watchlist, or a scheduled compliance screen
- You need a batch API that handles hundreds of subjects in one run

**Do NOT use this actor when:**

- You need a full sanctions aggregator (EU + UN + UK + OFAC + regional) — use `opensanctions-search` instead
- You specifically need the OFAC SDN list with fuzzy matching — use `ofac-sanctions-search` instead
- You need Politically Exposed Person (PEP) data — Interpol does not publish PEP data
- You need adverse media or news-article screening — Interpol does not publish adverse media
- You need Blue, Green, Black, Orange, or Purple Interpol notice colours — these are Interpol-internal and not publicly accessible

***

### Built for reliable automation

No SLA promises — just stating what's already in the product:

- **Deterministic schema** — field names, types, and row structure stay stable across runs. Dataset and output schemas are declared so AI tools and API consumers don't have to guess shapes.
- **Per-request timeouts and retries** — 30-second timeout per HTTP call, 2 automatic retries with exponential backoff (1s, 2s).
- **Classified failure records** — on error, the actor emits a structured `{ failureType, message, recommendation }` record instead of crashing. Every run ends with a `SUMMARY` regardless of outcome.
- **Per-item PPE charging** — spending limits stop cleanly mid-batch. You're never charged for data you didn't receive.
- **Zero cost on failure** — blocked, timed-out, and zero-result runs charge $0.
- **Legacy data compatibility** — v1 diff-mode baselines are auto-upgraded to v2 snapshots on the first run; existing schedules keep working.

***

### What this does NOT do

Honest positioning — know what you're getting before committing to a workflow.

- **Not a replacement for enterprise compliance platforms.** Refinitiv World-Check, Dow Jones Risk & Compliance, ComplyAdvantage, and LexisNexis WorldCompliance combine hundreds of sources with journalist-curated adverse media, PEP research, and ongoing monitoring. This actor wraps Interpol's public notice database with analyst-ready matching and monitoring — it's one screening layer, not a full compliance suite.
- **Not a general sanctions screener.** For OFAC, EU, UK HMT, and other sanctions lists, pair with a dedicated screening actor (e.g. `ryanclinton/ofac-sanctions-search`, `ryanclinton/opensanctions-search`). Pair them with this actor for broader coverage.
- **Not a PEP database.** Politically Exposed Persons data comes from legislative records, beneficial-ownership registries, and curated profiles — not from Interpol.
- **Not adverse media.** No news-article scraping, no sentiment analysis, no reputational risk scoring.
- **Not a phonetic-only matcher.** Matching combines Jaro-Winkler string similarity with structured signals (nationality, sex, DOB). Soundex, Metaphone, and script-aware transliteration are not part of the score. For pure phonetic matching across scripts, layer a dedicated name-matching actor on top.
- **Not a historical archive.** The Interpol API returns currently active notices only. Withdrawn notices disappear at the source.
- **Does not expose non-public Interpol notice colours.** Only Red, Yellow, and UN are publicly served. Blue, Green, Black, Orange, Purple are internal only; Silver is still in pilot.

Best-in-class access to Interpol's public data with explainable matching and monitoring at compliance-friendly pricing — not a white-labeled World-Check.

***

### Common mistakes

- **Relying on name matching alone.** Common surnames produce false positives. Use `matchConfig.requireNationalityMatch` or `requireDobYearMatch`, or switch to `policyPreset: "strict-kyc"`, to disqualify hits lacking identity confirmation.
- **Skipping `watchlistId` in screen mode.** Without a watchlist ID, every run is a fresh screen — you get no change detection. Set a stable `watchlistId` and the actor automatically diffs against the last run.
- **Ignoring `matchReasons` and `matchSummary`.** These are there specifically for audit trails. Storing only `matchScore` loses the explainability compliance reviewers need.
- **Treating a Red Notice match as an arrest warrant.** A Red Notice is a request to locate, not a conviction. Always pair `decision === "block"` with human review before acting.
- **Expecting non-public notice colours.** Blue, Green, Black, Orange, Purple are Interpol-internal — not exposed by the public API and therefore not returned by this actor.
- **Running unfiltered large searches.** `maxResults: 500` with no filters pulls the most recent 500 Red notices globally — rarely what you want. Set `nationality`, `issuingCountry`, or `chargeContains` to narrow first.

***

### Stable enum tokens

The following enums are **stability-promised**: additive changes only across minor versions; values are safe to switch on in downstream code.

| Field | Values |
|-------|--------|
| `recordType` | `notice`, `screening-match`, `screening-stub`, `watchlist-removed`, `diff-removed`, `error` |
| `decision` | `block`, `review`, `allow` |
| `matchBand` | `strong`, `possible`, `weak`, `none` |
| `reviewRecommendation` | `escalate`, `review`, `clear` |
| `riskLevel` | `critical`, `high`, `medium`, `low` |
| `materiality` | `blocker`, `major`, `minor`, `cosmetic` |
| `eventTaxonomy` | `exact-name-match`, `comprehensive-match`, `name-and-dob`, `name-and-nationality`, `alias-hit`, `name-only`, `no-match`, `diff-new`, `diff-updated`, `diff-removed`, `watchlist-new-match`, `watchlist-updated`, `watchlist-removal` |
| `changeType` (diff) | `new`, `updated`, `removed` |
| `watchlistChangeType` | `new-match`, `match-updated`, `match-removed` |
| `failureType` | `blocked`, `timeout`, `no-data`, `parse-error`, `invalid-input`, `upstream-error` |
| `agentContract.decision` | `block`, `review`, `allow`, `noop` |

***

### Output controls (premium)

- **`outputProfile`** (`minimal`/`standard`/`llm`/`full`) — strips fields at push time without writing a JSONata expression. `minimal` is decision-only (~10 fields), `llm` keeps natural-language fields for agent consumers, `full` (default) keeps every field.
- **`includeAgentContract`** (default `true`) — writes a top-level `agentContract: { decision, confidence, nextAction, costToAct }` on every record and on the run-level SUMMARY for MCP/agent consumers that want a single switch-on field.
- **`scoringWeights`** — override the screen-mode signal weights (`name`/`nationality`/`sex`/`dob`). Defaults: 0.55 / 0.15 / 0.10 / 0.20. Useful when your KYC environment cares more about DOB than name (e.g. wide diaspora communities).
- **`circuitBreakerThreshold`** — abort a screen-mode batch after N consecutive subject-fetch failures. `0` disables (default). Recommended `3` for unattended schedules.
- **`webhookUrl`** (Slack or Discord) — posts a rich embed at run end. Vendor auto-detected. Search-mode embeds include `new/updated/removed` counts (diff mode only) or total/returned counts; screen-mode embeds include the run-level `decision` plus blocked/review/new-watchlist counts.

Every run also writes:

- **`OUTPUT`** (KV) — full deterministic shape `{ schemaVersion, mode, runDecision, summary, records[] }`. Same shape every run, idempotent for downstream agents.
- **`OUTPUT.csv`** (KV) — wide CSV with the analyst-relevant columns for direct paste into Excel / Google Sheets / case-management imports.
- **`RECEIPTS`** (KV) — per-charge audit trail `[{ timestamp, action, cost, eventId }]`.
- **`CARDS`** (KV, screen mode only) — actionable card list `[{ title, severity, action, link, subjectId, summary }]` for UI rendering. Only emitted when at least one decision is `block` or `review`.

***

### Use as a Dify tool

The actor's input and dataset shapes are stable and typed, so it slots in as a Dify tool with no glue code.

1. In Dify, add an HTTP Request tool calling `POST https://api.apify.com/v2/acts/A5qfeUw5yBCtcdhn4/run-sync-get-dataset-items?token=YOUR_API_TOKEN`.
2. Body: standard JSON input — at minimum `{"mode": "screen", "subjects": [...]}` or `{"mode": "search", "name": "...", "maxResults": 10}`.
3. Response: a JSON array of records. Each record has `agentContract.decision` ∈ {`block`,`review`,`allow`,`noop`}, plus `summary` (≤280 chars LLM-friendly) and `whyThisMatters` for natural-language reasoning.
4. Dify branch logic on `agentContract.decision`: route `block` → reject, `review` → analyst notify, `allow` → proceed.
5. For batch-monitoring use, add a Dify schedule + set `outputProfile: "minimal"` to keep response tokens low.

Pair with the LLM-friendly `summary` field instead of joining raw fields — cuts prompt-injection risk and keeps agent prompts tight.

***

### Integrations

- **Google Sheets** — export dataset rows directly to a compliance review sheet
- **Slack / Email** — webhook on run completion; pair with `diffMode` or `matchBand` filters to alert only on genuinely new risk
- **Zapier / Make** — route `matchBand: strong` rows to case-management systems; route `matchBand: possible` to human review queues
- **REST API** — `GET /datasets/{id}/items?clean=1&view=matches` for pre-filtered screening output
- **Scheduled runs** — Apify's built-in scheduler drives diff-mode monitoring and recurring screens
- **MCP / AI agents** — the dataset schema and output schema are declared so AI tools (Claude, ChatGPT agents) can consume results with typed field metadata

***

### Responsible use

**A Red Notice is NOT an international arrest warrant.** It is a request from Interpol to law enforcement worldwide to locate and provisionally arrest a person pending extradition, surrender, or similar legal action. The issuance of a Red Notice does not mean the individual has been convicted of a crime — it means a member country has requested international cooperation in locating and detaining the individual.

- **Treat data with appropriate context.** Red Notice subjects are wanted by national authorities, not convicted by an international court. Always consider the legal context of the issuing country.
- **Compliance screening context.** Red Notice data should be one component of a broader screening program — not the sole basis for decisions. Pair with sanctions lists, PEP databases, and adverse-media screening.
- **Privacy and data handling.** Although this data is publicly available through Interpol's official channels, it pertains to identified individuals. Handle it responsibly and in accordance with applicable privacy laws (including GDPR where applicable).
- **No automated decision-making.** Do not use Red Notice data as the sole input for automated decisions that affect individuals (e.g., denying services, blocking accounts). Human review should always be part of any decision process involving wanted-persons data — the `reviewRecommendation` field is a routing hint, not an adjudication.
- **Verify through official channels.** For any law enforcement, legal, or high-stakes compliance action, verify information directly through official Interpol channels or the relevant national law enforcement authority.

***

### Limitations

- **Maximum 500 notices per search run** — cap enforced. Split coverage across filters (nationality, issuing country) for broader sweeps.
- **Interpol API availability** — results depend on Interpol's public API. The actor handles transient failures with retries and classified failure records, but prolonged outages will cause runs to fail.
- **Public data only** — some Red Notices are withheld at the requesting country's discretion.
- **Name transliteration** — names may appear in transliterated Latin characters from the original script. Fuzzy matching handles this partially; for deep transliteration tolerance, pre-process names or post-process results.
- **Physical codes are decoded best-effort** — the label map covers common Interpol codes. Unknown codes pass through as-is.
- **KV snapshot size** — diff-mode snapshots store full records per filter fingerprint. Very broad monitoring (e.g., all 500 Red notices globally) increases KV storage; narrow filters stay lean.

***

### FAQ

**What is an Interpol Red Notice?**
A Red Notice is a request to law enforcement worldwide to locate and provisionally arrest a person pending extradition, surrender, or similar legal action. It's issued by Interpol at the request of a member country's national central bureau. It is not an international arrest warrant — arrest authority remains with individual countries.

**How is this different from a World-Check or OFAC screen?**
This actor covers Interpol's public notices. World-Check, Dow Jones, ComplyAdvantage, and LexisNexis aggregate many additional sources (sanctions, adverse media, PEPs, court records). OFAC covers the US Treasury sanctions list specifically. For comprehensive compliance, use several screening tools side by side; this actor slots in as the Interpol-coverage layer.

**How accurate is the fuzzy match score?**
`matchScore` blends Jaro-Winkler name similarity (weighted 55%), nationality match (15%), sex match (10%), and DOB match (20%). Bands are `strong >= 0.85`, `possible >= 0.60`, `weak >= 0.35`. `reviewRecommendation` builds on the band: `strong` with no mismatches → `escalate`; `strong` or `possible` with any mismatch → `review`; `weak`/`none` → `clear`. Always pair the recommendation with human review for compliance decisions.

**Can I use this for large onboarding batches (1000+ subjects)?**
Yes, but each subject triggers its own Interpol search, so end-to-end time scales with subject count. For batches over ~200, consider splitting into multiple scheduled runs or pre-filtering with tighter `nationality` hints in each subject to reduce per-subject candidate volume. Total run time at 200 subjects with ~5 candidates each is typically 5–10 minutes.

**How does diff mode detect field changes, not just new records?**
Each diff-mode run writes a full-record snapshot to the key-value store (not just IDs). The next run loads the prior snapshot and compares each current record against its prior counterpart field-by-field across the tracked-fields set. Updated records emit with `changeType: updated` and `changedFields: [...]` listing the exact fields that differ.

**Does diff mode work on the first run?**
On the very first run (no prior snapshot), every returned record is flagged `new` and the snapshot is written. From the second run onward, diffs are computed against the prior snapshot.

**What's the difference between `name` and `freeText`?**
`name` searches the surname field specifically. `freeText` searches across all notice fields (names, charges, marks, aliases) — broader but noisier.

**Why is `chargeTranslation` sometimes null?**
Interpol only provides translations for non-English charges. English-language charges have no translation field, so `chargeTranslation` stays `null`. The `chargeContains` post-filter and screening match logic always check both the raw charge and its translation.

**What does `subjectType: entity` mean?**
UN Security Council sanctions can target organizations, groups, or other non-person entities (e.g., ISIL, sanctioned companies). When the API record lacks person signals (no forename, DOB, sex, physical description), the actor emits `subjectType: entity` and populates `entityName` instead of person-specific fields.

**Can I schedule daily monitoring?**
Yes. Use Apify's scheduler to run the actor on any cadence (hourly, daily, weekly). Combine with `diffMode: true` and a webhook to alert downstream systems only when there are genuine deltas.

**What if I want to monitor several queries with different filters simultaneously?**
Each schedule gets its own baseline automatically (the key is derived from the filter fingerprint). Or set a custom `diffKey` per schedule for explicit control.

**Does this actor support AI agents / MCP?**
Yes. The dataset schema and output schema are declared with per-field descriptions, so AI tools consuming the results know what each field means. The `SUMMARY` and `CHANGES` key-value records give agents structured run metadata without having to parse the dataset.

***

### Related actors

| Actor | Description | Link |
|-------|-------------|------|
| OFAC Sanctions Search | Search the US Treasury OFAC SDN list with fuzzy matching. Pair with Interpol for broader compliance coverage. | [apify.com/ryanclinton/ofac-sanctions-search](https://apify.com/ryanclinton/ofac-sanctions-search) |
| OpenSanctions Search | Aggregated sanctions, watchlists, and PEPs across EU/UN/UK. | [apify.com/ryanclinton/opensanctions-search](https://apify.com/ryanclinton/opensanctions-search) |
| FBI Most Wanted Search | FBI's Most Wanted database for fugitives and wanted persons. | [apify.com/ryanclinton/fbi-wanted-search](https://apify.com/ryanclinton/fbi-wanted-search) |
| UK Police Crime Data | UK police crime reports and street-level crime data. | [apify.com/ryanclinton/uk-police-crime-data](https://apify.com/ryanclinton/uk-police-crime-data) |
| SEC Insider Trading | SEC Form 4 filings for compliance and investment research. | [apify.com/ryanclinton/sec-insider-trading](https://apify.com/ryanclinton/sec-insider-trading) |

# Changelog

This Actor's version history is a separate document: https://apify.com/ryanclinton/interpol-red-notices/changelog.md

# Actor input Schema

## `mode` (type: `string`):

search = query Interpol with filters. screen = submit a batch of subjects and get ranked candidate matches with explainability.

## `noticeType` (type: `string`):

Which Interpol notice database to search. Red = wanted for arrest. Yellow = missing persons. UN = UN Security Council consolidated sanctions list (includes individuals and entities).

## `name` (type: `string`):

Search mode only. Family name / surname to search for. Case-insensitive — Interpol stores names in uppercase Latin transliteration (e.g. SMITH, PETROV) but you can type any case.

## `forename` (type: `string`):

Search mode only. First name / forename to search for. Case-insensitive.

## `nationality` (type: `string`):

Search mode only. Two-letter ISO 3166-1 country code (e.g. US, GB, DE, FR, RU). Uppercase or lowercase accepted.

## `sexId` (type: `string`):

Search mode only. Filter by sex.

## `ageMin` (type: `integer`):

Search mode only. Minimum age of the subject.

## `ageMax` (type: `integer`):

Search mode only. Maximum age of the subject.

## `freeText` (type: `string`):

Search mode only. Free text search across all notice fields (names, charges, marks, etc.).

## `issuingCountry` (type: `string`):

Search mode post-filter. Two-letter ISO country code. Only return notices with at least one arrest warrant issued by this country. Applied after fetch.

## `chargeContains` (type: `string`):

Search mode post-filter. Case-insensitive substring. Only return notices whose charge text or translated charge contains this phrase (e.g. "fraud", "terrorism", "homicide"). Applied after fetch.

## `subjects` (type: `array`):

Screen mode only. Array of people (or entities) to screen against the selected Interpol notice database. Each subject can include: subjectId (your tracking ID), name (surname), forename, fullName, aliases, dateOfBirth, yearOfBirth, nationality (ISO2), sex (M/F), freeText. At minimum provide one of: name, forename, fullName, or freeText.

## `subjectsText` (type: `string`):

Screen mode only. Paste one subject per line. Simple formats supported: just names (one per line) OR CSV/tab-separated 'name,ISO2,yearOfBirth'. Lines starting with # or // are treated as comments. Merged with the structured subjects array if both are provided.

## `minMatchScore` (type: `number`):

Screen mode only. Candidates below this score (0.0–1.0) are excluded from output. 0.5 = balanced, 0.6–0.7 = tighter, 0.4 = looser (more false positives).

## `candidatesPerSubject` (type: `integer`):

Screen mode only. Maximum candidates returned per subject (sorted by match score, highest first). Default 5.

## `emitNoMatchStubs` (type: `boolean`):

Screen mode only. When true, subjects with zero matches still appear in the dataset as stub rows with matchBand='none'. Useful for audit completeness; skip for cleaner output.

## `policyPreset` (type: `string`):

Screen mode only. Bundle of matchConfig + decisionPolicy defaults for common use cases. strict-kyc = require nationality/sex/DOB match, treat possible as block. balanced = default-balanced (recommended). loose = no strict requirements, treat strong as review (journalism/research). custom = no preset, use your own matchConfig/decisionPolicy. Individual matchConfig / decisionPolicy fields override preset values.

## `matchConfig` (type: `object`):

Screen mode only. Overrides policyPreset match rules. Enable strict-match flags to disqualify candidates that fail key signals regardless of name similarity. Disqualified candidates receive matchScore=0, matchBand='none'.

## `decisionPolicy` (type: `object`):

Screen mode only. Overrides policyPreset decisions. Map each match band to a decision: 'block', 'review', or 'allow'. Unset bands fall back to preset defaults. Each candidate row gets decision / decisionReason / policyRuleId fields.

## `watchlistId` (type: `string`):

Screen mode only. Stable identifier for this screening watchlist. When set, the actor snapshots matches under this key and diffs subsequent runs — emits watchlistChangeType on each row: new-match / match-updated / match-removed. Great for 'monitor these 73 people forever' workflows.

## `includeImages` (type: `boolean`):

Fetch the full image gallery for each notice (not just the thumbnail). Adds one extra API call per notice.

## `diffMode` (type: `boolean`):

Search mode only. Compare this run against the previous run with the same filters. Returns only notices that are new, updated (field-level), or removed since the last snapshot. The baseline is stored per search fingerprint in the key-value store — pair with a daily Apify schedule for automated monitoring.

## `diffKey` (type: `string`):

Custom key to store the diff baseline under. Leave blank to auto-derive from your search filters. Use a custom key when you want multiple schedules to share a baseline.

## `maxResults` (type: `integer`):

Search mode only. Maximum number of notices to fetch and return (1–500). Post-filters reduce this after fetch. Screening mode ignores this — use candidatesPerSubject instead.

## `outputProfile` (type: `string`):

Field-level filter applied at push time. 'minimal' = decision-only (recordType, eventId, fullName, matchScore, matchBand, decision, agentContract, summary, interpolUrl). 'standard' = adds risk/confidence/scoringTrace/eventTaxonomy + identity. 'llm' = LLM-friendly natural-language subset. 'full' = every field (default). Pair with outputMode-style downstream filtering.

## `includeAgentContract` (type: `boolean`):

Add a top-level agentContract { decision, confidence, nextAction, costToAct } on every record (and run-level on SUMMARY) for MCP and AI-agent consumers. Maps decision + matchBand + matchScore to a stable enum.

## `scoringWeights` (type: `object`):

Override the default screen-mode scoring weights for power users. Default: name=0.55, nationality=0.15, sex=0.10, dob=0.20. Weights do not need to sum to 1 — final score is clamped to 0..1 with mismatch penalties.

## `circuitBreakerThreshold` (type: `integer`):

Abort the screen-mode batch after this many consecutive subject-fetch failures (proxy/network/upstream errors). 0 = disabled (continue through all subjects). Recommended: 3 for unattended schedules so a proxy outage doesn't burn the whole batch.

## `webhookUrl` (type: `string`):

Optional. Slack or Discord incoming webhook URL. Posts a rich embed with run decision + counts (blocked/review/new/updated) on screen mode, or new/updated/removed on diff search. Auto-detects vendor.

## Actor input object example

```json
{
  "mode": "search",
  "noticeType": "red",
  "subjects": [
    {
      "subjectId": "cust_001",
      "fullName": "John Smith",
      "nationality": "US",
      "yearOfBirth": 1985
    },
    {
      "subjectId": "cust_002",
      "name": "Petrov",
      "forename": "Ivan",
      "nationality": "RU"
    }
  ],
  "minMatchScore": 0.5,
  "candidatesPerSubject": 5,
  "emitNoMatchStubs": false,
  "policyPreset": "balanced",
  "matchConfig": {
    "requireNationalityMatch": false,
    "requireSexMatch": false,
    "requireDobYearMatch": false
  },
  "decisionPolicy": {
    "strong": "block",
    "possible": "review",
    "weak": "allow",
    "none": "allow"
  },
  "includeImages": false,
  "diffMode": false,
  "maxResults": 10,
  "outputProfile": "full",
  "includeAgentContract": true,
  "scoringWeights": {
    "name": 0.55,
    "nationality": 0.15,
    "sex": 0.1,
    "dob": 0.2
  },
  "circuitBreakerThreshold": 0,
  "webhookUrl": ""
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `changes` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "search",
    "noticeType": "red",
    "subjects": [
        {
            "subjectId": "cust_001",
            "fullName": "John Smith",
            "nationality": "US",
            "yearOfBirth": 1985
        },
        {
            "subjectId": "cust_002",
            "name": "Petrov",
            "forename": "Ivan",
            "nationality": "RU"
        }
    ],
    "matchConfig": {
        "requireNationalityMatch": false,
        "requireSexMatch": false,
        "requireDobYearMatch": false
    },
    "decisionPolicy": {
        "strong": "block",
        "possible": "review",
        "weak": "allow",
        "none": "allow"
    },
    "maxResults": 10,
    "scoringWeights": {
        "name": 0.55,
        "nationality": 0.15,
        "sex": 0.1,
        "dob": 0.2
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ryanclinton/interpol-red-notices").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "search",
    "noticeType": "red",
    "subjects": [
        {
            "subjectId": "cust_001",
            "fullName": "John Smith",
            "nationality": "US",
            "yearOfBirth": 1985,
        },
        {
            "subjectId": "cust_002",
            "name": "Petrov",
            "forename": "Ivan",
            "nationality": "RU",
        },
    ],
    "matchConfig": {
        "requireNationalityMatch": False,
        "requireSexMatch": False,
        "requireDobYearMatch": False,
    },
    "decisionPolicy": {
        "strong": "block",
        "possible": "review",
        "weak": "allow",
        "none": "allow",
    },
    "maxResults": 10,
    "scoringWeights": {
        "name": 0.55,
        "nationality": 0.15,
        "sex": 0.1,
        "dob": 0.2,
    },
}

# Run the Actor and wait for it to finish
run = client.actor("ryanclinton/interpol-red-notices").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "search",
  "noticeType": "red",
  "subjects": [
    {
      "subjectId": "cust_001",
      "fullName": "John Smith",
      "nationality": "US",
      "yearOfBirth": 1985
    },
    {
      "subjectId": "cust_002",
      "name": "Petrov",
      "forename": "Ivan",
      "nationality": "RU"
    }
  ],
  "matchConfig": {
    "requireNationalityMatch": false,
    "requireSexMatch": false,
    "requireDobYearMatch": false
  },
  "decisionPolicy": {
    "strong": "block",
    "possible": "review",
    "weak": "allow",
    "none": "allow"
  },
  "maxResults": 10,
  "scoringWeights": {
    "name": 0.55,
    "nationality": 0.15,
    "sex": 0.1,
    "dob": 0.2
  }
}' |
apify call ryanclinton/interpol-red-notices --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ryanclinton/interpol-red-notices"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/A5qfeUw5yBCtcdhn4/builds/wK1UUiSgi77zFOtri/openapi.json
