Skip to main content

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.

This walks through the common end-to-end flow: authenticate, create a customer, and create a loan. A single POST /v1/loans call returns an already-active loan — there is no separate approve or disburse step.

1. Obtain a token

The API lives on two hosts:
PurposeHost
Token issuance, authenticate, authorizehttps://auth-api.bsa.ai
All other endpoints (customers, loans, repayments, credit, etc.)https://api-staging.bsa.ai
Get a token from the auth host using HTTP Basic with your account credentials. The service signs with whichever RSA key it currently considers active — you don’t pass a kid.
TOKEN=$(curl -sf --user "<email>:<password>" \
  https://auth-api.bsa.ai/v1/auth/token | jq -r .token)
The response is {"token": "<JWT>"}. Tokens are valid for 365 days — see Handling expiry for the three recommended refresh patterns (proactive exp check, reactive 401 catch-and-retry, or a scheduled rotation cron). All examples below assume TOKEN is set and BASE=https://api-staging.bsa.ai.

2. Create a customer

The only field you choose is your own externalId — the identifier that ties the LMS customer record back to your system. Everything else (office, names, legal form, activation date) is filled in server-side and the customer is created Active immediately.
CUSTOMER_ID=$(curl -sf -X POST "$BASE/v1/customers" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"externalId": "ext-ada-001"}' | jq -r .id)
echo "customerId: $CUSTOMER_ID"
Response
{
  "id": "42",
  "accountNo": "000000042",
  "externalId": "ext-ada-001",
  "status": "Active",
  "active": true,
  "officeId": "1",
  "officeName": "Head Office",
  "firstname": "ext-ada-001",
  "lastname": "Customer",
  "displayName": "ext-ada-001 Customer",
  "activationDate": "2026-05-25"
}
Note the id field — "42" as a string. Pass it back exactly as received. firstname mirrors your externalId so the customer is searchable by it; lastname is a fixed placeholder LMS requires for person records.

3. Create a loan

Three required fields plus an optional externalId — your own reference for the loan (e.g. a wallet transaction id). Every loan term is inherited from the chosen product. The returned loan is already Active.
LOAN_EXT="halotel-tx-$(date +%s)"

LOAN_ID=$(curl -sf -X POST "$BASE/v1/loans" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"customerId\": $CUSTOMER_ID, \"productId\": 1, \"principal\": 5000, \"externalId\": \"$LOAN_EXT\"}" | jq -r .id)
echo "loanId: $LOAN_ID  loanExternalId: $LOAN_EXT"
Response (abbreviated)
{
  "id": "501",
  "externalId": "halotel-tx-1779712705",
  "status": "Active",
  "principal": 5000,
  "currencyCode": "TZS",
  "totalOutstanding": 5550,
  "nextDueDate": "2026-06-04",
  "nextDueAmount": 5550,
  "repaymentSchedule": [ /* one period per instalment */ ]
}
The response carries the full balance roll-up (principalOutstanding, interestCharged, totalOutstanding, …) plus nextDueDate / nextDueAmount and the amortisation repaymentSchedule. Partners can render “you owe X, due Y” without a follow-up GET. See Create a loan for the complete field reference.

4. Record a repayment

Two equivalent forms — by the LMS numeric id, or by the loan’s externalId you set above.
# By LMS id
curl -sf -X POST "$BASE/v1/loans/$LOAN_ID/repayments" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"transactionAmount": 450}'

# Equivalent — by loan externalId
curl -sf -X POST "$BASE/v1/loans/external/$LOAN_EXT/repayments" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"transactionAmount": 450}'
Every repayment endpoint (waive-interest, recover, refund, credit-balance-refund, charge-off, get/adjust/reverse transaction) has both forms — see Repayments → Identifying the loan.

Next steps