Boost credit limit
curl --request POST \
--url https://api.dev.bsa.ai/v1/credit-boost \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"FullName": "<string>",
"MobileNumber": "<string>"
}
'import requests
url = "https://api.dev.bsa.ai/v1/credit-boost"
payload = {
"FullName": "<string>",
"MobileNumber": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({FullName: '<string>', MobileNumber: '<string>'})
};
fetch('https://api.dev.bsa.ai/v1/credit-boost', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.dev.bsa.ai/v1/credit-boost",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'FullName' => '<string>',
'MobileNumber' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.dev.bsa.ai/v1/credit-boost"
payload := strings.NewReader("{\n \"FullName\": \"<string>\",\n \"MobileNumber\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.dev.bsa.ai/v1/credit-boost")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"FullName\": \"<string>\",\n \"MobileNumber\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.dev.bsa.ai/v1/credit-boost")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"FullName\": \"<string>\",\n \"MobileNumber\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"mobileNumber": 123,
"finalCreditScore": 123,
"originalCreditLimit": 123,
"mankaCreditLimit": 123,
"finalCreditLimit": 123,
"boostStatus": "<string>",
"executionPeriod": "<string>"
}Credit Scoring
Boost credit limit
Upload a financial statement PDF to attempt a credit-limit increase.
POST
/
v1
/
credit-boost
Boost credit limit
curl --request POST \
--url https://api.dev.bsa.ai/v1/credit-boost \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"FullName": "<string>",
"MobileNumber": "<string>"
}
'import requests
url = "https://api.dev.bsa.ai/v1/credit-boost"
payload = {
"FullName": "<string>",
"MobileNumber": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({FullName: '<string>', MobileNumber: '<string>'})
};
fetch('https://api.dev.bsa.ai/v1/credit-boost', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.dev.bsa.ai/v1/credit-boost",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'FullName' => '<string>',
'MobileNumber' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.dev.bsa.ai/v1/credit-boost"
payload := strings.NewReader("{\n \"FullName\": \"<string>\",\n \"MobileNumber\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.dev.bsa.ai/v1/credit-boost")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"FullName\": \"<string>\",\n \"MobileNumber\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.dev.bsa.ai/v1/credit-boost")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"FullName\": \"<string>\",\n \"MobileNumber\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"mobileNumber": 123,
"finalCreditScore": 123,
"originalCreditLimit": 123,
"mankaCreditLimit": 123,
"finalCreditLimit": 123,
"boostStatus": "<string>",
"executionPeriod": "<string>"
}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
The limit was raised from 30,000 to 55,000 TZS. The customer gets an
SMS confirming the new limit.
Statement passed validation but the Manka-derived limit didn’t exceed
the existing one. No database update. Customer is notified by SMS.
The customer was already boosted within the last 45 days. The
statement is not reprocessed; the existing limit is echoed back.
200 OK with the same response shape; branch on boostStatus and the
limit fields.
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.
Content type
multipart/form-data
Form fields
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.
string
required
Exactly 12 digits in international format (e.g.
255762260621). For
MNO statements, must match the phone number on the statement exactly.file
required
The statement PDF. See Statement requirements
below.
Example
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"
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())
Response
200 OK — same shape across all three scenarios:
Scenario A — Boost applied
{
"mobileNumber": 255762260621,
"finalCreditScore": 500,
"originalCreditLimit": 30000.0,
"mankaCreditLimit": 55000.0,
"finalCreditLimit": 55000.0,
"boostStatus": "YES",
"executionPeriod": "4.32 seconds"
}
Scenario B — No increase
{
"mobileNumber": 255762260621,
"finalCreditScore": 500,
"originalCreditLimit": 30000.0,
"mankaCreditLimit": 18000.0,
"finalCreditLimit": 30000.0,
"boostStatus": "NO",
"executionPeriod": "3.91 seconds"
}
Scenario C — Cooldown active
{
"mobileNumber": 255762260621,
"finalCreditScore": 500,
"originalCreditLimit": 30000.0,
"mankaCreditLimit": 30000.0,
"finalCreditLimit": 30000.0,
"boostStatus": "NO"
}
Fields
integer
integer
number
TZS. Limit before this boost attempt.
number
TZS. Limit derived from the uploaded statement.
number
TZS.
max(originalCreditLimit, mankaCreditLimit) after a successful
evaluation; otherwise echoes originalCreditLimit.string
YES if the limit was raised; NO otherwise (including the cooldown case).string
Upstream processing time. Diagnostic only.
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) |
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".
