Skip to main content
GET
/
v1
/
loans
List loans
curl --request GET \
  --url https://api.dev.bsa.ai/v1/loans \
  --header 'Authorization: Bearer <token>'
Returns a page of loans. See Pagination for the common query parameter contract.

Query parameters

page
integer
default:"1"
rows
integer
default:"10"
orderBy
string
Common values: id, principal, expectedDisbursementDate.
sortOrder
string
ASC or DESC.
customerId
integer
Restrict the result to loans belonging to a specific customer (by internal LMS id). Mutually exclusive with customerExternalId — pass one or the other, not both.
customerExternalId
string
Restrict the result to loans belonging to the customer with this partner-supplied externalId. Mutually exclusive with customerId. Named explicitly because loans have their own externalId field too — this one filters by the customer’s externalId, not the loan’s.
status
string
Case-insensitive exact match against the loan’s status. Accepted values are the exact strings the LMS returns: Active, Submitted and pending approval, Approved, Withdrawn by applicant, Rejected, Closed (obligations met), Closed (written off), Overpaid. The filter is applied wrapper-side after fetching, so total reflects the filtered count, not the unfiltered scope. Scans up to 1000 loans per call.

Examples

# All loans for customer 42 (by LMS id)
curl -sf "$BASE/v1/loans?customerId=42&page=1&rows=25" \
  -H "Authorization: Bearer $TOKEN"

# Same customer, looked up by their externalId
curl -sf "$BASE/v1/loans?customerExternalId=ext-ada-001" \
  -H "Authorization: Bearer $TOKEN"

# Only this customer's currently-active loans
curl -sf "$BASE/v1/loans?customerExternalId=ext-ada-001&status=Active" \
  -H "Authorization: Bearer $TOKEN"

Response

Each row has the same shape as a single-loan read — balance roll-up, next-due summary, and the full repaymentSchedule — so partners can render outstanding-balance / next-instalment / schedule views without a per-loan detail call.
{
  "items": [
    {
      "id": "501",
      "accountNo": "000000501",
      "externalId": "loan-ext-12345",
      "customerId": "42",
      "customerName": "Ada Lovelace",
      "loanProductId": "1",
      "loanProductName": "7-Day Loan",
      "status": "Active",
      "principal": 5000,
      "approvedPrincipal": 5000,
      "currencyCode": "TZS",
      "termFrequency": 7,
      "numberOfRepayments": 1,
      "interestRatePerPeriod": 11,

      "principalDisbursed": 5000,
      "principalPaid": 0,
      "principalOutstanding": 5000,
      "interestCharged": 38.5,
      "interestPaid": 0,
      "interestOutstanding": 38.5,
      "feeCharged": 0,
      "feePaid": 0,
      "feeOutstanding": 0,
      "penaltyCharged": 0,
      "penaltyPaid": 0,
      "penaltyOutstanding": 0,
      "totalRepayment": 0,
      "totalOutstanding": 5038.5,
      "totalOverpaid": 0,

      "nextDueDate": "2026-06-04",
      "nextDueAmount": 5038.5,
      "overdue": false,

      "repaymentSchedule": [
        {
          "period": 1,
          "fromDate": "2026-05-28",
          "dueDate": "2026-06-04",
          "complete": false,
          "principalDue": 5000,
          "principalOutstanding": 5000,
          "interestDue": 38.5,
          "interestOutstanding": 38.5,
          "totalDue": 5038.5,
          "totalOutstanding": 5038.5
        }
      ]
    }
  ],
  "total": 7,
  "page": 1,
  "rowsPerPage": 25
}
See the loan object for the full field reference.
repaymentSchedule on list rows is best-effort: each row is hydrated with a per-loan read behind the scenes, and if that read fails transiently the row is returned without the schedule rather than failing your whole page. nextDueDate / nextDueAmount are still present in that case. Treat a missing schedule on a list row as “retry or fetch the single loan”, not as “this loan has no schedule” — GET /v1/loans/{id} always carries it.