> 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/authentication.md).

# Authentication

Every API request must include a valid bearer token in the `Authorization` header. Tokens are obtained by exchanging your account email and password at the `/auth/login` endpoint and are valid for **1 hour**.

{% stepper %}
{% step %}

#### Request a token

Submit your credentials as `application/x-www-form-urlencoded` using the `email` and `password` fields. The login endpoint accepts `email`.

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

```bash
curl -X POST "https://consumer-service-api.fraudcheckonline.co.za/auth/login" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "email=your-email@example.com&password=your-password"
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

response = requests.post(
    "https://consumer-service-api.fraudcheckonline.co.za/auth/login",
    data={
        "email": "your-email@example.com",
        "password": "your-password",
    },
)
token = response.json()["access_token"]
```

{% endtab %}

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

```javascript
const response = await fetch("https://consumer-service-api.fraudcheckonline.co.za/auth/login", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: "email=your-email@example.com&password=your-password",
});
const { access_token } = await response.json();
```

{% endtab %}
{% endtabs %}

**Response**

```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer"
}
```

{% endstep %}

{% step %}

#### Use the token

Include the token in the `Authorization` header on every API call:

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

**Example call**

```bash
curl -X POST "https://consumer-service-api.fraudcheckonline.co.za/consumer-service/idv/kyc/verify" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "IdentityNo": "8601015800086",
    "FirstName": "John",
    "LastName": "Smith",
    "ConsentObtainedByDataSubject": true
  }'
```

{% endstep %}

{% step %}

#### Token lifetime and refresh

* **Lifetime:** 1 hour from issue
* **Expiry behaviour:** expired tokens return `HTTP 401 Unauthorized` on every request
* **Refresh:** there is no refresh token - request a new token from `/auth/login` using the same credentials

A common pattern is to cache the token for 50 minutes, then proactively refresh before it expires. Alternatively, catch the first `401` from a request, fetch a new token, and retry the original request once.

**Python - refresh on 401**

```python
import requests

class TokenClient:
    def __init__(self, base_url, email, password):
        self.base_url = base_url
        self.email = email
        self.password = password
        self.token = None

    def _refresh(self):
        r = requests.post(
            f"{self.base_url}/auth/login",
            data={"email": self.email, "password": self.password},
        )
        r.raise_for_status()
        self.token = r.json()["access_token"]

    def post(self, path, json):
        if not self.token:
            self._refresh()
        headers = {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
        r = requests.post(f"{self.base_url}{path}", headers=headers, json=json)
        if r.status_code == 401:
            self._refresh()
            headers["Authorization"] = f"Bearer {self.token}"
            r = requests.post(f"{self.base_url}{path}", headers=headers, json=json)
        return r
```

{% endstep %}
{% endstepper %}

## Errors

| HTTP | Meaning                                                            | Action                                    |
| ---- | ------------------------------------------------------------------ | ----------------------------------------- |
| 400  | Missing `email` or `password` field                                | Include both fields in the form body      |
| 401  | Invalid credentials, or expired/invalid token on a subsequent call | Check credentials, or request a new token |
| 403  | Account exists but is disabled or not provisioned                  | Contact your account manager              |

## Storing tokens securely

* **Never** commit tokens to source control or log them.
* Store tokens in environment variables, a secrets manager, or an encrypted credential store - not in plain config files.
* Treat tokens like passwords: anyone with the token can call the API as you for the next hour.
* Rotate the password used to issue tokens periodically.

## Troubleshooting

<details>

<summary>I'm getting `401 Unauthorized` on every call.</summary>

The token has expired or was never set. Request a new token and confirm the `Authorization` header is being sent in the format `Bearer {token}` (with a space after `Bearer`).

</details>

<details>

<summary>My new token still returns `401`.</summary>

Check that you're using the correct base URL and that your account is active. If the credentials are right and the account is enabled, contact support.

</details>

<details>

<summary>I'm getting `403 Forbidden` on a service call.</summary>

The token is valid but your account doesn't have access to that service. Contact your account manager to enable the product.

</details>

<details>

<summary>Can I have multiple tokens active at once?</summary>

Yes - issuing a new token does not invalidate previously issued tokens. Each token is independently valid until it expires.

</details>


---

# 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/authentication.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.
