# Sanctions Screening $0.49/1K — OFAC data, SDN list, EU + UN (`snow_leo_data/ofac-sdn-eu-un-sanctions-list-screening`) Actor

$0.49/1K | OFAC SDN, EU and UN sanctions screening in one feed: 26,633 entries, not the 19,388 US-only ones. Cross-list flags show who is listed by two or more. SDN list data, denied party list, sanctioned entities, watchlist screening, compliance screening data, sanctions data for AML screening.

- **URL**: https://apify.com/snow\_leo\_data/ofac-sdn-eu-un-sanctions-list-screening.md
- **Developed by:** [Snow Leo Data](https://apify.com/snow_leo_data) (community)
- **Categories:** Developer tools, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 sanctioned entities

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

## Global Sanctions Screening — OFAC, EU and UN in one feed

**$0.49 per 1,000 records.** Every other sanctions Actor on this store scrapes
the **US list only**. This one joins three authorities and tells you, for every
name, which of them list it.

|  |  |
|---|---|
| **26,633 records** | against the 19,388 you get from OFAC alone — **37% more** |
| **US Treasury OFAC SDN** | 19,388 entries |
| **European Union consolidated list** | 6,234 entries |
| **UN Security Council consolidated list** | 1,011 entries |
| **4,370 sanctioned by two or more authorities** | cross-matched by name and alias |
| **1,693 sanctioned by all three** | the highest-exposure set |

All three sources are official government publications, fetched live, with **no
API key, no registration and no proxy**.

### The question this answers

"Is this person on the OFAC list?" is the wrong question for anyone doing
business outside the United States. The right one is **"is this person
sanctioned by anybody?"** — and answering it means reading three XML files with
three different schemas, in three different conventions, and reconciling the
names.

That reconciliation is the work. This Actor does it on every run, and what comes
out is AML screening data you can hold against your own book of names.

#### How names are matched across lists

Sanctions bodies do not agree on spelling. The same person is `PETROV, Ivan` in
one list, `Iván Petrov` in another and `Dr. Ivan PETROV` in a third. Matching
normalises before comparing:

- case folded and diacritics stripped, so `Iván` equals `Ivan`
- punctuation removed, so `AL-QAIDA` equals `Al Qaida`
- honorifics dropped — `mr`, `dr`, `sheikh`, `mullah`, `general` and others
- single letters discarded, so an initial never merges two different people
- remaining words sorted, so word order stops mattering

Aliases go through the same treatment, so a match on any published spelling
counts.

#### What the matching deliberately is not

It is **name-based, not identity resolution**. Two different people with the
same common name will be linked, and a person listed under genuinely different
names will not be. Every record carries `name_normalized` so you can see exactly
what was compared, and `birth_dates` and `id_numbers` are there precisely so a
human can separate a real match from a coincidence. This README would rather
state that than let you assume a certainty that does not exist.

### Screening a list of names

Paste what you need to check into **Screen these names**. Matching uses the
normalisation above, so you do not have to guess the spelling the authority
chose:

```json
{
  "searchNames": ["PETROV, Ivan", "Acme Trading LLC"],
  "maxItems": 0
}
```

A hit comes back with the authority, the programme, the aliases, and — the part
you cannot get elsewhere — whether the other two authorities list the same name.

### Fields

21 per record. The ones that do the work:

| Field | What it is |
|---|---|
| `name`, `aliases`, `alias_count` | the primary spelling and every alternative published |
| `entity_type` | `Individual`, `Entity`, `Vessel`, `Aircraft` — as each authority spells it |
| `source_list`, `source_name`, `source_uid` | which authority this record came from, and its own identifier |
| `also_on`, `list_count`, `on_all_three` | **the cross-match** — which other authorities list this name |
| `countries` | citizenship or country, where published |
| `birth_dates`, `id_numbers` | the identifiers that separate a real match from a common name |
| `programs` | the sanctions programme or regulation |
| `listed_on`, `remarks` | when it was listed and the authority's own note |
| `name_normalized` | the key used for matching, so the comparison is inspectable |
| `change_type` | in incremental runs: NEW, UPDATED or UNCHANGED |

Empty means that authority did not publish it. The UN list, for example,
carries listing dates that OFAC does not.

#### Why the same person appears more than once

Deliberately. Each authority's record is its own row, because each carries
different programmes, identifiers and remarks. `also_on` links them without
throwing any of that away. If you want one row per person, filter to a single
list and use `also_on` as a flag.

### What each authority publishes

The three lists are not interchangeable. EU sanctions data cites the regulation
that imposed the measure, and UN sanctions data carries a listing date on every
record — neither of which OFAC publishes. That difference is the reason to read
all three rather than one.

#### US Treasury OFAC SDN

The largest of the three at 19,388 entries, and the one with the deepest
programme detail: every record names the sanctions programme it falls under —
`SDGT`, `UKRAINE-EO13662`, `IRAN`, `CUBA` and dozens more. OFAC also lists
vessels and aircraft as separate entity types, which the other two mostly do
not. Dates of birth and passport numbers appear on many individual records.

#### European Union consolidated list

6,234 entries, and the richest on identity documents. Names are published as
repeated alias blocks rather than a single primary name, so the same person may
carry a dozen transliterations. Each entry cites the EU regulation that imposed
the measure, which is what a European compliance team will be asked to
reference.

#### UN Security Council consolidated list

The smallest at 1,011 entries, split into individuals and entities, and the only
one of the three that publishes a **listing date** on every record. Because UN
designations flow into both the US and EU regimes, a UN entry is very often
mirrored in the other two — which is why 1,693 records in this feed carry
`on_all_three`.

### A worked example

Screening a single name shows what the join adds:

```json
{ "searchNames": ["Ayman al-Zawahiri"] }
```

returns the OFAC record, the EU record and the UN record as three rows. Each
carries its own programme and remarks, and each has `also_on` naming the other
two and `on_all_three` set. A scraper that reads OFAC alone returns one row and
cannot tell you the rest exists.

Narrowing instead to what several governments agree on:

```json
{ "allThreeOnly": true, "maxItems": 0 }
```

returns the 1,693 entities designated by the US, the EU and the UN at once.

### Running it as a monitor

#### Getting only what changed

Turn on **Only records not seen before** and schedule it. Delivered entries are
remembered in a named key-value store that survives runs, and each row carries
`change_type`. An entry counts as updated when its name, countries, programmes
or the set of authorities listing it changes — not when an authority rewrites a
remark.

With no limit set, the first run delivers the whole list and later runs deliver
only additions and changes. With a limit set, each run walks forward through
what you have not yet received, which is useful for backfilling in small
batches.

#### Sanctions change on a schedule you do not control

OFAC updates the SDN list without warning, sometimes several times a week. A
daily run with incremental mode on costs a few cents and tells you exactly what
moved.

#### Where the output goes

JSON, CSV and Excel from the run page, or the API at
`https://api.apify.com/v2/datasets/<id>/items`. Apify's integrations push it to
Slack, Google Sheets, Zapier, Make or your own webhook — see
[Apify integrations](https://docs.apify.com/platform/integrations).

### Questions people ask

#### Is this legal advice or a compliance product?

Neither. It is a data feed of public government publications. Whether a match
requires action is a decision for your compliance process, and the identifiers
in each record exist to support that decision rather than replace it.

#### Which lists are missing?

The UK HMT list, Switzerland's SECO, Canada's consolidated list and several
others are not included. Adding them means parsing more schemas, and this README
names what is here rather than implying global coverage. The three included are
the ones with the widest legal reach.

#### How fresh is the data?

Every run downloads the current files: 27 MB from OFAC, 25 MB from the EU and
2 MB from the UN. Nothing is cached between runs, so a record that was added an
hour ago is in your output.

#### Why does a run take half a minute before returning anything?

Because the three files are downloaded and parsed in full before matching can
start. The cross-list index cannot be built from a partial read — that is the
cost of answering "sanctioned by anybody" rather than "on this one list".

#### Can I screen thousands of names at once?

Yes. **Screen these names** takes a list, and matching runs in memory against
the normalised index, so a thousand names cost no more than one. Only the
matching records are delivered and billed.

#### The run finished green and the dataset is empty

Every run that delivers nothing says why, both in the log and in the `REPORT`
record of the key-value store: a short code in `empty_reason` and a sentence in
`empty_explained`. The codes are:

- `cross-check-impossible` — you asked for entities listed by two or three
  authorities, but the run downloads only one. Cross-list counting is done over
  the lists that were actually downloaded, so every record would come out with
  `list_count` 1 and the filter would drop all of them. This is caught before
  anything is downloaded, so the run costs nothing; add the missing authorities
  to **Which authorities**, or leave it empty to take all three.
- `no-name-match` — none of the names you screened is on the lists you chose.
  For screening, that is the answer, not a fault.
- `cross-list-no-match` — the lists were downloaded, but nobody in them is
  listed by that many authorities at once.
- `filtered-out` — the country, programme, entity-type or keyword filters
  removed every record. The sentence names which filter removed how many.
- `nothing-changed` — incremental mode, and nothing has changed since the
  previous run. Nothing was written and nothing was charged.
- `source-returned-nothing` — no authority answered this run.
- `charge-limit-reached` — the run's spending limit was already used up.
- `nothing-collected` — records were collected, no filter removed them, and
  still nothing reached the dataset. This one should never appear; if it does,
  it is a bug and the run id is worth reporting.

#### What if one authority's site is down?

The run continues with the others and names the failure in the run report. A
partial answer that says which part is missing beats a silent one that looks
complete.

#### Does it need proxies or an API key?

No. All three sources are public government endpoints served without
authentication. That is also why this Actor costs what it does.

#### Can I get one row per person instead of three?

Not directly, and on purpose. Each authority publishes different programmes,
identifiers and remarks for the same person, and collapsing them would throw
that away. Filter to one list and read `also_on` if you want a single row with a
cross-reference flag.

#### How do I avoid false positives on common names?

Use `birth_dates` and `id_numbers`. Turn on **Only records with a date of
birth** or **Only records with a document number** when you need a second
identifier before acting. Those filters run before billing, so narrowing costs
nothing.

#### Why are there more records than sanctioned people?

Because a person designated by all three authorities appears three times, once
per authority. 26,633 records represent fewer distinct people — the
`name_normalized` field is what you group by to count them.

### Honest limits

- **Name matching is not identity resolution.** See above — common names will
  over-match, and that is visible in `name_normalized`.
- **Three authorities, not all of them.** UK, Swiss, Canadian and other national
  lists are not here.
- **No fuzzy matching.** A misspelling that differs by a letter will not match.
  Adding edit-distance matching would raise recall and also raise false
  positives, and that trade-off belongs to your compliance policy, not to a
  scraper's default.

### Reproducing the numbers

```
python3 tests/run_all.py --live
```

The live suite loads all three authorities, checks the per-source counts, and
asserts that cross-list matches actually exist in the real data. Every figure in
this README comes from that run.

# Actor input Schema

## `searchNames` (type: `array`):

Paste the names you need to check. Matching ignores case, accents, punctuation, titles and word order — `PETROV, Ivan` finds `Iván Petrov`. Aliases are checked too.

## `lists` (type: `array`):

Leave empty for all three — that is the point of this Actor.

## `multiListOnly` (type: `boolean`):

4,370 of the 26,633 entries appear on at least two lists. An entity sanctioned by several governments is a different risk from one flagged by a single agency.

## `allThreeOnly` (type: `boolean`):

1,693 entries are on the US, EU and UN lists at once.

## `minLists` (type: `integer`):

1, 2 or 3.

## `entityTypes` (type: `array`):

As each list spells it: `Individual`, `Entity`, `Vessel`, `Aircraft`, `person`, `enterprise`.

## `countries` (type: `array`):

Matched against citizenship and country fields, e.g. `Iran`, `Russia`.

## `programs` (type: `array`):

Matched against the programme or regulation name, e.g. `UKRAINE-EO13662`, `SDGT`, `Al-Qaida`.

## `keywords` (type: `array`):

Free-text match across name, aliases, programmes and remarks.

## `excludeKeywords` (type: `array`):

Drops a record if any of these appears in its name, aliases, programmes or remarks.

## `withBirthDateOnly` (type: `boolean`):

Useful when you need a second identifier to avoid false positives on common names.

## `withIdOnly` (type: `boolean`):

Passport, tax or registration numbers, where the list publishes them.

## `includeAliases` (type: `boolean`):

Alternative spellings as published. The largest field on many records.

## `includeRemarks` (type: `boolean`):

The free-text note each authority attaches.

## `compactOutput` (type: `boolean`):

9 core fields only — for screening pipelines and AI agents.

## `excludeEmptyFields` (type: `boolean`):

Leave out what the authority did not publish.

## `onlyNew` (type: `boolean`):

For schedules. Delivered entries are remembered in a named store that survives runs, so a daily run returns — and charges for — only additions and changes.

## `emitUnchanged` (type: `boolean`):

Off by default: an unchanged record is something you already paid for.

## `maxItems` (type: `integer`):

0 = no limit. The form starts at 500 so a first run is quick and cheap.

## Actor input object example

```json
{
  "searchNames": [],
  "lists": [],
  "multiListOnly": false,
  "allThreeOnly": false,
  "minLists": 0,
  "entityTypes": [],
  "countries": [],
  "programs": [],
  "keywords": [],
  "excludeKeywords": [],
  "withBirthDateOnly": false,
  "withIdOnly": false,
  "includeAliases": true,
  "includeRemarks": true,
  "compactOutput": false,
  "excludeEmptyFields": false,
  "onlyNew": false,
  "emitUnchanged": false,
  "maxItems": 500
}
```

# Actor output Schema

## `entities` (type: `string`):

Every record collected in this run.

## `report` (type: `string`):

Per-authority counts and filter totals.

# 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 = {
    "maxItems": 500
};

// Run the Actor and wait for it to finish
const run = await client.actor("snow_leo_data/ofac-sdn-eu-un-sanctions-list-screening").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 = { "maxItems": 500 }

# Run the Actor and wait for it to finish
run = client.actor("snow_leo_data/ofac-sdn-eu-un-sanctions-list-screening").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 '{
  "maxItems": 500
}' |
apify call snow_leo_data/ofac-sdn-eu-un-sanctions-list-screening --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,snow_leo_data/ofac-sdn-eu-un-sanctions-list-screening"
        }
    }
}
```

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/OBie4LRXkPhoN7wQY/builds/9jJhnolZVz97u3cx1/openapi.json
