> For the complete documentation index, see [llms.txt](https://developer.fraudcheck.co.za/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.fraudcheck.co.za/aml-and-watchlist/comprehensive-watchlist-screening.md).

# Comprehensive Watchlist Screening

Comprehensive Watchlist Screening checks an individual against international sanctions lists, Politically Exposed Persons (PEP) databases, and adverse media sources in a single call. Use it to satisfy AML and KYC obligations during onboarding, enhanced due diligence, and ongoing customer reviews.

## When to use this

Use this service to:

* Run a full AML screening (sanctions + PEP + adverse media) on a single individual
* Identify Politically Exposed Persons during onboarding
* Detect adverse media references that warrant enhanced due diligence
* Produce an auditable risk rating and screening outcome for compliance records

> For lighter or list-specific checks (e.g. FIC TFS only, PEPs only, crime watchlists), see the [Watchlist Screening](/aml-and-watchlist/watchlist-screening.md) service.

## How it works

Submit the individual's identity details - name, ID number, date of birth, and gender - in a single POST request. The service searches international sanctions lists, PEP databases, and adverse media sources, and returns a synchronous response containing the screening outcome, a risk level, match counts by category, and the matched records themselves. Screening typically takes 30–60 seconds.

| Property              | Value           |
| --------------------- | --------------- |
| Response type         | `Synchronous`   |
| Typical response time | 30–60s          |
| Result retrieval      | Returned inline |
| Consent required      | `Yes`           |
| Region                | International   |

## Authentication

```http
Authorization: Bearer {your_access_token}
```

## Endpoint

```http
POST /comprehensive-watchlist/search
```

## Request

### Headers

| Header          | Required | Value              |
| --------------- | -------- | ------------------ |
| `Authorization` | Yes      | `Bearer {token}`   |
| `Content-Type`  | Yes      | `application/json` |

### Body parameters

| Field                          | Type    | Required | Description                                   |
| ------------------------------ | ------- | -------- | --------------------------------------------- |
| `IdentityNo`                   | string  | Yes      | 13-digit South African ID number              |
| `FirstName`                    | string  | Yes      | Individual's first name                       |
| `LastName`                     | string  | Yes      | Individual's surname                          |
| `BirthDate`                    | string  | Yes      | Date of birth in `YYYY-MM-DD` format          |
| `Sex`                          | string  | Yes      | `M`, `F`, or `U` (unknown)                    |
| `ConsentObtainedByDataSubject` | boolean | Yes      | Must be `true`. Confirms POPIA consent.       |
| `SecondName`                   | string  | No       | Middle name                                   |
| `PrescribedPurpose`            | string  | No       | Business purpose (e.g. `AML_KYC_CHECK`)       |
| `YourReference`                | string  | No       | Your internal reference, echoed in audit logs |

### Example request

```json
{
  "IdentityNo": "8601015800086",
  "FirstName": "John",
  "LastName": "Smith",
  "SecondName": "Samuel",
  "BirthDate": "1986-01-01",
  "Sex": "M",
  "ConsentObtainedByDataSubject": true,
  "PrescribedPurpose": "AML_KYC_CHECK"
}
```

## Response

### Top-level fields

| Field             | Type    | Description                            |
| ----------------- | ------- | -------------------------------------- |
| `transaction_id`  | string  | Unique identifier for this screening   |
| `product`         | string  | Service identifier (`watchlist`)       |
| `identifier`      | string  | ID number screened                     |
| `first_name`      | string  | First name submitted                   |
| `surname`         | string  | Surname submitted                      |
| `success`         | boolean | Whether the request was processed      |
| `response_status` | string  | `SUCCESS`, `FAILED`, or `ERROR`        |
| `processing_time` | string  | Time taken in seconds                  |
| `screening_data`  | object  | Detailed screening results (see below) |

### `screening_data` fields

| Field                   | Type    | Description                                            |
| ----------------------- | ------- | ------------------------------------------------------ |
| `screening_result`      | string  | `ACCEPT`, `REVIEW`, or `REJECT`                        |
| `risk_level`            | string  | `LOW`, `MEDIUM`, or `HIGH`                             |
| `total_matches`         | integer | Total number of matches across all categories          |
| `has_matches`           | boolean | Whether any matches were found                         |
| `is_pep`                | boolean | Whether the individual is a Politically Exposed Person |
| `is_sanctioned`         | boolean | Whether the individual appears on a sanctions list     |
| `has_adverse_media`     | boolean | Whether adverse media references exist                 |
| `pep_matches`           | integer | Number of PEP matches                                  |
| `sanction_matches`      | integer | Number of sanctions list matches                       |
| `adverse_media_matches` | integer | Number of adverse media matches                        |
| `watchlist_matches`     | integer | Number of generic watchlist matches                    |
| `matches`               | array   | Matched records (see below)                            |

### Match record fields

| Field          | Type   | Description                                         |
| -------------- | ------ | --------------------------------------------------- |
| `match_id`     | string | Unique identifier for this match                    |
| `match_type`   | string | `PEP`, `SANCTIONS`, `ADVERSE_MEDIA`, or `WATCHLIST` |
| `list_name`    | string | Name of the source list or database                 |
| `match_score`  | number | Match confidence (0.0–1.0)                          |
| `jurisdiction` | string | Jurisdiction associated with the match              |
| `description`  | string | Description of the match                            |
| `match_date`   | string | Date the match was first recorded                   |
| `last_updated` | string | Date the source record was last updated             |

### Risk levels

| Level    | Meaning                                            | Recommended action                      |
| -------- | -------------------------------------------------- | --------------------------------------- |
| `LOW`    | No matches against any watchlist                   | Proceed with normal flow                |
| `MEDIUM` | Minor matches or historical references only        | Review context and verify identity      |
| `HIGH`   | Current PEP, active sanctions, or multiple matches | Escalate for manual review or rejection |

### Example response - no matches

```json
{
  "transaction_id": "d41d8cd98f00b204e9800998ecf8427e",
  "product": "watchlist",
  "identifier": "8601015800086",
  "first_name": "John",
  "surname": "Smith",
  "success": true,
  "response_status": "SUCCESS",
  "processing_time": "2.8",
  "screening_data": {
    "screening_result": "ACCEPT",
    "risk_level": "LOW",
    "total_matches": 0,
    "has_matches": false,
    "is_pep": false,
    "is_sanctioned": false,
    "has_adverse_media": false,
    "matches": []
  }
}
```

### Example response - PEP and adverse media match

```json
{
  "transaction_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "product": "watchlist",
  "identifier": "8601015800086",
  "first_name": "John",
  "surname": "Smith",
  "success": true,
  "response_status": "SUCCESS",
  "processing_time": "45.2",
  "screening_data": {
    "screening_result": "REVIEW",
    "risk_level": "HIGH",
    "total_matches": 2,
    "has_matches": true,
    "is_pep": true,
    "is_sanctioned": false,
    "has_adverse_media": true,
    "pep_matches": 1,
    "adverse_media_matches": 1,
    "matches": [
      {
        "match_id": "WATCHLIST_12345",
        "match_type": "PEP",
        "list_name": "International PEP Database",
        "match_score": 0.98,
        "jurisdiction": "Multiple",
        "description": "Politically Exposed Person - Government Official",
        "match_date": "2024-01-15",
        "last_updated": "2024-01-15"
      },
      {
        "match_id": "WATCHLIST_12346",
        "match_type": "ADVERSE_MEDIA",
        "list_name": "News/Media Database",
        "match_score": 0.85,
        "jurisdiction": "International",
        "description": "Reference in international media",
        "match_date": "2024-01-14",
        "last_updated": "2024-01-14"
      }
    ]
  }
}
```

## Errors

| HTTP | Code                  | Meaning                                                            | Action                         |
| ---- | --------------------- | ------------------------------------------------------------------ | ------------------------------ |
| 401  | `unauthorized`        | Token missing or expired                                           | Refresh your access token      |
| 403  | `forbidden`           | Account lacks access to this service                               | Contact your account manager   |
| 422  | `invalid_input`       | Required field missing, invalid date format, or consent not `true` | See `details.field`            |
| 503  | `service_unavailable` | Service temporarily unavailable                                    | Retry with exponential backoff |
| 500  | `server_error`        | Unexpected error                                                   | Contact support                |

### Common validation pitfalls

* **`BirthDate` must be `YYYY-MM-DD`.** Other formats (e.g. `04/11/1999`) will fail validation.
* **`Sex` accepts only `M`, `F`, or `U`.**

## Code examples

{% tabs %}
{% tab title="cURL" %}

```bash
BASE_URL="https://consumer-service-api.fraudcheckonline.co.za/consumer-service"

curl -X POST "$BASE_URL/comprehensive-watchlist/search" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "IdentityNo": "8601015800086",
    "FirstName": "John",
    "LastName": "Smith",
    "BirthDate": "1986-01-01",
    "Sex": "M",
    "ConsentObtainedByDataSubject": true,
    "PrescribedPurpose": "AML_KYC_CHECK"
  }'
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

BASE_URL = "https://consumer-service-api.fraudcheckonline.co.za/consumer-service"

response = requests.post(
    f"{BASE_URL}/comprehensive-watchlist/search",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={
        "IdentityNo": "8601015800086",
        "FirstName": "John",
        "LastName": "Smith",
        "BirthDate": "1986-01-01",
        "Sex": "M",
        "ConsentObtainedByDataSubject": true,
        "PrescribedPurpose": "AML_KYC_CHECK",
    },
    timeout=90,
)
data = response.json()
screening = data["screening_data"]

print(f"Result: {screening['screening_result']} (risk: {screening['risk_level']})")
print(f"PEP: {screening['is_pep']} | Sanctioned: {screening['is_sanctioned']} | Adverse media: {screening['has_adverse_media']}")
for m in screening.get("matches", []):
    print(f"  - [{m['match_type']}] {m['list_name']} ({m['match_score']:.0%})")
```

{% endtab %}

{% tab title="JavaScript (Node.js)" %}

```javascript
const BASE_URL = "https://consumer-service-api.fraudcheckonline.co.za/consumer-service";

const response = await fetch(`${BASE_URL}/comprehensive-watchlist/search`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    IdentityNo: "8601015800086",
    FirstName: "John",
    LastName: "Smith",
    BirthDate: "1986-01-01",
    Sex: "M",
    ConsentObtainedByDataSubject: true,
    PrescribedPurpose: "AML_KYC_CHECK",
  }),
});
const data = await response.json();
const s = data.screening_data;

console.log(`${s.screening_result} (risk: ${s.risk_level}, ${s.total_matches} matches)`);
s.matches?.forEach(m => console.log(`  [${m.match_type}] ${m.list_name}`));
```

{% endtab %}
{% endtabs %}

## Match categories explained

**PEP (Politically Exposed Person).** Individuals in government, the judiciary, or international organisations with significant decision-making power, plus their close associates and family members where listed.

**Sanctions.** Names appearing on international sanctions lists such as UN, OFAC SDN, EU consolidated, and similar.

**Adverse media.** Negative press mentions, criminal records, regulatory violations, or other negative news references tied to the individual.

## Compliance & consent

* Consent is mandatory. Set `ConsentObtainedByDataSubject` to `true`.
* Obtain explicit consent from the data subject before submitting.
* Retain proof of consent and the `transaction_id` for each screening in line with POPIA and your AML record-keeping obligations.

## FAQ

<details>

<summary>How long does screening take?</summary>

Typically 30–60 seconds. Configure your client timeout to at least 90 seconds.

</details>

<details>

<summary>What's the difference between `screening_result` and `risk_level`?</summary>

`screening_result` is the overall decision (`ACCEPT`/`REVIEW`/`REJECT`). `risk_level` is a finer-grained risk rating (`LOW`/`MEDIUM`/`HIGH`) that supports your own decisioning logic.

</details>

<details>

<summary>How is the risk level calculated?</summary>

Active sanctions hits and current PEP matches drive the level to `HIGH`. Historical references or weaker matches result in `MEDIUM`. No matches return `LOW`.

</details>

## Changelog

| Date       | Version | Change           |
| ---------- | ------- | ---------------- |
| 2026-04-11 | v1.0    | Initial release. |
