# Sanctions Screening - OFAC, EU, UK, UN Bulk Lists (`s-r/sanctions-screening`) Actor

Screen a name against the four government sanctions bulk lists (OFAC SDN, EU, UK OFSI, UN). Returns matched rows with list, programme, reference id, DOB and country, plus a merged hit/no\_hit verdict.

- **URL**: https://apify.com/s-r/sanctions-screening.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Business, Lead generation, Other
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$25.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?

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

## Sanctions Screening - OFAC, EU, UK, UN Bulk Lists

Screen a counterparty name against the four government sanctions lists that compliance teams actually get asked about: OFAC SDN, the EU consolidated list, UK OFSI and the UN Security Council consolidated list. One run returns matched rows with the list source, programme, reference id, date of birth and country where the list records them, plus a single merged verdict per name. Built for marketplaces vetting new sellers and fintechs screening counterparties before payout, invoice finance or onboarding.

### What you get

- One dataset row per matched list entry, carrying `list_source` (`ofac_sdn`, `eu_fsf`, `uk_ofsi`, `un_sc`), `programme`, `reference_id`, `entity_type`, `entity_name`, `aliases`, `dob`, `country` and `addresses`.
- A merged verdict per screened name: `hit`, `possible_hit` or `no_hit`, with `lists_hit` naming which of the four lists carry the entry.
- Match diagnostics on every row: `match_type` (exact, normalised, fuzzy), `score` from 0 to 1, and the flags `dob_conflict`, `country_match` and `lei_match` so you can see why a row survived or got dropped.
- Batch screening: put one name per line in `names` and get rows for the whole list in one run.
- Local matching on a SQLite index built from the bulk files at run start, so the screen itself does not depend on a third-party search API being up.
- A `summary` record in the key-value store with per-name verdicts, row counts per list, and when the bulk files were last fetched.

### Why sanctions screening

Compliance budgets buy certainty, not dashboards. The finding that changes a decision is binary: counterparty Y is named on OFAC SDN under programme Z, so the account is blocked and the case is escalated. A risk index, a coverage score or a comparative metric does not trigger that action. What teams need is the actual list row, with the programme and the reference id their auditor will ask for, and a clear statement of which lists were searched.

The four lists we screen are the ones a regulator names when they ask "did you check?". OFAC SDN is the US list with secondary-sanctions reach. The EU consolidated list is what a Dutch or German payments firm needs. UK OFSI covers the UK regime after Brexit. The UN Security Council list is the floor almost every national regime builds on. Screening all four in one call is the difference between a real check and a checkbox.

### Input

| Field | Required | Description |
|---|---|---|
| `name` | yes | Person or legal entity name as the counterparty gave it. Prefilled with `Deripaska` so the form runs as-is. |
| `names` | no | One name per line, for batch screening of a seller list. |
| `country` | no | ISO code or country name. Demotes fuzzy matches from other countries; exact name hits survive. |
| `dob` | no | `YYYY-MM-DD` or any text carrying a year. A conflicting year removes the match. |
| `lei` | no | Legal Entity Identifier, checked against list reference identifiers. |
| `lists` | no | `all`, `ofac_sdn`, `eu_fsf`, `uk_ofsi` or `un_sc`. Defaults to all four. |
| `match_threshold` | no | Similarity floor for fuzzy matches, 0.7 to 1.0. Default 0.88. Exact and normalised matches always pass. |
| `refresh_lists` | no | Force a re-download of the bulk files even if the cache is under 24 hours old. |

### Output

```json
{
  "query_name": "Deripaska",
  "verdict": "hit",
  "lists_hit": ["eu_fsf", "ofac_sdn", "uk_ofsi"],
  "match_name": "Oleg VLADIMIROVICH DERIPASKA",
  "match_type": "normalised",
  "score": 0.96,
  "list_source": "ofac_sdn",
  "programme": "RUSSIA-EO14024",
  "reference_id": "16152",
  "entity_type": "individual",
  "entity_name": "Oleg Vladimirovich DERIPASKA",
  "aliases": ["Oleg Vladimirovich DERIPASKA"],
  "dob": "1968-01-02",
  "country": ["RU"],
  "addresses": ["Moscow, RU"],
  "dob_conflict": false,
  "country_match": true,
  "lei_match": null
}
```

Names with no match return a single row with `verdict: "no_hit"` and empty match fields, so a clean screen is visible in the dataset and not just an absence of rows.

### How matching works

Names are normalised (diacritics folded, punctuation and legal suffixes such as Ltd, LLC, GmbH and PJSC stripped) and matched in three tiers. `exact` means the raw strings agree. `normalised` means they agree after folding. `fuzzy` means a token-set overlap or character similarity above the threshold. A date of birth on the query that contradicts the listed year removes the row outright. A country on the query that contradicts every listed country drops a fuzzy candidate but keeps an exact name hit, because lists record nationality loosely and a missed hit costs more than a false one.

### Use cases

**Marketplace seller onboarding.** Before a new seller goes live, screen the legal name and the beneficial owner against all four lists. A hit on any one of them is a block and an escalation, and the dataset row gives compliance the programme and reference id to file. Batch mode takes the weekly seller queue in one run.

**Fintech counterparty checks before payout.** Invoice finance and payout flows need a check at the moment money moves. Run the payer name, the country and the date of birth as provided; the DOB filter keeps common names from producing noise on the exact-match path, and `verdict` is the field your decision rule keys on.

