Record a repayment
curl --request POST \
--url https://api.dev.bsa.ai/v1/loans/external/{loan_external_id}/repayments \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"transactionAmount": 123,
"transactionDate": "<string>",
"transactionAt": "<string>",
"idempotencyKey": "<string>"
}
'import requests
url = "https://api.dev.bsa.ai/v1/loans/external/{loan_external_id}/repayments"
payload = {
"transactionAmount": 123,
"transactionDate": "<string>",
"transactionAt": "<string>",
"idempotencyKey": "<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({
transactionAmount: 123,
transactionDate: '<string>',
transactionAt: '<string>',
idempotencyKey: '<string>'
})
};
fetch('https://api.dev.bsa.ai/v1/loans/external/{loan_external_id}/repayments', 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/loans/external/{loan_external_id}/repayments",
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([
'transactionAmount' => 123,
'transactionDate' => '<string>',
'transactionAt' => '<string>',
'idempotencyKey' => '<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/loans/external/{loan_external_id}/repayments"
payload := strings.NewReader("{\n \"transactionAmount\": 123,\n \"transactionDate\": \"<string>\",\n \"transactionAt\": \"<string>\",\n \"idempotencyKey\": \"<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/loans/external/{loan_external_id}/repayments")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"transactionAmount\": 123,\n \"transactionDate\": \"<string>\",\n \"transactionAt\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.dev.bsa.ai/v1/loans/external/{loan_external_id}/repayments")
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 \"transactionAmount\": 123,\n \"transactionDate\": \"<string>\",\n \"transactionAt\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"loanStatus": "<string>",
"totalOutstanding": 123,
"availableCreditLimit": 123
}Repayments
Record a repayment
Post a regular installment repayment.
POST
/
v1
/
loans
/
external
/
{loan_external_id}
/
repayments
Record a repayment
curl --request POST \
--url https://api.dev.bsa.ai/v1/loans/external/{loan_external_id}/repayments \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"transactionAmount": 123,
"transactionDate": "<string>",
"transactionAt": "<string>",
"idempotencyKey": "<string>"
}
'import requests
url = "https://api.dev.bsa.ai/v1/loans/external/{loan_external_id}/repayments"
payload = {
"transactionAmount": 123,
"transactionDate": "<string>",
"transactionAt": "<string>",
"idempotencyKey": "<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({
transactionAmount: 123,
transactionDate: '<string>',
transactionAt: '<string>',
idempotencyKey: '<string>'
})
};
fetch('https://api.dev.bsa.ai/v1/loans/external/{loan_external_id}/repayments', 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/loans/external/{loan_external_id}/repayments",
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([
'transactionAmount' => 123,
'transactionDate' => '<string>',
'transactionAt' => '<string>',
'idempotencyKey' => '<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/loans/external/{loan_external_id}/repayments"
payload := strings.NewReader("{\n \"transactionAmount\": 123,\n \"transactionDate\": \"<string>\",\n \"transactionAt\": \"<string>\",\n \"idempotencyKey\": \"<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/loans/external/{loan_external_id}/repayments")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"transactionAmount\": 123,\n \"transactionDate\": \"<string>\",\n \"transactionAt\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.dev.bsa.ai/v1/loans/external/{loan_external_id}/repayments")
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 \"transactionAmount\": 123,\n \"transactionDate\": \"<string>\",\n \"transactionAt\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"loanStatus": "<string>",
"totalOutstanding": 123,
"availableCreditLimit": 123
}Records a standard repayment against an active loan. The amount is
allocated to interest then principal according to the loan’s
processing rules.
Two equivalent forms — prefer the externalId form for partner integrations.
The position fields appear only on this POST response. Ledger reads
(list, get-one, and the
Path parameters
string
required
The loan’s externalId. On the
/v1/loans/{loan_id}/repayments form,
this is the numeric LMS id instead.Request body
number
required
Must be greater than 0.
string
Optional value date of the payment, in ISO
yyyy-MM-dd. Books the
repayment as of the day the money actually moved on your side rather
than the day you call this API — the reconciliation case where a
wallet debit succeeds but settling with us is delayed. Defaults to the
current date when omitted. Must be on or after the loan’s
disbursement date and not in the future.string
Optional exact instant the money moved on your side, RFC3339 (e.g.
2026-08-15T13:15:00+03:00). Also fixes transactionDate (which must
agree if both are sent). Recommended on every settlement so the loan’s
overdue position reflects when the customer actually paid.string
Optional dedupe token — typically your wallet transaction reference
for this payment (max 100 characters). If a repayment carrying this key
has already been recorded on the loan, the original transaction is
returned unchanged — the retry does not double-post. Strongly
recommended on every repayment so timeouts
and retries are safe to replay. Use a value unique per payment.
Examples
# By loan externalId (recommended)
curl -sf -X POST "$BASE/v1/loans/external/loan-ext-12345/repayments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "transactionAmount": 11100 }'
# Backdated value date + idempotency key — settle as of the day the
# wallet was debited, safe to retry. Recommended shape for every payment.
curl -sf -X POST "$BASE/v1/loans/external/loan-ext-12345/repayments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "transactionAmount": 11100, "transactionDate": "2026-06-01", "idempotencyKey": "wallet-txn-abc123" }'
# Same effect, by LMS id
curl -sf -X POST "$BASE/v1/loans/501/repayments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "transactionAmount": 11100 }'
Response
200 OK returns the repayment object showing
how the amount was allocated, plus the loan’s post-repayment position
so you can decide what to do next without follow-up reads.
{
"id": "7821",
"type": "Repayment",
"date": "2026-05-25",
"amount": 11100,
"principal": 10000,
"interest": 1100,
"status": "posted",
"loanStatus": "Closed (obligations met)",
"totalOutstanding": 0,
"availableCreditLimit": 15000
}
string
The loan’s status after this payment —
Active,
Closed (obligations met), or Overpaid.number
What is still owed on this loan after the payment.
0 means the
loan is settled.number
Remaining borrowing headroom now, across all the customer’s loans —
the same figure as the credit scorecard:
finalCreditLimit - totalOutstanding, floored at 0. Every repayment
frees up headroom immediately. If the scoring service cannot be reached,
the field is omitted — fall back to the scorecard endpoint.repaymentHistory embedded in the loan detail) return pure transaction
objects.
Errors
| Code | When |
|---|---|
not_found | No loan with that id or externalId |
failed_precondition | Loan is not in an active state |
invalid_argument | Missing or non-positive transactionAmount; transactionDate not ISO yyyy-MM-dd, in the future, or before disbursement; idempotencyKey longer than 100 characters |

