> 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/identity/sim-swap-check.md).

# SIM Swap Check

SIM Swap Check identifies whether a mobile number has recently been transferred to a new SIM card. A recent SIM swap is a strong indicator of account-takeover fraud, where an attacker tricks a mobile operator into porting a victim's number so they can intercept SMS one-time passwords. Use this service to surface that risk before authorising sensitive actions.

## When to use this

Use this service to:

* Detect potential account-takeover attempts before sending an SMS OTP
* Block or step up authentication on high-value transactions when a number has recently been swapped
* Flag accounts for fraud review when frequent SIM changes are observed
* Support account recovery decisions

## How it works

Submit the mobile number along with the consumer's ID number and name in a single POST request. The service queries telecom records, retrieves the SIM change history for the number, and returns a synchronous response containing the swap status, the date of the most recent swap, swap counts over 6 and 12 months, and an overall risk indicator. Recent swaps (within 30 days) are treated as the highest risk.

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

***

## Authentication

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

***

## Endpoint

```http
POST /sim-swap
```

***

## 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                                                           |
| `CellNo`                       | string  | Yes      | South African mobile number. Local (`0721234567`) or international (`27721234567`) format. |
| `FirstName`                    | string  | Yes      | Consumer's first name                                                                      |
| `LastName`                     | string  | Yes      | Consumer's surname                                                                         |
| `ConsentObtainedByDataSubject` | boolean | Yes      | Must be `true`. Confirms POPIA consent.                                                    |

### Example request

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

***

## Response

### Response fields

| Field            | Type   | Description                        |
| ---------------- | ------ | ---------------------------------- |
| `transaction_id` | string | Unique identifier for this request |
| `identifier`     | string | ID number submitted                |
| `cell_no`        | string | Mobile number checked              |
| `first_name`     | string | First name submitted               |
| `last_name`      | string | Surname submitted                  |
| `SimSwapDetails` | object | Container for SIM swap result      |

### `SimSwapDetails` fields

| Field              | Type           | Description                                                |
| ------------------ | -------------- | ---------------------------------------------------------- |
| `swap_detected`    | boolean        | `true` if a recent SIM swap was found                      |
| `last_swap_date`   | string \| null | Date of the most recent SIM swap (`YYYY-MM-DD`), or `null` |
| `swap_count_6m`    | integer        | Number of SIM swaps in the last 6 months                   |
| `swap_count_12m`   | integer        | Number of SIM swaps in the last 12 months                  |
| `risk_indicator`   | string         | `LOW`, `MEDIUM`, or `HIGH`                                 |
| `network_operator` | string         | Mobile network operator (e.g. `Vodacom`, `MTN`)            |
| `line_status`      | string         | Line status (e.g. `Active`, `Suspended`)                   |

### Risk indicator logic

| Condition                                          | `risk_indicator` |
| -------------------------------------------------- | ---------------- |
| No SIM swap detected                               | `LOW`            |
| Last swap > 90 days ago                            | `LOW`            |
| Last swap 30–90 days ago                           | `MEDIUM`         |
| Last swap within 30 days, or frequent recent swaps | `HIGH`           |

### Example response - no swap detected

```json
{
  "transaction_id": "TXN20231215120540001",
  "identifier": "8601015800086",
  "cell_no": "0721234567",
  "first_name": "JOHN",
  "last_name": "SMITH",
  "product": "consumer_sim_swap",
  "SimSwapDetails": {}
}
```

### Example response - recent swap detected

```json
{
  "transaction_id": "TXN20231215120540002",
  "identifier": "***********",
  "cell_no": "0721234567",
  "first_name": "JOHN",
  "last_name": "SMITH",
 "product": "consumer_sim_swap",
  "SimSwapDetails": {
    "CellNumber": "0721234567",
        "SimSwapResult": "Sim has been swapped within the last  6month(s)",
        "RiskFlag": "Sim Swapped"
  }
}
```

***

## Errors

| HTTP | Code            | Meaning                              | Action                           |
| ---- | --------------- | ------------------------------------ | -------------------------------- |
| 400  | `bad_request`   | Missing or malformed `CellNo`        | Provide a valid SA mobile number |
| 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               | See `details.field`              |
| 500  | `server_error`  | Unexpected error                     | Contact support                  |

***

## Recommended decisioning

A simple decisioning pattern using the response:

| Outcome                                         | Recommended action                                                               |
| ----------------------------------------------- | -------------------------------------------------------------------------------- |
| `swap_detected: false`, `risk_indicator: LOW`   | Proceed with normal flow                                                         |
| `swap_detected: true`, `risk_indicator: MEDIUM` | Step up authentication (e.g. additional verification before allowing the action) |
| `swap_detected: true`, `risk_indicator: HIGH`   | Block SMS OTP delivery; require an out-of-band verification channel              |
| `swap_count_12m` ≥ 3                            | Flag account for manual fraud review                                             |

***

## Code examples

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

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

curl -X POST "$BASE_URL/sim-swap" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "IdentityNo": "8601015800086",
    "FirstName": "John",
    "LastName": "Smith",
    "CellNo": "0721234567",
    "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}/sim-swap",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={
        "IdentityNo": "8601015800086",
        "FirstName": "John",
        "LastName": "Smith",
        "CellNo": "0721234567",
        "ConsentObtainedByDataSubject": True,
    },
)
data = response.json()
details = data["SimSwapDetails"]

if details["swap_detected"]:
    print(f"⚠️  SIM swap detected - risk: {details['risk_indicator']}")
    print(f"Last swap: {details['last_swap_date']}")
else:
    print(f"No SIM swap detected - risk: {details['risk_indicator']}")
```

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

if (details.swap_detected) {
  console.warn(`SIM swap detected (${details.risk_indicator}) on ${details.last_swap_date}`);
} else {
  console.log(`No SIM swap detected - risk: ${details.risk_indicator}`);
}
```

{% 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 in line with POPIA requirements.

***

## FAQ

<details>

<summary>What counts as a "recent" SIM swap?</summary>

A swap within the last 30 days is treated as the highest risk. Swaps between 30 and 90 days old are medium risk; older than 90 days drops back to low risk.

</details>

<details>

<summary>Can I check any mobile number?</summary>

Only South African mobile numbers from supported network operators are covered.

</details>

<details>

<summary>What format should `CellNo` be in?</summary>

Either local (`0721234567`) or international (`27721234567`) format is accepted.

</details>

<details>

<summary>Why might a number return `MEDIUM` risk with no recent swap?</summary>

Frequent historical swaps over the last 12 months can elevate the risk indicator even if the most recent swap is older than 30 days.

</details>

***

## Changelog

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