> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lms.bsa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Boost credit limit

> Upload a financial statement PDF to attempt a credit-limit increase.

Submits a PDF statement (mobile money or bank) to the Manka AI engine.
If the statement is valid, recent, and yields a higher limit than the
customer's current one, the customer's pre-approved limit is increased
on the spot. All three outcomes — increased, unchanged, cooldown — return
`200 OK` with the same response shape; branch on `boostStatus` and the
limit fields.

<Note>
  The upstream **always sends an SMS** to the customer's mobile number
  when this endpoint is called, including on validation failures. Treat
  every successful call as an outbound communication event.
</Note>

## Content type

`multipart/form-data`

## Form fields

<ParamField body="FullName" type="string" required>
  Customer's full name as it appears on the statement. For bank
  statements, at least two name parts must match the statement
  (case-insensitive) or the upstream rejects with a name mismatch.
</ParamField>

<ParamField body="MobileNumber" type="string" required>
  Exactly 12 digits in international format (e.g. `255762260621`). For
  MNO statements, must match the phone number on the statement exactly.
</ParamField>

<ParamField body="pdf_file" type="file" required>
  The statement PDF. See [Statement requirements](#statement-requirements)
  below.
</ParamField>

## Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -sf -X POST "$BASE/v1/credit-boost" \
    -H "Authorization: Bearer $TOKEN" \
    -F "FullName=JOHN DEO MWAMBA" \
    -F "MobileNumber=255762260621" \
    -F "pdf_file=@/path/to/statement.pdf"
  ```

  ```python Python theme={null}
  import requests

  with open("/path/to/statement.pdf", "rb") as f:
      resp = requests.post(
          f"{BASE}/v1/credit-boost",
          headers={"Authorization": f"Bearer {TOKEN}"},
          data={"FullName": "JOHN DEO MWAMBA", "MobileNumber": "255762260621"},
          files={"pdf_file": ("statement.pdf", f, "application/pdf")},
      )
  print(resp.status_code, resp.json())
  ```
</CodeGroup>

## Response

`200 OK` — same shape across all three scenarios:

### Scenario A — Boost applied

```json theme={null}
{
  "mobileNumber": 255762260621,
  "finalCreditScore": 500,
  "originalCreditLimit": 30000.0,
  "mankaCreditLimit": 55000.0,
  "finalCreditLimit": 55000.0,
  "boostStatus": "YES",
  "executionPeriod": "4.32 seconds"
}
```

The limit was raised from 30,000 to 55,000 TZS. The customer gets an
SMS confirming the new limit.

### Scenario B — No increase

```json theme={null}
{
  "mobileNumber": 255762260621,
  "finalCreditScore": 500,
  "originalCreditLimit": 30000.0,
  "mankaCreditLimit": 18000.0,
  "finalCreditLimit": 30000.0,
  "boostStatus": "NO",
  "executionPeriod": "3.91 seconds"
}
```

Statement passed validation but the Manka-derived limit didn't exceed
the existing one. No database update. Customer is notified by SMS.

### Scenario C — Cooldown active

```json theme={null}
{
  "mobileNumber": 255762260621,
  "finalCreditScore": 500,
  "originalCreditLimit": 30000.0,
  "mankaCreditLimit": 30000.0,
  "finalCreditLimit": 30000.0,
  "boostStatus": "NO"
}
```

The customer was already boosted within the last **45 days**. The
statement is not reprocessed; the existing limit is echoed back.

## Fields

<ResponseField name="mobileNumber" type="integer" />

<ResponseField name="finalCreditScore" type="integer" />

<ResponseField name="originalCreditLimit" type="number">
  TZS. Limit before this boost attempt.
</ResponseField>

<ResponseField name="mankaCreditLimit" type="number">
  TZS. Limit derived from the uploaded statement.
</ResponseField>

<ResponseField name="finalCreditLimit" type="number">
  TZS. `max(originalCreditLimit, mankaCreditLimit)` after a successful
  evaluation; otherwise echoes `originalCreditLimit`.
</ResponseField>

<ResponseField name="boostStatus" type="string">
  `YES` if the limit was raised; `NO` otherwise (including the cooldown case).
</ResponseField>

<ResponseField name="executionPeriod" type="string">
  Upstream processing time. Diagnostic only.
</ResponseField>

## Statement requirements

| Requirement           | Detail                                                       |
| --------------------- | ------------------------------------------------------------ |
| Format                | PDF only (`.pdf`)                                            |
| Accepted sources      | Airtel, Yas, Vodacom, Halotel, NMB, CRDB                     |
| Rejected sources      | HaloPesa, Selcom, any other institution                      |
| Recency               | Last transaction within the past 14 days                     |
| Coverage — Airtel     | Minimum 15 days of history                                   |
| Coverage — all others | Minimum 60 days of history                                   |
| MNO match             | Phone number on statement must equal `MobileNumber`          |
| Bank match            | At least 2 name parts on the statement must match `FullName` |
| Authenticity          | Unedited original, direct from the provider                  |

## Errors

The upstream uses several non-standard status codes. We translate them
onto our standard error code surface:

| Code                  | Caused by                                                                                                                                                                                                                                    |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_argument`    | File is not a PDF (upstream 400); statement processing failed (402); name/phone mismatch (405); statement not recent enough (406); statement source unsupported, insufficient coverage, or other validation failure surfaced as upstream 401 |
| `failed_precondition` | Statement appears tampered or forged (upstream 403); customer not eligible — current limit ≤ 500 TZS (upstream 500)                                                                                                                          |
| `unauthenticated`     | Token missing/invalid (upstream 401 with detail `"Invalid or missing token"`)                                                                                                                                                                |
| `unavailable`         | Upstream Manka engine unreachable (upstream 502)                                                                                                                                                                                             |
| `internal`            | Database write failed after a successful boost compute (upstream 998); other unhandled server error (501)                                                                                                                                    |

<Warning>
  The upstream returns `401` for both genuine auth failures and several
  business-validation failures (unsupported source, HaloPesa statement,
  insufficient coverage). In those latter cases the API translates the
  result to `invalid_argument`, not `unauthenticated`. If you need to
  distinguish, inspect the `message` field — auth failures contain the
  literal string `"Invalid or missing token"`.
</Warning>
