> 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/safps-fraud-listing-lookup.md).

# SAFPS Fraud Listing Lookup

SAFPS Fraud Listing Lookup checks whether a South African consumer is recorded on the SAFPS (South African Fraud Prevention Service) fraud database. Use it to flag known fraud listings during onboarding, employment screening, or credit applications.

The service is **asynchronous**: the initial request returns immediately with a `PENDING` status and a transaction ID. The final result is produced in the background and retrieved using that transaction ID.

## When to use this

Use this service to:

* Screen consumers against SAFPS fraud listings during onboarding
* Run pre-employment fraud checks
* Detect fraud risk on credit applications
* Support KYC and AML workflows that require a SAFPS check

## How it works

Submit the consumer's name, ID number, and consent flag in a single POST request. The service immediately accepts the request, creates a tracking transaction, and returns a `transaction_id` with `screening_result: "PENDING"`. The actual SAFPS lookup is processed in the background. Once complete, fetch the final result using the transaction endpoint.

| Property              | Value                                                  |
| --------------------- | ------------------------------------------------------ |
| Response type         | `Asynchronous`                                         |
| Typical response time | Immediate ack; final result usually within 1–5 minutes |
| Result retrieval      | Poll `GET /transactions/{transaction_id}`              |
| Consent required      | `Yes`                                                  |
| Region                | South Africa                                           |

***

## Authentication

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

***

## Endpoint

```http
POST /fraud-listing-lookup/verify
```

***

## 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      | Consumer's first name                                               |
| `LastName`                     | string  | Yes      | Consumer's surname                                                  |
| `ConsentObtainedByDataSubject` | boolean | Yes      | Must be `true`. Confirms POPIA consent.                             |
| `SecondName`                   | string  | No       | Middle name                                                         |
| `BirthDate`                    | string  | No       | Date of birth (`YYYY-MM-DD`). Derived from `IdentityNo` if omitted. |
| `ReferenceNo`                  | string  | No       | Your internal reference, echoed back in result lookups              |

### Example request

```json
{
  "IdentityNo": "8601015800086",
  "FirstName": "John",
  "LastName": "Smith",
  "ConsentObtainedByDataSubject": true,
  "ReferenceNo": "ORDER-12345"
}
```

***

## Response (initial acknowledgement)

The initial response confirms the request was accepted. The screening itself runs in the background.

### Response fields

| Field                  | Type   | Description                                                              |
| ---------------------- | ------ | ------------------------------------------------------------------------ |
| `transaction_id`       | string | Unique identifier for this request. Use it to retrieve the final result. |
| `screening_result`     | string | Initial status - always `PENDING` on the first response                  |
| `response_description` | string | Human-readable status message                                            |

### Example response - accepted

```json
{
  "transaction_id": "d41d8cd98f00b204e9800998ecf8427e",
  "screening_result": "PENDING",
  "response_description": "Request accepted and is being processed."
}
```

***

## Retrieving the final result

Once the lookup has been processed in the background, fetch the final result using the transaction ID:

```http
GET /transactions/{transaction_id}
Authorization: Bearer {your_access_token}
```

The `screening_result` will transition from `PENDING` to one of:

| Result   | Meaning                                                |
| -------- | ------------------------------------------------------ |
| `ACCEPT` | No SAFPS fraud listing found for this consumer         |
| `REVIEW` | A possible match was found that requires manual review |
| `REJECT` | A confirmed SAFPS fraud listing was found              |
| `FAILED` | The lookup could not be completed                      |

**Recommended polling:** every 5 seconds, up to 2 minutes. If the result is still `PENDING` after 2 minutes, switch to a longer interval with exponential backoff.

***

## 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 or invalid    | See `details.missing_fields` |
| 500  | `server_error`  | Unexpected error                     | Contact support              |

***

## Code examples

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

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

curl -X POST "$BASE_URL/fraud-listing-lookup/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 time
import requests

BASE_URL = "https://consumer-service-api.fraudcheckonline.co.za/consumer-service"
headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json",
}

# Submit the request
response = requests.post(
    f"{BASE_URL}/fraud-listing-lookup/verify",
    headers=headers,
    json={
        "IdentityNo": "8601015800086",
        "FirstName": "John",
        "LastName": "Smith",
        "ConsentObtainedByDataSubject": True,
    },
)
transaction_id = response.json()["transaction_id"]
print(f"Submitted, transaction_id={transaction_id}")

# Poll for the final result
for _ in range(24):  # up to 2 minutes
    time.sleep(5)
    result = requests.get(f"{BASE_URL}/transactions/{transaction_id}", headers=headers).json()
    if result["screening_result"] != "PENDING":
        print(f"Final result: {result['screening_result']}")
        break
```

{% endtab %}

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

```javascript
const BASE_URL = "https://consumer-service-api.fraudcheckonline.co.za/consumer-service";
const headers = {
  Authorization: `Bearer ${accessToken}`,
  "Content-Type": "application/json",
};

// Submit the request
const submit = await fetch(`${BASE_URL}/fraud-listing-lookup/verify`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    IdentityNo: "8601015800086",
    FirstName: "John",
    LastName: "Smith",
    ConsentObtainedByDataSubject: true,
  }),
});
const { transaction_id } = await submit.json();

// Poll for the final result
for (let i = 0; i < 24; i++) {
  await new Promise(r => setTimeout(r, 5000));
  const res = await fetch(`${BASE_URL}/transactions/${transaction_id}`, { headers });
  const result = await res.json();
  if (result.screening_result !== "PENDING") {
    console.log(`Final result: ${result.screening_result}`);
    break;
  }
}
```

{% endtab %}
{% endtabs %}

***

## 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 lookup in line with POPIA requirements.

***

## FAQ

<details>

<summary>Why is the result <code>PENDING</code> when I first call the API?</summary>

This service is asynchronous. The initial response confirms the request was accepted; the actual SAFPS lookup runs in the background and the result becomes available shortly afterwards.

</details>

<details>

<summary>How do I get the final result?</summary>

Poll `GET /transactions/{transaction_id}` until `screening_result` is no longer `PENDING`. A 5-second interval for up to 2 minutes is a reasonable starting point.

</details>

<details>

<summary>Do I need to provide <code>BirthDate</code>?</summary>

No. If omitted, it is derived from the South African ID number.

</details>

<details>

<summary>What does each final result mean?</summary>

`ACCEPT` = no listing found. `REVIEW` = possible match needing manual review. `REJECT` = confirmed SAFPS fraud listing. `FAILED` = the lookup could not complete; retry the request.

</details>

***

## Changelog

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