# Sanctions Screening API - OFAC, EU, UN Consolidated Lists (`neverempty/sanctions-screening-official`) Actor

Screen customer or supplier names against the official OFAC SDN, EU financial sanctions and UN Security Council lists. Catches aliases, word-order swaps and misspellings. Every row says which list version it was checked against, and no-hit is an answer, not an empty cell.

- **URL**: https://apify.com/neverempty/sanctions-screening-official.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Lead generation, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 name screeneds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Sanctions Screening API - OFAC, EU, UN Consolidated Lists

Give it a list of customer or supplier names. Get back, for **every name**, whether it appears on the **US Treasury OFAC SDN list**, the **EU financial sanctions list** or the **UN Security Council consolidated list** — with the alias it matched, how close the match was, and **which version of each list it was checked against**.

Straight from the three government files. No third-party aggregator, no licence to worry about, no API key.

### Why "per name", not "per hit"

Every other screening Actor in the store charges for the **records it returns**. Screen 10,000 suppliers, find 3 matches, and they bill you for 3 records — which sounds generous until you realise the work you actually paid for was the 9,997 names that came back clean, and that is the answer you needed for your audit file.

**This one charges per name screened.** A clean list costs exactly the same as a dirty one, because it took exactly the same work, and a clean row is a real answer. Rows that could *not* be screened — an empty input, or a run where no list could be downloaded — are **not charged**.

### A clean name is an answer, not an empty cell

Every input name produces exactly one row:

| Field | Meaning |
|---|---|
| `query` | The name you gave |
| `hitCount` | **How many matches were found**, `0` included. This is the number found, not the number returned |
| `matches` | The best `maxMatches` of them. If more were found, `reason` says so |
| `topScore` | Best match score, `0` when nothing matched |
| each match | `listedName`, `matchedName`, `matchType`, `score`, `source`, `programs`, `countries`, `listedOn`, `reference` |
| `listVersions` | **The version of each list this answer is based on** — e.g. `{"ofac":"08/26/2026","eu":"2026-08-05T16:47:04+02:00","un":"2026-08-26T23:00:04Z"}` |
| `listsUsed` / `listsUnavailable` | Which lists this answer actually covers, and which it does not |

That last pair matters more than anything else here. A screening tool that quietly answers "no match" after failing to fetch a list is worse than no tool at all, because it produces a clean audit trail for a check that never happened.

- **No list could be read** → every row comes back `ok: false`, `screened: false`, `hitCount: null`, the failures are named, and **nothing is charged**.
- **Some lists could be read** → the row is answered, but `reason` names the lists it was **NOT** checked against. It never says "every list".

### `matchType` says how it matched, not just that it did

| Value | Meaning |
|---|---|
| `exact-name` / `exact-alias` | After normalisation, the strings are **identical** |
| `reordered-name` / `reordered-alias` | Same words, different order — `HUSSEIN SADDAM` against the alias `SADDAM HUSSEIN` |
| `contained-name` / `contained-alias` | Every word you typed appears in the listed name, which has one or two more — `Nicolas Maduro` against `Nicolas MADURO MOROS` |
| `fuzzy-name` / `fuzzy-alias` | Close but not identical. `score` says how close |

A reordered match is never labelled `exact`, because that column ends up in an audit file.

### What it catches

Measured on the real lists on 2026-08-27 — **19,319 OFAC + 6,234 EU + 1,011 UN records, 78,733 names including aliases**:

| You type | It finds | How |
|---|---|---|
| `Saddam Hussein Al-Tikriti` | 21 records, the three exact-name ones first | exact name, then aliases and relatives |
| `Abu Ali` | 27 records — it is a very common alias, and every one of them is returned with its own score | alias |
| `HUSSEIN SADDAM` | The same person | reordered |
| `aero caribbean` | AEROCARIBBEAN AIRLINES | alias, spacing |
| `Sadam Husein` | SADDAM HUSSEIN | **misspelled**, score 0.857 |
| `Bank Meli Iran` | BANK MELLI IRAN, and the EU's `Bank Melli` | misspelled, both lists |
| `김동운` | KIM Tong-un | **Korean script** |
| `Χαμάς` | Hamas | **Greek script** |
| `صدام حسين التكريتي` | SADDAM HUSSEIN AL-TIKRITI | **Arabic original script, from the UN list** |
| `Dorde Vukovic` | matches records written `Đorđe Vuković` | Latin-extended letters folded |
| `Nicolas Maduro` | Nicolas MADURO MOROS | **middle name missing** |
| `Toyota Motor Corporation` | nothing | correctly clean |
| `John Smith` | nothing | correctly clean |

Matching uses Levenshtein distance for spelling and a token overlap for word order, blocked on both whole words and three-letter word prefixes — which is what makes `Sadam Husein` reachable at all, since neither of its words appears in any list.

