# screening

Source: https://docs.korala.ai/guides/screening

---

# Sanctions and PEP screening

Korala screens a person or a company against the official sanctions lists
you choose, and a person against Korala's list of politically exposed
persons (PEPs) and their family members, and tells you which listed
entries may be them, how likely, and why. Screening runs on its own through the API, or as a step of a
verification session, where the person's identity document supplies the
name, date of birth, nationality and document number.

News about a subject is a separate search: see
[Adverse media](https://docs.korala.ai/guides/adverse-media).

Korala gives no default opinion on which lists apply to you. You name the
lists your obligations require, and your risk policy decides what a
possible match or a match does.

## The lists

| Key | List | Publisher |
| --- | --- | --- |
| `eu_fsf` | EU Consolidated Financial Sanctions List | European Commission |
| `ofac_sdn` | Specially Designated Nationals and Blocked Persons List | US Treasury, OFAC |
| `ofac_consolidated` | Consolidated Sanctions List (non-SDN) | US Treasury, OFAC |
| `uk_sanctions` | UK Sanctions List | UK FCDO |
| `idb_sanctions` | Sanctioned Firms and Individuals | Inter-American Development Bank |
| `korala_pep` | Korala PEP list (see [PEP screening](#pep-screening)) | Korala, from Wikidata and official rosters |

`GET /api/v1/screening/sources` returns each list with its active version,
the publication date the file states, when Korala last confirmed it, and
whether it is stale or needs an operator's attention. A result that used a
list must credit it with the `attribution` text the endpoint returns.

## Screen a subject

```bash
POST /api/v1/screening/runs
Idempotency-Key: 6f1d7c1e-onboarding-4411
```

```json
{
  "subject": {
    "name": "Testa Fictionaria",
    "datesOfBirth": ["1984-03-17"],
    "nationalities": ["IT"],
    "identifiers": [{ "kind": "passport", "value": "YA1234567", "country": "IT" }]
  },
  "sources": ["eu_fsf", "uk_sanctions", "ofac_sdn"],
  "reviewScore": 70,
  "matchScore": 90
}
```

| Field | Notes |
| --- | --- |
| `subject.name` | Required, up to 300 characters, in any script. |
| `subject.aliases` | Up to five other names: the name in its own script, a former name. |
| `subject.entityType` | `person` or `entity`. Unset, both can match. |
| `subject.country` | The country of the document the name comes from; it decides how a name in another script is read. |
| `subject.datesOfBirth` | Up to three, as `YYYY-MM-DD`, `YYYY-MM` or `YYYY`. |
| `subject.nationalities` | Up to five ISO 3166-1 codes; for a company, its country of registration. |
| `subject.identifiers` | Up to ten: `passport`, `national_id`, `registration_number` or `wallet`. |
| `sources` | Required. The lists to screen against, each once. |
| `reviewScore`, `matchScore` | Whole numbers from 1 to 100, defaults 70 and 90; `reviewScore` must be below `matchScore` (equal scores are refused, `400`; so is a `reviewScore` of 90 or more without a `matchScore`). |
| `riskSubjectId` | Optional: a risk subject in the same environment to record the outcome on. |

`Idempotency-Key` is required. Retry with the same key and the same body to
get the same run; a different body with a used key returns `409`. A test
API key screens in the test environment and sees only test runs.

The response is the run:

```json
{
  "id": "0b8c1f4e-2d7a-4c35-9a51-3f0e6d2b9c10",
  "status": "possible_match",
  "incomplete": false,
  "incompleteReasons": [],
  "errorCode": null,
  "trigger": "api",
  "sources": [
    {
      "key": "eu_fsf",
      "versionId": "5e2f0a91-7c43-4b8e-a1d6-0c9b3e7f2a58",
      "publishedAt": "2026-10-08T00:00:00.000Z",
      "missing": false,
      "stale": false,
      "staleReasons": [],
      "attention": null
    }
  ],
  "policy": { "reviewScore": 70, "matchScore": 90, "scorerVersion": "screening-score-v2" },
  "riskSource": "korala:screening:eu_fsf+ofac_sdn+uk_sanctions:r70:m90",
  "candidateCount": 12,
  "topScore": 78,
  "matches": [
    {
      "id": "9a7d3c2b-1e4f-4a6b-8c5d-2f1e0b9a8c7d",
      "source": "eu_fsf",
      "sourceEntryId": "123456",
      "listedName": "Testa Maria Fictionaria",
      "matchedName": "Testa Maria Fictionaria",
      "score": 78,
      "factors": [
        { "code": "all_subject_tokens", "points": 72, "detail": "Testa Maria Fictionaria" },
        { "code": "dob_same_year", "points": 6, "detail": "1984" }
      ],
      "decision": "pending"
    }
  ]
}
```

`candidateCount` is how many listed entries were scored, and `topScore` the
highest of their scores (`null` when none was). When more than 200 entries
share a word with the name, those whose kind, dates of birth or
nationalities keep them below `reviewScore` are set aside without a score:
neither number counts them.

| `status` | Meaning |
| --- | --- |
| `match` | An entry scored at or above `matchScore`, or a reviewer confirmed one. |
| `possible_match` | An entry scored at or above `reviewScore`. A person should look. |
| `clear` | Every requested list was searched completely and nothing reached `reviewScore`, or every hit was marked a false positive. |
| `incomplete` | Nothing reached `reviewScore`, but the screening was not complete. Never treat it as clear. |
| `error` | The subject was not screened: no name with anything to search by (`missing_name`), none of the lists is loaded (`no_lists`), or the service is not configured or failed. Never treat it as clear. |

`incomplete: true` says the screening was not complete, and
`incompleteReasons` says why:

| Reason | Meaning |
| --- | --- |
| `source_missing` | A requested list was not searched: unknown, switched off or never loaded. |
| `source_stale` | A list is stale (see its `staleReasons`). |
| `source_attention` | A list needs an operator's attention. |
| `source_dropped_entries` | The list version dropped entries its parser could not read (`droppedEntries`). |
| `source_reindex_pending` | The list's index had not yet been rebuilt for names one letter off (`reindexPending`): those were not searched on it. |
| `candidates_capped` | More sanctions list entries that could reach review share a word with the name than are scored (200). An entry whose kind, dates of birth or nationalities keep it below review is not scored and does not count. |
| `pep_candidates_capped` | The same for the PEP list, which has a pool of its own (1,000), so PEP entries never take the place of sanctions entries. |
| `name_terms_capped` | The subject had more names (six, counting spellings without spaces), more identifiers (ten) or more search terms than are searched, or an identifier too long to search by. |
| `name_without_tokens` | No name had a word to search by, only initials or similar. |

A possible match or a match in an incomplete run keeps its status.

A name may have at most 12 words; a longer one is refused. A name in
Chinese, Japanese or Korean script written with spaces between its
characters is also screened as written without them, and Chinese
characters with no country to read them by are read as mainland China,
Taiwan and Hong Kong romanise them.

A name alone reaches `possible_match` at most. A match needs the same day of
birth, a matching wallet address, or a document number that matches with the
same country beside a listed name that agrees with the subject's
exactly: the same name, or every word of one name in the other, with no
word one letter off (an alias the list marks as weak counts, and the
match names it).

## Decide a match

```bash
POST /api/v1/screening/matches/9a7d3c2b-1e4f-4a6b-8c5d-2f1e0b9a8c7d/decision
Idempotency-Key: review-9a7d3c2b-1
```

```json
{ "decision": "false_positive", "reason": "Listed person was born in 1951; passport says 1984." }
```

Decisions are made by an organization owner or admin signed in to the
dashboard; API keys and members are refused with `403`.
`decision` is `confirmed` or `false_positive`; the reason is required. The
run's status is derived again from all its matches, every decision is kept in
the run's audit trail with who made it, and a later decision can change an
earlier one.

`GET /api/v1/screening/runs/:id` reads one run; `GET /api/v1/screening/runs`
lists them newest first (`page`, `limit` up to 100, and filters `status`,
`sessionId`, `riskSubjectId`).

## In a verification session

Add a check step to a workflow that runs after the identity document:

```json
{
  "type": "check",
  "config": {
    "kind": "pep_sanctions",
    "provider": "korala_screening",
    "providerConfig": { "sources": ["eu_fsf", "uk_sanctions"], "reviewScore": 70, "matchScore": 90 }
  }
}
```

The step reads the person's identity document from the same session: the
name in Latin letters and as printed, date of birth, nationality and the
passport or national id number. A workflow is refused at save when
`sources` is missing, a score is out of bounds, or `reviewScore` is not
below `matchScore`; a step saved earlier with equal scores ends in
`error` with review recommended, never `clear`. The session check's
`providerRef` is `screening-run:<id>`, and its status is `match`,
`review_required` (a possible match, or an incomplete run) or `error`.

## Risk evidence

When the screened session has a risk subject (or an API run names one),
Korala records the fact `screening.sanctions` with value `clear`,
`possible_match` or `match` under the run's `riskSource`, such as
`korala:screening:eu_fsf+uk_sanctions:r70:m90`. A run that screened the
PEP list also records `screening.pep` under its own source, such as
`korala:screening:korala_pep:r70:m90:l12` (the last part is the
look-back), with value `none`, `possible_pep`, `pep`, `former_pep` or
`family_member`. A definite match (confirmed, or at or above `matchScore`)
gives its standing, `pep` before `family_member`; an open possible match on
a PEP or a family member gives `possible_pep`, which comes before
`former_pep`. A possible match on a former PEP gives `former_pep` at most. The
run's `riskSources` lists each fact with its source. A PEP is never
recorded as a sanctions hit, and a problem with one kind of list (stale,
capped) does not make the other kind's evidence unavailable. When the PEP
screening was incomplete, only `pep` and `possible_pep` are recorded; any
other value is unavailable, since a current PEP may be among the entries
not searched. A policy condition on a screening fact may name only the
values above: a misspelt one is refused when the policy is published. Accept each source in a
policy input for its fact, `screening.sanctions` or `screening.pep` (type
`string`), and decide in the
policy what each value does. Changing the lists or the scores of the step
changes the source, so the policy must be bound again. A run that could not
screen, or an incomplete run that found nothing, records the fact as
unavailable, which your policy treats as missing evidence.

## Webhooks

| Event | When |
| --- | --- |
| `screening.run.completed` | A run was recorded: run id, status, `incomplete`, error code, trigger, session and risk subject ids, match count. |
| `screening.match.found` | The run has matches at or above `reviewScore`: each match's id, entry id, list, `listKind`, score and, for a PEP match, `pepStanding`. |

Payloads never carry a name, a date of birth or an identifier; read the run
for the matches. Deliveries are at least once. Decisions do not send an
event; read the run after a decision.

## What a run keeps

A run keeps a keyed digest of what was screened, not the name, the date of
birth or the numbers: those stay in the session or in your own system.
Matches keep the listed entry and the factors about it.

## PEP screening

Add `korala_pep` to `sources` to screen for politically exposed persons,
on its own or beside sanctions lists. Each list has a `listKind`
(`sanctions` or `pep`), and each match says which kind it came from.

The PEP list is built by Korala from Wikidata and official rosters
(parliaments, governments, EU institutions): heads of state and government,
ministers, members of parliament, top judges, central bank boards,
ambassadors, heads of international organisations and of state-owned
enterprises, regional leaders, and the family members Wikidata links to
them. Close associates are not included: no free source records them.
Coverage differs by country; Korala publishes it per country rather than
claiming global coverage.

A PEP match carries `pep`:

- `standing`: `pep` (in office, or out of office for less than the
  look-back), `former_pep` (out of office for longer, with
  `lastHeldUntil`), `family_member` (with `relatedPeps`: the PEP's entry
  id, name, the relation, such as `spouse` or `child`, and the PEP's
  positions), or `none`: a relative whose related PEPs are no longer PEPs
  on the day. A `none` match is shown with its relation, and
  `screening.pep` records no family member for it.
- `flags`: `position_dates_unknown` when no position states when it was
  held. Such a person is a PEP until a reviewer finds otherwise.
- `positions`: each with its category, title, country, start and end
  dates, whether the source says it is current, and the roster that states
  it.

The standing is judged on the day of the screening from the dates the list
states. A family member is one while a PEP it is related to is a PEP by the
same look-back. Following the EU AML Regulation (Article 2(1)(35)),
spouses, children and parents count for every PEP (the Regulation also names
registered partners and children's spouses, which the list does not hold
yet); a sibling counts only for heads of state and government, ministers and
deputy ministers. `pepLookbackMonths` sets how long after leaving office a person
still counts as a PEP: at least 12, the minimum the EU AML Regulation
sets, and the default. It is accepted only with `korala_pep`.

```json
{
  "subject": { "name": "Erika Lindqvist-Moreau", "datesOfBirth": ["1972-03-14"], "nationalities": ["SE"] },
  "sources": ["eu_fsf", "ofac_sdn", "korala_pep"],
  "pepLookbackMonths": 18
}
```

A PEP match makes the run a `possible_match` or `match` like any other, and
`screening.pep` says what was found. A PEP calls for enhanced due
diligence, not a refusal, so a check step never blocks on one: a finding
on the PEP list alone makes the step `review_required` at most, whatever
its score, and only a sanctions match makes it a `match`. Decide in your
risk policy what each standing does.

## How well it finds people

Measured on 2026-10-10 with the default scores against the five lists:

- **False hits:** about 2% of ordinary customers reached `possible_match`,
  most of them without a date of birth to compare; none reached `match`.
- **Listed names:** every listed entry sampled (798) reached review on its
  own listed name; with the listed full date of birth, all reached `match`,
  under aliases, names in their original script and reordered names too.
- **Spelling variants** the transliteration rules do not fold are found
  about four times in five.
- **A name one letter off** (a letter changed, added or dropped, or two
  neighbouring letters swapped, in a word of five letters or more) is
  found at review when a date of birth or nationality agrees: for persons
  with a listed date of birth about nine times in ten (measured
  2026-10-12; before, one time in ten). Such a name alone never reaches
  review at the default scores, and it never reaches `match`, whatever
  match score you set: with a date of birth, a nationality or a document
  number it reaches review at most (a wallet address stays decisive). The
  match carries a `fuzzy_token` factor naming the listed spelling, and each
  list on the run gives the `termBuilderVersion` it was searched with (3
  and later find names one letter off). Two letters off is
  still missed, so check names that were typed by hand.

**PEPs**, measured on 2026-10-11 against the build of 2026-10-10 (78,119
entries), with a labelled set drawn from the list: every listed name, name
in another script and reordered name was found at review in the 20
countries with most entries and Korea; with spelling changes and typos
included, 92% to 99.7% per country (81% to 99% before names one letter
off were matched). Of the current heads of state and
government on the US World Leaders list, 91% are found by an entry from
another source.

## Not yet

Close associates of PEPs, adverse media and ongoing re-screening when a
list changes are not part of screening yet.
