> For the complete documentation index, see [llms.txt](https://docs.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://docs.fraudcheck.co.za/consumer-service/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](/consumer-service/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. |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.fraudcheck.co.za/consumer-service/aml-and-watchlist/comprehensive-watchlist-screening.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
