> 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/education/tertiary-verification.md).

# Tertiary Verification

Tertiary Verification confirms a consumer's higher education qualification - degree, diploma, or certificate - against a database of accredited tertiary institutions. Use it for employment screening, professional certification checks, and detecting fraudulent credentials on applications and CVs.

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.

A companion endpoint lists the institutions that can be verified - call it before submitting a request to ensure the `Institution` field will be accepted.

## When to use this

Use this service to:

* Verify a degree or diploma claim during pre-employment screening
* Check professional qualifications for regulated roles
* Detect fraudulent education credentials on CVs and applications
* Confirm bursary or grant eligibility based on qualification

## How it works

Submit the consumer's identity, the institution name, the year the qualification was obtained, and a base64-encoded image of the supporting document (degree certificate, diploma, transcript). The service validates the institution against its active list, immediately accepts the request, and returns a `transaction_id` with `screening_result: "PENDING"`. The actual verification 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 a few minutes to hours |
| Result retrieval      | Poll `GET /transactions/{transaction_id}`                         |
| Consent required      | `Yes`                                                             |
| Region                | South Africa                                                      |

***

## Authentication

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

***

## Endpoints

```http
POST /tertiary/verify
GET  /tertiary/institutions
```

***

## Listing supported institutions

Before submitting a verification, fetch the list of currently supported institutions. The `Institution` field on the verification request must match the `name` of an `active` entry from this list exactly.

```http
GET /tertiary/institutions
Authorization: Bearer {your_access_token}
```

### Example response

```json
[
  { "name": "University of Cape Town", "status": "active" },
  { "name": "University of the Witwatersrand", "status": "active" },
  { "name": "Stellenbosch University", "status": "active" }
]
```

***

## Submitting a verification

### 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                                                                                |
| `Institution`                  | string  | Yes      | Name of the tertiary institution. Must match an `active` entry from `GET /tertiary/institutions`. |
| `YearObtained`                 | integer | Yes      | Year the qualification was obtained                                                               |
| `Document1`                    | string  | Yes      | Base64-encoded image or PDF of the supporting document                                            |
| `ConsentObtainedByDataSubject` | boolean | Yes      | Must be `true`. Confirms POPIA consent.                                                           |
| `Document1Name`                | string  | No       | Filename for `Document1` (e.g. `degree_certificate.pdf`)                                          |
| `Qualification`                | string  | No       | Qualification or degree name (e.g. `Bachelor of Science`)                                         |
| `Major`                        | string  | No       | Major or field of study                                                                           |
| `CertificateNo`                | string  | No       | Certificate or diploma number                                                                     |
| `StudentNumber`                | string  | No       | Student number at the institution                                                                 |
| `SecondName`                   | string  | No       | Middle name                                                                                       |
| `BirthDate`                    | string  | No       | Date of birth (`YYYY-MM-DD`). Derived from `IdentityNo` if omitted.                               |

### Document image requirements

* **Formats:** JPEG, PNG, or PDF
* **Encoding:** base64-encoded string in `Document1`
* **Quality:** clear, in-focus, full-colour where possible, with the entire certificate visible

### Example request

```json
{
  "IdentityNo": "8601015800086",
  "FirstName": "John",
  "LastName": "Smith",
  "Institution": "University of Cape Town",
  "YearObtained": 2015,
  "Qualification": "Bachelor of Science",
  "Major": "Computer Science",
  "Document1": "base64_encoded_document_image...",
  "Document1Name": "degree_certificate.pdf",
  "ConsentObtainedByDataSubject": true
}
```

***

## Response (initial acknowledgement)

The initial response confirms the request was accepted. The verification 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 verification 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` | The qualification was verified against institution records |
| `REVIEW` | A partial match was found that requires manual review      |
| `REJECT` | The qualification could not be verified                    |
| `FAILED` | The verification could not be completed                    |

**Recommended polling:** Every 2 hours after 24 four . Tertiary verifications can take longer than other async services because some institutions are slower to respond.

Results are also sent via email

***

## 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 `Institution` not in the active list | See `details.field` or `details.missing_fields`. Re-check the institutions list. |
| 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"

# 1. List supported institutions
curl -X GET "$BASE_URL/tertiary/institutions" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# 2. Submit a verification
curl -X POST "$BASE_URL/tertiary/verify" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "IdentityNo": "8601015800086",
    "FirstName": "John",
    "LastName": "Smith",
    "Institution": "University of Cape Town",
    "YearObtained": 2015,
    "Qualification": "Bachelor of Science",
    "Document1": "'"$(base64 -w0 degree_certificate.pdf)"'",
    "Document1Name": "degree_certificate.pdf",
    "ConsentObtainedByDataSubject": true
  }'
```

{% endtab %}

{% tab title="Python" %}

```python
import base64
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",
}

# 1. Confirm the institution is active
institutions = requests.get(f"{BASE_URL}/tertiary/institutions", headers=headers).json()
active = {i["name"] for i in institutions if i["status"] == "active"}
assert "University of Cape Town" in active

# 2. Encode the supporting document
with open("degree_certificate.pdf", "rb") as f:
    document_b64 = base64.b64encode(f.read()).decode()

# 3. Submit the verification
submit = requests.post(
    f"{BASE_URL}/tertiary/verify",
    headers=headers,
    json={
        "IdentityNo": "8601015800086",
        "FirstName": "John",
        "LastName": "Smith",
        "Institution": "University of Cape Town",
        "YearObtained": 2015,
        "Qualification": "Bachelor of Science",
        "Document1": document_b64,
        "Document1Name": "degree_certificate.pdf",
        "ConsentObtainedByDataSubject": True,
    },
).json()

transaction_id = submit["transaction_id"]
print(f"Submitted, transaction_id={transaction_id}")

# 4. Poll for the final result
for _ in range(20):
    time.sleep(30)
    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
import fs from "node:fs/promises";

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

// 1. Encode the document
const document = (await fs.readFile("degree_certificate.pdf")).toString("base64");

// 2. Submit the verification
const submit = await fetch(`${BASE_URL}/tertiary/verify`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    IdentityNo: "8601015800086",
    FirstName: "John",
    LastName: "Smith",
    Institution: "University of Cape Town",
    YearObtained: 2015,
    Qualification: "Bachelor of Science",
    Document1: document,
    Document1Name: "degree_certificate.pdf",
    ConsentObtainedByDataSubject: true,
  }),
});
const { transaction_id } = await submit.json();

// 3. Poll for the final result
for (let i = 0; i < 20; i++) {
  await new Promise(r => setTimeout(r, 30000));
  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.
* Store the supporting document securely and retain proof of consent in line with POPIA requirements.

***

## FAQ

<details>

<summary>Why do I need to call `/tertiary/institutions` first?</summary>

The `Institution` field must match an active institution in the database exactly. Calling the institutions endpoint first lets you validate the name (or present a dropdown to your end user) and avoid 422 errors.

</details>

<details>

<summary>Why does verification take longer than other async services?</summary>

Some institutions are slower to respond than others, and verification may involve manual checking against the supporting document. Final results typically arrive within minutes to hours rather than seconds.

</details>

<details>

<summary>What document should I send?</summary>

A clear scan or photograph of the qualification certificate, diploma, or official transcript. Send it base64-encoded in the `Document1` field.

</details>

<details>

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

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

</details>

<details>

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

`ACCEPT` = qualification verified by the institution. `REVIEW` = partial match needing manual review. `REJECT` = qualification could not be verified. `FAILED` = verification could not complete; retry with a clearer document or contact support.

</details>

***

## Changelog

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