> 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/self-exclusion-screening.md).

# Self-Exclusion Screening

Self-Exclusion Screening is for gambling licensees that must refuse excluded persons. It screens an individual at signup against the self-exclusion list the operator receives from its licensing authority. That list is hosted in an encrypted architecture and is ring-fenced: it is never shared with, compared against, or used by any other client or product.

The check returns a match result for the operator to act on. It does not block the customer. The duty to refuse an excluded person stays with the licensee.

| Segment  | Category       | Turnaround       |
| -------- | -------------- | ---------------- |
| Consumer | Self-Exclusion | Real-time · Live |

## What is required

The individual's 13-digit South African ID number or passport number. The operator must have uploaded an exclusion list before a screen can match anyone.

Lists are uploaded through the client portal as CSV or Excel. A file can add people, revoke people, or both. A new ID is added. An ID that is already stored is left as it is. A revocation applies only when that person is currently excluded. One person can also be added or revoked from the portal without a file. Invalid rows are skipped and reported. They do not reject the rest of the file.

## When to use this

Use this service to:

* Screen a new player at registration, before an account is opened or a first deposit is accepted
* Show that self-exclusion checks run at onboarding against the list last uploaded
* Keep the `transaction_id` from each screen with your own record of the decision you made

## How it works

The operator uploads the exclusion list it receives from its licensing authority. At signup, submit the customer's ID or passport, name, and consent flag in one POST request. The ID or passport is normalised and compared with that operator's list only. The response is synchronous.

`FirstName` and `LastName` are required and kept with the check. They do not change the outcome. Spaces in the ID or passport are ignored, and letters are matched without regard to case.

A person who was excluded and later revoked is not a match. The screen is only as current as the latest upload.

| Property              | Value            |
| --------------------- | ---------------- |
| Response type         | `Synchronous`    |
| Typical response time | Real-time · < 5s |
| Result retrieval      | Returned inline  |
| Consent required      | `Yes`            |
| Region                | South Africa     |

## Authentication

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

## Endpoint

```http
POST /self-exclusion/verify
```

## Request

### Headers

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

### Body parameters

| Field                          | Type    | Required | Description                                                                         |
| ------------------------------ | ------- | -------- | ----------------------------------------------------------------------------------- |
| `IdentityNo`                   | string  | Yes      | South African ID number or passport number. This is the only field that is matched. |
| `FirstName`                    | string  | Yes      | Person's first name. Recorded with the check. Not used in the match.                |
| `LastName`                     | string  | Yes      | Person's surname. Recorded with the check. Not used in the match.                   |
| `ConsentObtainedByDataSubject` | boolean | Yes      | Must be `true`. Confirms POPIA consent.                                             |

Spaces in `IdentityNo` are ignored. Letters are matched without regard to case, so `bj 0412963` and `BJ0412963` are the same passport.

### Example request

```json
{
  "IdentityNo": "8601015800086",
  "FirstName": "John",
  "LastName": "Smith",
  "ConsentObtainedByDataSubject": true
}
```

## Response

### What you get back

| Outcome  | `screening_result` | Meaning                                                                |
| -------- | ------------------ | ---------------------------------------------------------------------- |
| Match    | `REJECT`           | The ID or passport exactly matches a person who is currently excluded. |
| No match | `ACCEPT`           | The individual is not on the list. A revoked exclusion is a no match.  |

On a match, `data.excluded_at` is the date that person was excluded. The response does not block the customer. Your system decides what to do with the result.

### Response fields

| Field              | Type    | Description                      |
| ------------------ | ------- | -------------------------------- |
| `success`          | boolean | `true` when the check completed  |
| `transaction_id`   | string  | Unique identifier for this check |
| `screening_result` | string  | `ACCEPT` or `REJECT`             |
| `message`          | string  | Short explanation of the outcome |
| `data`             | object  | Match detail (see below)         |

### `data` fields

| Field         | Type    | Description                                                                           |
| ------------- | ------- | ------------------------------------------------------------------------------------- |
| `matched`     | boolean | `true` when an active exclusion was found                                             |
| `status`      | string  | `EXCLUDED` when matched. Otherwise `null`.                                            |
| `excluded_at` | string  | When the person was excluded, as an ISO 8601 datetime. `null` when there is no match. |

