Addressing the loan
Both forms hit the same handlers and return identical responses. The
externalId form returns
404 not_found if no loan matches.
The repayment object
The same object is returned by repay, get a transaction, the transaction list, and as each entry ofrepaymentHistory on the loan object.
string
Transaction type label — always
Repayment. The history is restricted
to customer repayments; disbursements, waivers, and accruals are
excluded.string
ISO-8601
YYYY-MM-DD. Value date the transaction was effective.number
Total transaction amount.
number
Portion applied to principal. Omitted if zero.
number
Portion applied to interest. Omitted if zero.
number
Portion applied to fees. Omitted if zero.
number
Portion applied to penalty charges. Omitted if zero.
number
Overpayment amount, present only when the transaction exceeded the
outstanding balance. A strong signal that something needs reconciling.
string
The dedupe token this repayment was posted with — typically your wallet
transaction reference — echoed back so a ledger entry can be matched to
your own payment records. Absent for repayments recorded without one.
string
posted for a normal transaction, or reversed if it has been
reversed (an operational LMS-side action).Endpoints
The repayment reads (list and get-one) are available in the
externalId form only. Posting a repayment still supports both the
externalId and numeric-id forms.
Repayment history: two ways to read it
The repayment history is available in two places, both returning the object above and both containing only customer repayments (newest first) — disbursements, waivers, and accruals are excluded:- Embedded snapshot — every single-loan read
(
GET /v1/loans/external/{loan_external_id}) includes arepaymentHistoryarray. Use this for a quick view without a second call. It is not included on the loan list endpoint, only on single-loan reads. - Paginated list —
GET /v1/loans/external/{loan_external_id}/repayments(reference) returns the same entries withpage/rowspagination, for loans with many partial payments.
Request body
POST /v1/loans/.../repayments takes:
number
required
Must be greater than 0.
string
Optional value date (ISO
yyyy-MM-dd). Defaults to today. See
Reconciling delayed settlements.string
Optional dedupe token — typically your wallet transaction reference
(max 100 characters). A repayment carrying a key already seen on the
loan returns the original transaction unchanged, so retries never
double-post. Strongly recommended on every repayment.
Timeouts and retries
If aPOST .../repayments call times out, you cannot know whether the
repayment was recorded — and without the response you have no repayment
id. Don’t try to find out first: retry the identical request with the
same idempotencyKey.
The service checks the loan’s ledger for that key before posting anything:
- If the first attempt was recorded, the retry returns the original
transaction — same
id,status: "posted"— and nothing is posted twice. Penalty reconciliation is not re-run either. - If it was not, the retry records the repayment normally.
id you were
missing. This is safe to repeat any number of times, immediately or days
later: the key is stored on the transaction itself in the LMS, so the
guarantee never expires. The key is also echoed back as idempotencyKey
on every repayment read, so the ledger can always be matched to your own
records.
If the retries themselves keep timing out, don’t retry forever —
switch to the read-only path and query the transaction by your own
reference:
- One item back → the repayment was recorded; you have its
id, amount, value date andstatus. - Empty result → it was not recorded. The key guarantees nothing partial exists, so you can mark the payment failed on your side, or re-post it later with the same key.
Reconciling delayed settlements
A common integration pattern is to debit the customer’s wallet first, then call this API to settle the loan. If that settle call fails, times out, or is only retried the next day, the payment still happened on your side — and your records are the source of truth for when. PasstransactionDate set to the date the wallet was actually debited.
The repayment is then booked as of that value date rather than the
day the API call lands. What that does:
- It records when the payment happened. Allocation and balances reflect the backdated date.
- On-time payments are not penalised for your delay. The penalty is
posted at the loan’s exact
dueDateTime(disbursedAt+ term). If your settle call lands after that instant but the payment settles the loan in full and provably happened beforedueDateTime, the service waives the penalty before posting the repayment, so the payment lands on interest + principal (net zero penalty). “Provably” meanstransactionAtis beforedueDateTime, ortransactionDateis a day strictly before it — a same-day value date withouttransactionAtcannot be told apart from a late payment, and the penalty stands. - Partial on-time payments keep the penalty. The penalty is 10% of
the original principal, owed because the loan was still open at
dueDateTime; paying part of it on time does not remove it. - Late payments bear the penalty. If the money moved after
dueDateTime, the payment is genuinely late and the penalty stands (allocation: penalty → interest → principal). - A settling repayment cancels a pending penalty. If the loan is
cleared before
dueDateTime, nothing fires.
The loan products use flat interest fixed at disbursement, so
the value date never changes the interest owed. The value date must
be on/after disbursement and not in the future.