`minScore` is yours to set, between `0.5` and `1.0`. `1.0` returns only identical strings and word-order variants of them. `0.85` is the default and catches the misspellings above. Below `0.8`, false alarms start to outnumber real hits — at `0.5`, most ordinary names match something. **A value outside `0.5`–`1.0` falls back to the default rather than to the loosest setting**, so a typo in your config cannot silently flood you with false positives.

### What it does not do

Being honest about this is part of the product:

- **A one-word query is not enough.** Measured against 600 real listed entities and individuals on 2026-08-27, at the default threshold:

  | What you type | Found |
  |---|---|
  | The listed name as published | **100%** |
  | The listed name with one letter wrong | **100%** |
  | Surname first / word order swapped | **100%** |
  | A company name **without** its `LTD` / `LLC` / `OAO` suffix | **93%** |
  | A person **without** their middle name | **84%** |
  | Only the first two words of a long listed name | **74%** |
  | A single word (`Hezbollah`, `Gazprom`) | **not matched at all** |

  A single word is deliberately never treated as "contained in" a longer listed name, because `Ali` alone would match dozens of unrelated people. Type the name as your records hold it — two words or more.
- **Common personal names will hit.** Screening 50 ordinary names on 2026-08-27 (30 large companies, 20 everyday personal names) produced **7 matches, every one of them a personal name** — `Wei Zhang`, `Ali Hassan`, `Muhammad Ali` and similar. **None of the 30 company names produced a false hit.** A person-name hit is a candidate to review, not a finding.
- It screens **names**. It does not check dates of birth, passport numbers or addresses, so a common name will produce matches you must review by hand.
- It is **not a full KYC or PEP check**. There is no politically-exposed-persons list here, and no UK, Swiss, Canadian or Australian list yet. OFAC's SSI (non-SDN) list is not included either.
- It reads the lists **fresh on every run**, which takes about **40 seconds** for all three (57 MB). It does not tell you what changed since your last run.
- A match is a **candidate**, not a legal determination. The `reference` on every match is the government's own record id, so you can open the source and decide.

### Input

| Field | Default | What it does |
|---|---|---|
| `names` | 3 sample names | One name per line, people or companies. Rows that are not text are returned with a note, and not charged |
| `sources` | `ofac`, `eu`, `un` | Which lists to check. Repeats are ignored, and an id this Actor does not know is reported back to you rather than dropped |
| `minScore` | `0.85` | Match threshold, `0.5`–`1.0` |
| `maxMatches` | `10` | Matches returned per name, best first. `hitCount` still reports the true total |
| `maxRetries` | `3` | Retries when a government server times out. A 404 is never retried |

### Sources

| List | Publisher | Updated |
|---|---|---|
| SDN | US Department of the Treasury, OFAC | Almost daily — the copy read on 2026-08-27 was published 2026-08-26 |
| Financial Sanctions List (FSF) | European Commission | On regulation changes |
| Consolidated List | UN Security Council | On committee decisions |

All three are public government files, fetched directly. The exact version of each is written into every row, so an answer you filed six months ago can still be explained.

### Other tools by NeverEmpty

Every NeverEmpty Actor follows the same rule: it never returns an empty result to mean two different things, and it only charges for rows that actually carry an answer.

- **[eu-tender-monitor](https://apify.com/neverempty/eu-tender-monitor)** - EU public tender notices from the official TED API
- **[domain-lookup](https://apify.com/neverempty/domain-lookup)** - WHOIS, DNS, MX, SPF, DMARC and SSL expiry for a list of domains
- **[website-contacts](https://apify.com/neverempty/website-contacts)** - emails, phones and social profiles from any website, no invented addresses

# Actor input Schema

## `names` (type: `array`):

One name per line. People or companies. You are charged per name screened, not per hit, so a clean list costs the same as a dirty one.

## `sources` (type: `array`):

ofac for the US Treasury SDN list, eu for the EU financial sanctions list, un for the UN Security Council consolidated list. Leave all three for the widest cover.

## `minScore` (type: `number`):

1.0 means only exact names and exact aliases. 0.85 also catches misspellings such as Sadam Husein. Below 0.8 the false alarms start to outnumber the real hits.

## `maxMatches` (type: `integer`):

How many matches to return for each name screened, best score first.

## `maxRetries` (type: `integer`):

How many times to retry when a list server times out or returns a server error. Errors that will not change on a retry are not retried.

## Actor input object example

```json
{
  "names": [
    "Saddam Hussein Al-Tikriti",
    "Bank Melli Iran",
    "Toyota Motor Corporation"
  ],
  "sources": [
    "ofac",
    "eu",
    "un"
  ],
  "minScore": 0.85,
  "maxMatches": 10,
  "maxRetries": 3
}
```

# Actor output Schema

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

All screening results from this run.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/sanctions-screening-official").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/sanctions-screening-official").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 '{}' |
apify call neverempty/sanctions-screening-official --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/sanctions-screening-official"
        }
    }
}

```

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/BfyFEC7CcZQ3ZENyF/builds/wpfZKfhVLUbjA3wgy/openapi.json
