Skip to main content
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>",
  "idempotencyKey": "<string>"
}
'
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.

Path parameters

loan_external_id
string
required
The loan’s externalId. On the /v1/loans/{loan_id}/repayments form, this is the numeric LMS id instead.

Request body

transactionAmount
number
required
Must be greater than 0.
transactionDate
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 (a failure, timeout, or a next-day retry).When the value date is on or before the loan’s due date (the customer paid on time) and the loan is carrying an overdue penalty the LMS raised while your settle call was in flight, the service automatically cancels that penalty before posting the repayment, then lets the LMS recompute it against whatever is genuinely still overdue. See Reconciling delayed settlements for the full behaviour.Defaults to the current date when omitted. Must be on or after the loan’s disbursement date and not in the future. Backdating across a later transaction already recorded on the loan may be rejected by the ledger.
idempotencyKey
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 neither double-posts nor re-runs penalty reconciliation. 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.
{
  "id": "7821",
  "type": "Repayment",
  "date": "2026-05-25",
  "amount": 11100,
  "principal": 10000,
  "interest": 1100,
  "status": "posted"
}

Errors

CodeWhen
not_foundNo loan with that id or externalId
failed_preconditionLoan is not in an active state
invalid_argumentMissing or non-positive transactionAmount; transactionDate not ISO yyyy-MM-dd, in the future, or before disbursement; idempotencyKey longer than 100 characters