**Periodic rescreening.** Lists change. Rescreen your book of counterparties on a schedule and diff `reference_id` and `programme` against the last run to see who was newly designated or delisted.

**Audit evidence.** The dataset is a dated record of who you screened, against which lists, with what result. That is the artefact an auditor asks for when they ask how you satisfied the sanctions screening requirement.

### How it compares

OpenSanctions aggregates these lists and hundreds more, and sells access under a commercial data licence with per-customer terms. Sanctions.io and the KYB screening actors on this platform sell a similar check as a subscription or per-lookup product. What differs is the shape: this actor returns the raw government list rows with their own reference ids and programmes, priced per screening event, with no seat licence and no monthly minimum. If you need politically exposed persons, adverse media or a curated deduplicated entity graph, a commercial dataset is the right tool and this actor is not a substitute for it.

### Pricing

$0.025 per screening event: one event per name screened, whether the result is a hit or a clean no\_hit. Batch runs charge one event per name in the batch. All pricing is pay-per-event, you only pay for results you receive. No actor-start fee, no per-compute-unit charges.

### Limits and gotchas

- **Politically exposed persons are out of scope.** PEP coverage requires a commercial data licence we do not bundle; the underlying aggregators publish under non-commercial terms with serious contractual penalties for resale. This actor screens sanctions lists only.
- The first run in a fresh environment downloads the bulk files (tens of megabytes) and builds the SQLite index, which adds time before the first result row. Later runs in the same environment reuse a cache younger than 24 hours; `refresh_lists` forces a rebuild.
- Fuzzy matching is name-based. Misspelled transliterations of Cyrillic or Arabic names can score below the threshold: lower `match_threshold` and review `possible_hit` rows by hand.
- Date of birth on many list rows is partial (year only, or absent). A DOB filter only removes a row when both sides carry a year and the years disagree.
- Vessels and companies appear alongside individuals; check `entity_type` before applying a person-only workflow.
- The four lists are the scope. National regimes beyond the EU and UK (Swiss SECO, Canadian consolidated, Australian DFAT) are not searched.
- A `no_hit` is a statement about these four lists on the day of the run. It is not a legal opinion and not a substitute for your own compliance policy.

### FAQ

**Do I need an API key for the government lists?**
No. The four bulk files are published for direct download. The actor fetches them itself and matches locally.

**Can I screen a whole seller list in one run?**
Yes. Put one name per line in `names`. Each name is screened separately and charged as one screening event.

**What does `possible_hit` mean?**
A name that matched above the fuzzy threshold but not exactly. Review those rows before acting; they are the ones a human should look at.

**Does a date of birth help with common names?**
Yes. Supplying `dob` removes candidates whose listed birth year disagrees, which is the main source of false positives on names like Smith or Ivanov.

**Why is PEP screening not included?**
Politically exposed persons data comes from commercial aggregators whose licences forbid resale. Bundling it would need a reseller arrangement we do not hold, so it is explicitly out of scope.

### Related Actors

- https://apify.com/s-r/market-quotes
- https://apify.com/s-r/cost-of-living

# Actor input Schema

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

Person or legal entity name exactly as your counterparty gave it. One name per run is enough for a quick check.

## `names` (type: `string`):

Optional. One name per line for batch screening of sellers or counterparties.

## `country` (type: `string`):

Optional ISO country code or country name. Used to demote fuzzy matches from other countries; exact name hits survive.

## `dob` (type: `string`):

Optional date of birth, YYYY-MM-DD or any text carrying a year. A conflicting year removes a match.

## `lei` (type: `string`):

Optional Legal Entity Identifier, checked against the list reference identifiers.

## `lists` (type: `string`):

Which government lists to screen against.

## `match_threshold` (type: `number`):

Similarity floor for fuzzy name matches, 0.7 to 1.0. Exact and normalised matches always pass.

## `refresh_lists` (type: `boolean`):

Re-download the bulk files even if the cached copies are under 24 hours old.

## Actor input object example

```json
{
  "name": "Deripaska",
  "names": "Acme Trading Ltd\nJane Doe",
  "country": "RU",
  "dob": "1968-01-02",
  "lei": "5493001KJTIIGC8Y1R12",
  "lists": "all",
  "match_threshold": 0.88,
  "refresh_lists": false
}
```

# Actor output Schema

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

One row per list match, or one no\_hit row per screened name.

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

Per-name verdicts, list row counts and cache freshness.

## `errors` (type: `string`):

Failures with a code and a redacted message.

# 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 = {
    "name": "Deripaska",
    "names": `Deripaska
Al-Tikriti`,
    "country": "RU",
    "dob": "1968-01-02",
    "lei": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/sanctions-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 = {
    "name": "Deripaska",
    "names": """Deripaska
Al-Tikriti""",
    "country": "RU",
    "dob": "1968-01-02",
    "lei": "",
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/sanctions-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 '{
  "name": "Deripaska",
  "names": "Deripaska\\nAl-Tikriti",
  "country": "RU",
  "dob": "1968-01-02",
  "lei": ""
}' |
apify call s-r/sanctions-screening --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/sanctions-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/z2gdQECKEcwSWBkPm/builds/Tb5EbUjelWknoPpvX/openapi.json