### Screening result logic

| Condition                     | `screening_result` | `message`                                 |
| ----------------------------- | ------------------ | ----------------------------------------- |
| Active exclusion found        | `REJECT`           | `Match found on self-exclusion register.` |
| No active exclusion           | `ACCEPT`           | `No active self-exclusion found.`         |
| Lookup could not be completed | HTTP 500           | `Self-exclusion lookup failed.`           |

A revoked exclusion is treated as no active exclusion.

### Example response - no match

```json
{
  "success": true,
  "transaction_id": "d41d8cd98f00b204e9800998ecf8427e",
  "screening_result": "ACCEPT",
  "message": "No active self-exclusion found.",
  "data": {
    "matched": false,
    "status": null,
    "excluded_at": null
  }
}
```

### Example response - match

```json
{
  "success": true,
  "transaction_id": "d41d8cd98f00b204e9800998ecf8427e",
  "screening_result": "REJECT",
  "message": "Match found on self-exclusion register.",
  "data": {
    "matched": true,
    "status": "EXCLUDED",
    "excluded_at": "2026-10-09T12:00:00"
  }
}
```

## Errors

| HTTP | Code            | Meaning                                          | Action                                                     |
| ---- | --------------- | ------------------------------------------------ | ---------------------------------------------------------- |
| 401  | `unauthorized`  | Token missing, expired, or invalid               | Refresh your access token                                  |
| 403  | `forbidden`     | Credentials do not include this service          | Contact your account manager                               |
| 422  | `invalid_input` | Required field missing, or consent is not `true` | See `missing_fields` or set consent to `true`              |
| 500  | `server_error`  | The lookup could not be completed                | Retry once, then contact support with the time of the call |

## Code examples

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

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

curl -X POST "$BASE_URL/self-exclusion/verify" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "IdentityNo": "8601015800086",
    "FirstName": "John",
    "LastName": "Smith",
    "ConsentObtainedByDataSubject": true
  }'
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

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

response = requests.post(
    f"{BASE_URL}/self-exclusion/verify",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={
        "IdentityNo": "8601015800086",
        "FirstName": "John",
        "LastName": "Smith",
        "ConsentObtainedByDataSubject": True,
    },
)
result = response.json()

print(f"{result['screening_result']}: {result['message']}")
if result["data"]["matched"]:
    print(f"Excluded at {result['data']['excluded_at']}")
```

{% 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}/self-exclusion/verify`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    IdentityNo: "8601015800086",
    FirstName: "John",
    LastName: "Smith",
    ConsentObtainedByDataSubject: true,
  }),
});
const result = await response.json();

console.log(`${result.screening_result}: ${result.message}`);
```

{% endtab %}
{% endtabs %}

## Recommended decisioning

| Outcome  | Recommended action                                                                              |
| -------- | ----------------------------------------------------------------------------------------------- |
| `ACCEPT` | No active self-exclusion was found. The licensee decides whether to continue.                   |
| `REJECT` | A match was found. The licensee decides whether to refuse. The API does not block the customer. |

## Compliance & consent

* Obtain explicit consent from the data subject before submitting a request.
* Set `ConsentObtainedByDataSubject` to `true` to confirm consent has been captured.
* Retain proof of consent and the `transaction_id` for each check in line with POPIA.

## FAQ

<details>

<summary>Does a match stop the signup?</summary>

No. `REJECT` means the ID or passport is on the list. Your system decides whether to refuse the person. This API does not block them.

</details>

<details>

<summary>Does the name affect the result?</summary>

No. `FirstName` and `LastName` are required and stored with the check. The match compares `IdentityNo` only.

</details>

<details>

<summary>Can I send a passport number?</summary>

Yes. `IdentityNo` accepts a South African ID number or a passport number. Spaces are removed and letters are uppercased before the comparison.

</details>

<details>

<summary>What if the person was excluded and then revoked?</summary>

That is not a match. The response is `ACCEPT` with `data.matched` set to `false`.

</details>

<details>

<summary>Can I fetch the result again later?</summary>

The outcome is returned in the POST response. Keep the `transaction_id` for your own audit and for support.

</details>

## Changelog

| Date       | Version | Change           |
| ---------- | ------- | ---------------- |
| 2026-10-13 | 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/self-exclusion-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.
