1. Loans
Core Banking Services
  • Introduction
  • Terminologies
  • Webhooks
  • Authentication
  • Account
    • Product List
      GET
    • Create Account
      POST
    • Update Account
      PUT
    • Find Client
      POST
    • Accounts
      GET
    • Create Additional Account
      POST
    • Account Details By BVN
      GET
    • Get Client Addresses
      GET
    • Get Account Details
      GET
    • Get Account Summary
      GET
    • Client Images
      GET
    • Update BVN
      POST
    • Update Transaction Limit
      POST
    • Client Notifications
      PUT
    • Activate Client Account
      POST
    • Add Client Address
      POST
    • Update Client Address
      PUT
    • Get Full Account Details
      GET
    • Get Full Account Details By NUBAN
      GET
    • Upload Client Passport
      POST
    • Update NIN
      POST
    • Create Client
      POST
    • Get Directors
      GET
    • Create Directors
      POST
    • Update Directors
      PUT
    • Delete Director
      DELETE
    • Get Genders
      GET
    • Get Signatories
      GET
    • Create Signatories
      POST
    • Delete Signatory
      DELETE
    • Get Template
      GET
  • KYC
    • Get KYC Info
    • Update KYC
    • Get KYC Level
    • Auto Verify KYC
    • Get KYC Comparison Details
    • Face Comparison
    • Get KYC Info
    • Get KYC Settings
    • Get KYC Info
    • Create Face Liveness Session
    • Get KYC Log Details
    • Get KYC Logs
    • Set KYC Level
    • Set KYC Log Status
  • Transaction
    • Payment Types
    • Debit Account
    • Credit Account
    • Transaction History
    • Transaction Details [Legacy]
    • Transaction Details
    • Get All Transactions
  • Transfer
    • Interbank Transfer
      • Get Institutions
      • Name Inquiry
      • Interbank Transfer
      • InterBank Transfer Query
      • Transfer Details
      • Bulk Interbank Transfer
    • Intrabank Transfer
      • IntraBank Transfer
      • Intrabank Transfer To Virtual Accounts
      • Query Intrabank Transfer Status
      • Intrabank Transfer To Virtual Accounts
      • Query Intrabank Transfer To Virtual Account Status
  • Account Restrictions
    • Block Debit
    • Unblock Debit
    • Hold Amount
    • Release Amount
    • Block Account
    • Block Account Credits
    • Unblock Account
    • Unblock Account Credits
  • Reporting
    • Report Types
    • Get Report Parameters
    • Create Report
    • Report Details
    • Get Reports
  • Organisation
    • Get Address Types
    • Get Offices
    • Get States
  • Loans
    • Loans Guide
    • Loan Booking
      • Submit Loan Application
      • Update Loan Application
      • Delete Loan Application
      • Approve Loan
      • Assign Loan Officer
      • Reject Loan
    • Loan Management
      • List Customer's Loans
      • Calculate Loan Schedule
      • Retrieve Loan
      • Disburse Loan
      • Disburse Loan to Savings
      • Recover From Guarantors
      • Calculate Loan Schedule with Term Variations
      • Create Loan Term Variations
      • Remove All Loan Term Variations
      • Unassign Loan Officer
      • Undo Loan Approval
      • Undo Loan Disbursal
      • Withdraw Loan
    • Loan Transactions
      • Foreclose Loan
      • Loan Pre-Closure Template
      • Make Loan Recovery Payment
      • Refund Active Loan by Cash
      • Enter Loan Repayment
      • Undo Loan Write-Off
      • Waive Loan Interest
      • Write Off Loan
      • Retrieve Loan Transaction
      • Adjust Loan Transaction
    • Loan Reschedule
      • Create Loan Reschedule Request
      • Retrieve Loan Reschedule Request
      • Approve Loan Reschedule Request
      • Preview Loan Reschedule
      • Reject Loan Reschedule Request
  • Schemas
    • Client
    • Product
    • ClientNotificationsRequest
    • ClientNotificationsResponse
    • ErrorDetail
    • ErrorResponse
    • ValidationErrorResponse
    • LoanCustomer
    • LoanApplicationRequest
    • LoanApproveRequest
    • LoanRejectRequest
    • LoanWithdrawRequest
    • LoanNoteRequest
    • LoanAssignOfficerRequest
    • LoanUnassignOfficerRequest
    • LoanDisburseRequest
    • LoanTransactionRequest
    • LoanAdjustTransactionRequest
    • LoanDateNoteRequest
    • LoanPassthroughRequest
    • LoanRescheduleRequest
    • LoanRescheduleApproveRequest
    • LoanRescheduleRejectRequest
    • LoanCommandResult
    • Loan
    • LoanSummary
    • LoanTransaction
    • LoanRescheduleRequestData
    • LoanSchedule
    • LoanTemplate
  1. Loans

Loans Guide

This guide explains the whole life of a loan, from asking for a price quote to the last repayment, and shows which API call
does each step. You do not need to know anything about banking or about this API before you start.
Part 1 explains the words used everywhere else.
Part 2 explains the rules that every loan call follows.
Part 3 shows how a loan moves from one state to the next.
Part 4 is a worked story: one customer takes one loan, with the request and the response of every step.
Parts 5 to 7 cover linking accounts, loans that go wrong, and changing a loan after it starts.
Parts 8 to 10 are for looking things up: every route in one table, common errors, and things worth knowing.
If you only read one part, read Part 4. For field-by-field detail of every route, use the API reference (see
Where to go next).
All example data is made up. All money is in naira (NGN). Amounts, dates and ids in responses are real shapes, but the numbers
in the schedule are illustrative: your loan product decides the interest rules, so your figures can differ.

1. Loan concepts in plain words#

WordWhat it meansIn the API
CustomerThe person or business that borrows. The bank calls this a client.clientId (a number the bank gave the customer)
Savings accountThe customer's ordinary bank account, which holds their money.identified by its NUBAN, the 10-digit account number: nuban
Loan productA template made by the bank that fixes the rules of a kind of loan: allowed amounts, interest, how often to repay, and so on. Every loan is created from one product.productId (ask the bank which ids you may use)
Loan accountOne actual loan given to one customer. A customer can have many loan accounts.identified by its loan account number: loanAccountNo, for example 000000059
PrincipalThe amount borrowed.principal
InterestThe price of the loan, charged on the amount still owed.interestRatePerPeriod
TermHow long the loan lasts.loanTermFrequency and loanTermFrequencyType
InstallmentOne repayment in the plan, for example "8,926.29 on 4 December".one row of the schedule
Repayment scheduleThe full plan: every installment, its due date, and how much of it is principal and how much is interest.periods in a schedule
Booking (applying)Recording that a customer is asking for a loan. Nothing is paid out yet.POST /core/loans
ApprovalThe bank agreeing to lend.approve
DisbursementThe money actually leaving the bank and reaching the customer.disburse, disburse-to-savings
RepaymentThe customer paying money back.transactions/repayment
ArrearsInstallments that are due but not paid. The loan is then "in arrears" or "overdue".inArrears
OverpaymentThe customer paid more than they owe.status "overpaid"
WaiverThe bank choosing not to charge some interest.waive-interest
Write-offThe bank giving up on collecting the rest and treating it as a loss.write-off
ForeclosureClosing a loan early by settling everything that is owed.foreclosure
RescheduleReplacing the remaining plan with a new one (more time, different rate).loan-reschedules
GuarantorSomeone who promised to pay if the borrower cannot.recover-guarantees
Loan officerThe bank staff member who looks after the loan.toLoanOfficerId
Two numbers are easy to mix up, so keep them apart:
The NUBAN (nuban) belongs to a savings account. It is never the number of a loan.
The loan account number (loanAccountNo) belongs to a loan. It is the only number you use to point at a loan.

2. How the loan APIs work#

2.1 Signing in#

Every call needs an access token. Get one once, then reuse it until it expires.
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600
}
Send the token on every request as Authorization: Bearer <access_token>, with Content-Type: application/json for bodies.
Your client id, client secret and host address come from the bank. A call without a valid token is refused with 401.

2.2 The customer rule#

Every call on an existing loan must also say which customer the loan belongs to, by sending exactly one of:
clientId: the customer's client id, or
nuban: the NUBAN of one of the customer's savings accounts (the customer is the holder of that account).
Where you put it depends on the kind of call:
Kind of callWhere clientId / nuban goes
POST and PUT (they have a body)inside the JSON body
GET and DELETE (no body)in the query string, for example ?nuban=0000000206
The loan must really belong to that customer. If it does not, nothing is changed and you get 403. This protects customers
from a mistyped loan number acting on somebody else's loan. Sending both, or neither, is a 400.

2.3 The one loan identifier: loanAccountNo#

You point at a loan with its loan account number, in the path: /core/loans/000000059/approve. You get the number back
when you book the loan (Part 4, step 3) and you can always list a customer's loans to find it again.
Responses also contain other numbers called id, loanId or resourceId. They are the bank's internal numbers. You never send
them back in a request; only loanAccountNo identifies a loan to you. (The one exception is the transaction id, which names
a repayment or other transaction, and the reschedule request id. These are not loan numbers.)

2.4 Dates and amounts#

Send dates as yyyy-MM-dd, for example 2026-11-04. A date in any other format is a 400.
Where a request has a date and you leave it out, today is used.
Dates in responses come back as [year, month, day], for example [2026, 11, 4].
A date may not be in the future for something that has already happened (approval, payout, repayment), and may not be earlier
than the step it follows (you cannot approve a loan before it was submitted).
Amounts are plain numbers in naira, for example 8926.29.

2.4.1 Fields you do not recognise#

The bodies in this guide list the fields that matter. A few requests (loan updates, term variations, reschedule requests) pass
extra fields through to the bank's loan system. For those, dates use the format yyyy-MM-dd and the language en unless you
send dateFormat and locale yourself.
On the other requests, a field the API does not know is ignored without an error. That means a misspelt optional field, for
example aprovedOnDate, is silently dropped and the default (today) is used. Check your spelling.

2.5 Errors#

When a call fails you get an HTTP status and a JSON body:
{
  "error": "Loan Account Does Not Belong To This Customer",
  "detailedMessage": "Transaction Point Exception: Loan Account Does Not Belong To This Customer",
  "httpStatusCode": 403,
  "errors": []
}
error is the message to show or log.
parameters (on 400 validation errors) lists every field that failed, one message each.
errors lists each failed field when the bank's loan system rejected several things at once.
Each error in the API reference says whether it comes from the API (checked before the bank is asked) or from Core Banking
(the bank's loan system said no).
Part 9 lists the common errors and what to do about them.

2.6 Repeating a call safely#

There is no idempotency key. If a request times out and you send it again, it may happen twice:
repeating book a loan creates a second application;
repeating repayment posts the money twice.
Before you repeat a call that may have worked, look first: list the customer's loans (GET /core/loans?nuban=...) or read the loan
and its transactions (GET /core/loans/{loanAccountNo}?associations=transactions&nuban=...).

2.7 What your API user must be allowed to do#

Each operation also needs your API user to be allowed to do it (for example, to approve loans or to enter repayments). If it is not,
you get 403 with User has no authority to: <PERMISSION>, naming the missing permission. Your merchant account must also have
been set up for loan operations; if not, you get 401 with Access Denied or No Access To Core Banking. In both cases contact
the bank's support.

3. The loan lifecycle#

A loan is always in exactly one status. Every call moves it along, or is refused if the loan is in the wrong status.
image.png
A repayment on a closed or overpaid loan can reopen it as Active when money is still owed.

3.1 Statuses#

The loan's status in a response looks like { "id": 300, "code": "loanStatusType.active", "value": "Active" }.
status.idstatus.valueMeaning
100Submitted and pending approvalBooked, waiting for the bank to decide. Nothing paid out.
200ApprovedThe bank agreed. Not paid out yet.
300ActiveThe money has been paid out and the customer is repaying.
400Withdrawn by clientThe customer withdrew the application. Final.
500RejectedThe bank refused. Final.
600Closed (obligations met)Fully repaid.
601Closed (written off)Written off by the bank.
602Closed (rescheduled)Replaced by a rescheduled loan.
700OverpaidThe customer paid more than they owed.

3.2 Which call works in which status#

CallThe loan must beIt moves the loan to
Update, delete loan applicationPending approval (100), not yet approved(stays, or is removed)
ApprovePending approval (100)Approved (200)
RejectPending approval (100)Rejected (500)
WithdrawPending approval (100)Withdrawn by client (400)
Undo approvalApproved (200)Pending approval (100)
Disburse, disburse to savingsApproved (200)Active (300)
Undo disbursalActive (300)Approved (200)
Repayment, waive interestActive (300)stays Active, or Closed (600) when nothing is left, or Overpaid (700)
Write-offActive (300)Closed, written off (601)
Undo write-offWritten off (601)Active (300)
Recovery paymentWritten off (601)stays written off
Refund by cashActive (300), and a payment must already have been madestays Active
ForeclosureActive (300)Closed (600)
Loan reschedule requestActive (300)stays Active, with a new plan after approval
Term variationsPending approval (100)(stays)
If you call something in the wrong status, the bank's loan system answers with 400 and a message such as "Validation errors exist."
and the reason in errors. Nothing is changed.

4. The running story: Ada's 50,000 loan#

Ada Obi is a customer of the bank. She has a savings account with NUBAN 0000000206, and the bank knows her as client 42.
She wants to borrow 50,000, to repay over 6 months.
Story dates: she applies on 2 November 2026, the bank approves on 3 November, the money is paid out on 4 November,
and her installments fall due on the 4th of each month. In real use you leave dates out when you mean "today".
What you need before you start:
1.
an access token (section 2.1);
2.
the customer, as a clientId or as the nuban of one of their savings accounts, and that account must be active;
3.
a loan product id (productId). There is no call that lists loan products, so ask the bank which product to use. In this
story it is 1;
4.
for repayments and payouts, a payment type id: call GET /core/paymenttypes for the list.
Every example below uses {tpGatewayHostUrl} for the address of the API.

Step 1. Look at the customer's savings account#

{
  "id": 12,
  "clientId": 42,
  "firstname": "Ada",
  "lastname": "Obi",
  "nuban": "0000000206",
  "currentBalance": 12500.00,
  "availableBalance": 12500.00,
  "accountName": "Ada Obi",
  "productName": "Savings Account",
  "productCurrency": "NGN",
  "postNoDebit": false,
  "blockStatus": false
}
What this tells you. The account exists, belongs to client 42, and holds 12,500. Make a note of the balance: after the
loan is paid out in step 7 it will go up by 50,000.

Step 2. Calculate a schedule (a quote)#

Before booking anything, ask what the loan would look like. Nothing is saved, so you can try different amounts and terms.
{
  "nuban": "0000000206",
  "productId": 1,
  "principal": 50000,
  "loanTermFrequency": 6,
  "loanTermFrequencyType": 2,
  "numberOfRepayments": 6,
  "repaymentEvery": 1,
  "repaymentFrequencyType": 2,
  "interestRatePerPeriod": 2,
  "amortizationType": 1,
  "interestType": 0,
  "interestCalculationPeriodType": 1,
  "expectedDisbursementDate": "2026-11-04"
}
What each field means:
FieldMeaningStory value
nuban (or clientId)Who the loan is for (the customer rule, section 2.2).Ada's account
productIdThe loan product.1
principalAmount to borrow.50,000
loanTermFrequency, loanTermFrequencyTypeHow long the loan lasts, and in which unit: 0 days, 1 weeks, 2 months, 3 years.6 months
numberOfRepaymentsHow many installments.6
repaymentEvery, repaymentFrequencyTypeThe gap between installments, and its unit: 0 days, 1 weeks, 2 months.every 1 month
interestRatePerPeriodInterest rate for one period. The product decides what a period is; here it is a month.2
amortizationTypeHow installments are worked out: 0 equal principal payments (installments shrink), 1 equal installments (the same amount every time).1
interestTypeHow interest is worked out: 0 on the balance still owed (declining balance), 1 on the full amount (flat).0
interestCalculationPeriodType0 interest counted daily, 1 counted per repayment period.1
expectedDisbursementDateThe date the money is expected to be paid out.2026-11-04
Optional fields you can add: transactionProcessingStrategyCode (how a payment is shared between principal, interest and charges;
ask the bank which codes it uses), submittedOnDate, externalId (your own reference) and loanType. Other fields the product
accepts, such as charges, are passed through.
{
  "currency": { "code": "NGN", "decimalPlaces": 2 },
  "loanTermInDays": 181,
  "totalPrincipalDisbursed": 50000,
  "totalPrincipalExpected": 50000.00,
  "totalInterestCharged": 3557.75,
  "totalFeeChargesCharged": 0,
  "totalPenaltyChargesCharged": 0,
  "totalRepaymentExpected": 53557.75,
  "totalOutstanding": 53557.75,
  "periods": [
    { "dueDate": [2026, 11, 4], "principalDisbursed": 50000, "feeChargesDue": 0 },
    { "period": 1, "fromDate": [2026, 11, 4], "dueDate": [2026, 12, 4], "principalDue": 7926.29, "principalLoanBalanceOutstanding": 42073.71, "interestDue": 1000.00, "feeChargesDue": 0, "penaltyChargesDue": 0, "totalDueForPeriod": 8926.29 },
    { "period": 2, "fromDate": [2026, 12, 4], "dueDate": [2027, 1, 4], "principalDue": 8084.82, "principalLoanBalanceOutstanding": 33988.89, "interestDue": 841.47, "feeChargesDue": 0, "penaltyChargesDue": 0, "totalDueForPeriod": 8926.29 },
    { "period": 3, "fromDate": [2027, 1, 4], "dueDate": [2027, 2, 4], "principalDue": 8246.51, "principalLoanBalanceOutstanding": 25742.38, "interestDue": 679.78, "feeChargesDue": 0, "penaltyChargesDue": 0, "totalDueForPeriod": 8926.29 },
    { "period": 4, "fromDate": [2027, 2, 4], "dueDate": [2027, 3, 4], "principalDue": 8411.44, "principalLoanBalanceOutstanding": 17330.94, "interestDue": 514.85, "feeChargesDue": 0, "penaltyChargesDue": 0, "totalDueForPeriod": 8926.29 },
    { "period": 5, "fromDate": [2027, 3, 4], "dueDate": [2027, 4, 4], "principalDue": 8579.67, "principalLoanBalanceOutstanding": 8751.27, "interestDue": 346.62, "feeChargesDue": 0, "penaltyChargesDue": 0, "totalDueForPeriod": 8926.29 },
    { "period": 6, "fromDate": [2027, 4, 4], "dueDate": [2027, 5, 4], "principalDue": 8751.27, "principalLoanBalanceOutstanding": 0.00, "interestDue": 175.03, "feeChargesDue": 0, "penaltyChargesDue": 0, "totalDueForPeriod": 8926.30 }
  ]
}
How to read the schedule:
The first row (no period number) is the payout: 50,000 leaves the bank on 4 November.
Each numbered row is one installment. dueDate is when it is due. totalDueForPeriod is what the customer pays.
It is split into principalDue (paying back the borrowed amount) and interestDue (the price). feeChargesDue and
penaltyChargesDue are charges, zero here.
principalLoanBalanceOutstanding is how much of the borrowed amount is still owed after that installment.
Because interest is charged on what is still owed, the interest part falls each month (1,000.00, 841.47, 679.78, ...) and the
principal part grows. The installment stays the same (8,926.29); the last one is 8,926.30 because of rounding.
The totals at the top add it up: the customer pays back 53,557.75 for a 50,000 loan, so the interest is 3,557.75.
InstallmentDuePrincipalInterestTotalPrincipal still owed after
12026-12-047,926.291,000.008,926.2942,073.71
22027-01-048,084.82841.478,926.2933,988.89
32027-02-048,246.51679.788,926.2925,742.38
42027-03-048,411.44514.858,926.2917,330.94
52027-04-048,579.67346.628,926.298,751.27
62027-05-048,751.27175.038,926.300.00
Ada looks at this, and agrees.

Step 3. Book the loan (the application)#

Booking records the application. No money moves.
{
  "nuban": "0000000206",
  "productId": 1,
  "principal": 50000,
  "loanTermFrequency": 6,
  "loanTermFrequencyType": 2,
  "numberOfRepayments": 6,
  "repaymentEvery": 1,
  "repaymentFrequencyType": 2,
  "interestRatePerPeriod": 2,
  "amortizationType": 1,
  "interestType": 0,
  "interestCalculationPeriodType": 1,
  "expectedDisbursementDate": "2026-11-04",
  "submittedOnDate": "2026-11-02",
  "externalId": "LN-2026-0001"
}
It is the same body as the calculation, plus submittedOnDate (the day Ada applies; defaults to today) and, if you wish, your own
externalId. Because the customer is given as nuban, the gateway uses Ada's savings account both as the borrower and as the
account the money can later be paid into. Part 5 explains linking.
{
  "officeId": 1,
  "clientId": 42,
  "loanId": 7,
  "loanAccountNo": "000000059",
  "resourceId": 7
}
What just happened. The loan exists and its number is 000000059. Keep that number: it is how you refer to the loan from
now on. The loan is in status 100, Submitted and pending approval.
If a response ever comes back without loanAccountNo (this is rare and means the bank could not read the new loan straight
away), do not book again: list the customer's loans (step 4) and find the new one.

Step 4. Look at the loan#

Read one loan. The customer rule applies, so add ?nuban= or ?clientId=:
{
  "id": 7,
  "loanAccountNo": "000000059",
  "externalId": "LN-2026-0001",
  "status": { "id": 100, "code": "loanStatusType.submitted.and.pending.approval", "value": "Submitted and pending approval" },
  "clientId": 42,
  "clientName": "Ada Obi",
  "clientAccountNo": "000000042",
  "loanProductId": 1,
  "loanProductName": "Micro Loan",
  "principal": 50000,
  "linkedAccount": { "id": 12, "nuban": "0000000206", "clientId": 42, "clientName": "Ada Obi", "productName": "Savings Account" }
}
Notes on this response:
loanAccountNo is the loan's number; status is where it is in its life.
clientAccountNo is the customer's own number at the bank. It is not a loan number and not a NUBAN.
linkedAccount is the savings account the loan can be paid into, shown by its nuban.
You can ask for more data with associations. For example ?nuban=0000000206&associations=repaymentSchedule,transactions adds the
schedule and every transaction. exclude leaves data out and fields limits which fields come back.
To find loan numbers, list the customer's loans:
[
  {
    "id": 7,
    "loanAccountNo": "000000059",
    "externalId": "LN-2026-0001",
    "productId": 1,
    "productName": "Micro Loan",
    "status": { "id": 100, "code": "loanStatusType.submitted.and.pending.approval", "value": "Submitted and pending approval" },
    "inArrears": false,
    "originalLoan": 50000,
    "loanBalance": 50000
  }
]
You get one entry per loan of that customer, whichever of clientId or nuban you used. The list is not paginated and is empty when the
customer has no loans.

Step 5. If Ada changes her mind (before approval)#

While the loan is still pending approval (100) you can still change it or stop it:
WhatCallNotes
Change the applicationPUT /core/loans/{loanAccountNo}Send the customer and only the fields to change, for example { "nuban": "0000000206", "principal": 60000 }.
Remove it completelyDELETE /core/loans/{loanAccountNo}?nuban=...The application disappears.
The bank refusesPOST /core/loans/{loanAccountNo}/rejectBody { "nuban": "...", "rejectedOnDate": "2026-11-03", "note": "Insufficient income" }. Final: status 500.
Ada withdrawsPOST /core/loans/{loanAccountNo}/withdrawBody { "nuban": "...", "withdrawnOnDate": "2026-11-03", "note": "Applicant withdrew" }. Final: status 400.
The difference between delete, reject and withdraw: delete wipes the application as if it never happened; reject and withdraw
keep it on record with the reason. None of these is possible after the loan is approved.
Ada continues, so we do none of these.

Step 6. Approve the loan#

{
  "nuban": "0000000206",
  "approvedOnDate": "2026-11-03",
  "approvedLoanAmount": 50000,
  "note": "Approved after credit review"
}
approvedOnDate may not be before the day the loan was submitted, nor in the future. It defaults to today.
approvedLoanAmount may be less than the amount asked for, never more. Leave it out to approve the full amount.
expectedDisbursementDate (optional) lets you move the payout date; it may not be before the approval date.
{
  "officeId": 1,
  "clientId": 42,
  "loanId": 7,
  "loanAccountNo": "000000059",
  "resourceId": 7,
  "changes": {
    "status": { "id": 200, "code": "loanStatusType.approved", "value": "Approved" },
    "approvedOnDate": "03 November 2026"
  }
}
What just happened. The status is now 200, Approved. Still no money has moved.
Related calls:
Undo approval: POST /core/loans/{loanAccountNo}/undo-approval with { "nuban": "...", "note": "..." }. Returns the loan to
pending approval. Only possible while the loan is Approved (not yet paid out).
Assign a loan officer: POST /core/loans/{loanAccountNo}/assign-loan-officer with { "nuban": "...", "toLoanOfficerId": 2 }.
fromLoanOfficerId and assignmentDate are optional. Loan officer ids come from the bank; there is no call that lists them.
Unassign: POST /core/loans/{loanAccountNo}/unassign-loan-officer.

Step 7. Pay the money out (disbursement)#

There are two ways to pay out an approved loan.
Option A: disburse to the customer's savings account. The 50,000 is moved into the savings account linked to the loan.
{
  "nuban": "0000000206",
  "actualDisbursementDate": "2026-11-04",
  "transactionAmount": 50000
}
{
  "officeId": 1,
  "clientId": 42,
  "loanId": 7,
  "loanAccountNo": "000000059",
  "resourceId": 30,
  "savingsId": 12,
  "changes": {
    "status": { "id": 300, "code": "loanStatusType.active", "value": "Active" }
  }
}
Now read Ada's account again (step 1): currentBalance is 62,500, which is the 12,500 she had plus the 50,000 loan. The loan
is 300, Active, and the first installment is due on 4 December.
This only works if the loan is linked to a savings account, which happens when you book the loan with a nuban (or with a
clientId and a linkedNuban, see Part 5). Without a link the call is refused.
Option B: disburse (POST /core/loans/{loanAccountNo}/disburse). This records that the loan was paid out in some other way, for
example cash or a cheque, and does not credit the savings account. Use paymentTypeId to say how: get the ids from
GET /core/paymenttypes.
{
  "nuban": "0000000206",
  "actualDisbursementDate": "2026-11-04",
  "transactionAmount": 50000,
  "paymentTypeId": 5,
  "note": "Paid out in cash"
}
Both options take the same optional fields: actualDisbursementDate (defaults to today; not before the approval date and not in the
future), transactionAmount (defaults to the approved amount; it may not exceed it) and the payment details paymentTypeId,
accountNumber, checkNumber, routingCode, receiptNumber, bankNumber and note.
Paid out by mistake? POST /core/loans/{loanAccountNo}/undo-disbursal (with { "nuban": "...", "note": "..." }) reverses the payout
and takes the loan back to Approved. It also reverses the money moved into the savings account and any repayments recorded on
the loan, so use it with care.

Step 8. Repayments#

On 4 December Ada pays her first installment of 8,926.29.
{
  "nuban": "0000000206",
  "transactionDate": "2026-12-04",
  "transactionAmount": 8926.29,
  "paymentTypeId": 1,
  "receiptNumber": "R-1001",
  "note": "Installment 1"
}
{
  "officeId": 1,
  "clientId": 42,
  "loanId": 7,
  "loanAccountNo": "000000059",
  "resourceId": 31,
  "changes": {
    "transactionDate": "04 December 2026",
    "transactionAmount": "8926.29"
  }
}
transactionAmount is required and must be 0.01 or greater. transactionDate defaults to today. It may not be before the loan
was paid out, in the future, or before the loan's last transaction. The id in resourceId (31) is the transaction id.
How a repayment is shared. The bank's loan system splits each payment between principal, interest, fees and penalties, by a
fixed order the product defines. Read it back to see:
{
  "id": 31,
  "loanAccountNo": "000000059",
  "type": { "id": 2, "code": "loanTransactionType.repayment", "value": "Repayment" },
  "date": [2026, 12, 4],
  "amount": 8926.29,
  "principalPortion": 7926.29,
  "interestPortion": 1000.00,
  "feeChargesPortion": 0,
  "penaltyChargesPortion": 0,
  "outstandingLoanBalance": 42073.71
}
7,926.29 went to principal and 1,000.00 to interest, exactly as the schedule planned. outstandingLoanBalance is the principal
still owed afterwards. Ada has now repaid 8,926.29 of the 53,557.75 due.
To see every transaction on the loan at once, read the loan with associations=transactions.
What if the amount does not match?
Less than the installment: the payment is still accepted and applied; the rest of the installment stays due and becomes
overdue after its due date.
More than owed: the extra is an overpayment and the loan becomes 700, Overpaid.
Correcting a mistake: adjust a transaction. Suppose the second repayment (due 4 January) was entered as 8,000 by mistake, and
got transaction id 32. Reverse it and enter the right amount in one call:
{
  "nuban": "0000000206",
  "transactionDate": "2027-01-04",
  "transactionAmount": 8926.29,
  "note": "Corrected amount"
}
The old transaction is reversed and a new one for transactionAmount is booked. Send transactionAmount: 0 to reverse the
transaction and book nothing new.
transactionAmount is required for this call.
Other ways to move money on an active loan:
WhatCallWhen to use it
Waive interestPOST /core/loans/{loanAccountNo}/transactions/waive-interest with transactionAmountThe bank decides not to charge some interest (for example as goodwill). The amount cannot exceed the interest still owed.
Refund by cashPOST /core/loans/{loanAccountNo}/transactions/refund-by-cash with transactionAmountMoney paid in advance is given back to the customer in cash. Only after a payment has been made.

Step 9. Paying the loan off early#

This is one possible ending of the story. (Parts 6 and 7 show other endings: a loan that goes wrong, and a loan whose plan is changed.)
On 4 February 2027 Ada has paid installments 1 and 2 and wants to settle everything on the day installment 3 falls due. First ask
how much that costs:
transactionDate here is in the format named by dateFormat (this one call differs from the usual yyyy-MM-dd), and locale is the
language of that date. All three are optional; today is used if you leave transactionDate out.
{
  "type": { "id": 2, "code": "loanTransactionType.repayment", "value": "Repayment" },
  "date": [2027, 2, 4],
  "amount": 34668.67,
  "principalPortion": 33988.89,
  "interestPortion": 679.78,
  "feeChargesPortion": 0,
  "penaltyChargesPortion": 0
}
This changes nothing: it only tells you that on that date 34,668.67 closes the loan (33,988.89 of principal still owed, plus
679.78 interest). To close it, post that amount as an ordinary repayment:
{
  "nuban": "0000000206",
  "transactionDate": "2027-02-04",
  "transactionAmount": 34668.67,
  "paymentTypeId": 1,
  "note": "Early payoff"
}
Paying installments 3 to 6 as planned would cost 35,705.17 (3 x 8,926.29 + 8,926.30). Settling early costs 34,668.67, so Ada saves
1,036.50: the interest of months 4 to 6 (514.85 + 346.62 + 175.03).
Foreclosure is the bank-side way to do the same thing: POST /core/loans/{loanAccountNo}/transactions/foreclosure with
{ "nuban": "...", "transactionDate": "2027-02-04", "note": "Early settlement" } settles everything that is owed on that date and
closes the loan. It is refused if the date is in the future, before the loan's last transaction, or if the product recalculates
interest on early payments.

Step 10. Closing the loan#

When the last of what is owed is paid, the loan's status becomes 600, Closed (obligations met). It cannot be paid out again.
If more than that was paid, the status is 700, Overpaid. A later repayment or waiver on a closed or overpaid loan can reopen it
to Active when something is owed again.
Check with GET /core/loans/000000059?nuban=0000000206: look at status.

5. Account linking explained#

Three different things are involved in a loan. It helps to keep them apart.
   Customer (clientId 42)
      |
      |-- Savings account (nuban 0000000206)  <------------------- loan is paid out into this account
      |        ^
      |        | linked
      |-- Loan account (loanAccountNo 000000059)
The customer can own several savings accounts and several loans.
A savings account holds the customer's money. You identify it by its nuban.
A loan account is one loan. You identify it by its loanAccountNo.
A loan can be linked to one savings account. The link says "pay this loan out into that account".

5.1 How the link is made#

The link is made when you book the loan (section 4, step 3), in one of two ways:
You sendWhat happens
nubanThe customer is the holder of that savings account, and the loan is linked to it. This is the simplest way.
clientId and linkedNubanThe customer is the client you name, and the loan is linked to the savings account linkedNuban. That account must belong to the same customer, otherwise the request is refused with 403.
clientId onlyThe customer is the client, and the loan has no link. You can still disburse it with disburse, but not with disburse-to-savings.
A schedule calculation (step 2) never links anything: it only needs to know the customer.
Rules for the savings account: it must be active, and it must belong to the same customer as the loan.

5.2 What the link does#

Disburse to savings moves the loan money into the linked savings account (step 7, option A). Without a link it is refused.
The linked account is shown when you read the loan, as linkedAccount with its nuban.

5.3 What the link does not do through this API#

It does not set up automatic repayments from the savings account, and there is no loan call that takes a repayment straight from
a savings account. Record repayments with transactions/repayment.
The bank may, depending on how it has set things up, recover overdue amounts from a customer's savings automatically when money
is deposited. If you see repayments appear that you did not post, ask the bank whether this is switched on.

6. When things go wrong#

6.1 Late payments (arrears)#

If an installment is not paid by its due date the loan is in arrears: inArrears is true in the loan list, and the loan's
summary shows the overdue amount. The loan stays Active. Nothing changes status by itself; arrears are a signal for the bank to
act. The bank may add penalty charges, depending on its product. Your options:
Wait and collect: the customer pays late; record the repayment as usual.
Change the plan: reschedule (Part 7).
Forgive part of it: waive interest (step 8).
Give up: write the loan off (below).
Collect from guarantors: POST /core/loans/{loanAccountNo}/recover-guarantees with { "nuban": "...", "note": "..." }
asks the bank to recover the overdue amount from the loan's guarantors, if the loan has any. Ask the bank whether guarantors are used
for your products.

6.2 Write-off#

Writing a loan off means the bank accepts it will not get the rest back. The loan must be Active. (Imagine a different ending
to the story: Ada stopped paying after a few installments and the bank gave up on collecting.)
{
  "nuban": "0000000206",
  "transactionDate": "2027-06-04",
  "note": "Uncollectable"
}
The status becomes 601, Closed (written off). The date may not be before the loan was paid out, in the future, or before earlier
transactions.
Undo write-off: POST /core/loans/{loanAccountNo}/transactions/undo-write-off with { "nuban": "...", "note": "..." }
returns the loan to Active. Only possible while it is written off.
Recovery payment: if the customer pays something after a write-off, record it with
POST /core/loans/{loanAccountNo}/transactions/recovery-payment and a transactionAmount. It is only accepted on a written-off
loan, and the amount cannot be more than what was written off.

7. Changing a loan: term variations and rescheduling#

Two different tools change the repayment plan. Do not mix them up.
Term variationsReschedule request
ForSmall changes before the loan starts, such as moving one due dateA new plan for a loan that is already running
The loan must bePending approval (100)Active (300)
Needs approvalNo: they are saved straight awayYes: a request is created, then approved or rejected

7.1 Term variations (before the loan starts)#

Variations change individual installments of a loan that has been booked but not yet approved. The loan product must allow variable
installments
, otherwise the call is refused. Always calculate first, check the result, and only then save.
1. Calculate the schedule with the variations applied. Nothing is saved:
{
  "nuban": "0000000206",
  "dateFormat": "yyyy-MM-dd",
  "locale": "en",
  "exceptions": {
    "modifiedinstallments": [
      { "dueDate": "2026-12-04", "modifiedDueDate": "2026-12-14" }
    ]
  }
}
The response is a schedule like the one in step 2, with installment 1 moved to 14 December.
2. Save the same body with POST /core/loans/{loanAccountNo}/schedule/variations. The loan now carries the change.
3. Remove every saved variation with DELETE /core/loans/{loanAccountNo}/schedule/variations?nuban=.... The original schedule
comes back.
exceptions can hold three lists. Each item names the installment by its current due date:
ListMeaningFields of each item
modifiedinstallmentsChange an installment.dueDate, and at least one of modifiedDueDate, principal, installmentAmount
newinstallmentsAdd an installment.dueDate, principal, installmentAmount
deletedinstallmentsRemove an installment.dueDate
installmentAmount is only allowed for loans with a declining balance and equal installments; principal is not allowed for them.
The calculation tells you at once if you combined them wrongly.

7.2 Reschedule requests (a running loan)#

A reschedule replaces the unpaid part of the plan with a new one, for example when the customer cannot keep up. It works in
three steps: ask, look, decide.
Imagine another ending to the story: Ada does not pay off early. After paying installments 1 to 3 she asks for 3 more months.
1. Create the request. The loan is named by loanAccountNo in the body:
{
  "nuban": "0000000206",
  "loanAccountNo": "000000059",
  "rescheduleFromDate": "2027-03-04",
  "submittedOnDate": "2027-02-20",
  "rescheduleReasonId": 1,
  "extraTerms": 3
}
{
  "officeId": 1,
  "clientId": 42,
  "loanId": 7,
  "loanAccountNo": "000000059",
  "resourceId": 5
}
resourceId (5) is the reschedule request id. You use it in the next calls instead of the loan number.
Fields of the request:
FieldMeaning
loanAccountNoThe loan to reschedule (required).
rescheduleFromDateThe due date of the first installment to be replaced (required). It must be the due date of an installment that is not fully paid.
submittedOnDateThe day of the request (required). Not before the loan was paid out.
rescheduleReasonIdWhy (required). Reason ids come from the bank; there is no call that lists them.
rescheduleReasonCommentA comment on the reason (up to 500 characters).
extraTermsNumber of installments to add.
newInterestRateA new interest rate.
graceOnPrincipal, graceOnInterestNumber of installments in which no principal or no interest is due.
adjustedDueDateA new due date for the installment that starts the change; not before rescheduleFromDate.
At least one change must be asked for (extra terms, new rate, a grace, an adjusted due date, ...), and the loan must be Active.
2. Preview. See the plan the request would produce before anyone decides. Nothing changes.
The response is a schedule in the same shape as step 2.
3. Approve or reject.
{ "nuban": "0000000206", "approvedOnDate": "2027-02-21" }
Rejecting is POST /core/loan-reschedules/5/reject with { "nuban": "...", "rejectedOnDate": "2027-02-21" }. Both dates default to today, may
not be before the request was submitted, and the request must still be pending. After approval the loan stays Active with the new plan
from rescheduleFromDate onward. You can read a request at any time with GET /core/loan-reschedules/{requestId}?nuban=....

8. Quick reference: every loan route#

All routes are under {tpGatewayHostUrl}. "Customer" means one of clientId or nuban (section 2.2): in the body on POST and PUT,
in the query on GET and DELETE. {no} is the loanAccountNo.
RouteWhat it doesLoan must beKey fields
POST /core/loans/schedule/calculateQuote: works out a schedule, saves nothing(no loan yet)customer, productId, principal, term and repayment fields, expectedDisbursementDate
POST /core/loansBook a loan application(no loan yet)as above; optional linkedNuban, submittedOnDate, externalId
GET /core/loansList a customer's loansclientId or nuban
GET /core/loans/{no}Read one loananycustomer; associations, exclude, fields
PUT /core/loans/{no}Change an applicationpendingcustomer, fields to change
DELETE /core/loans/{no}Delete an applicationpendingcustomer
POST /core/loans/{no}/approveApprovependingcustomer, approvedOnDate, approvedLoanAmount, expectedDisbursementDate, note
POST /core/loans/{no}/undo-approvalUndo approvalapprovedcustomer, note
POST /core/loans/{no}/assign-loan-officerAssign a loan officercustomer, toLoanOfficerId, fromLoanOfficerId, assignmentDate
POST /core/loans/{no}/unassign-loan-officerRemove the loan officercustomer, unassignedDate
POST /core/loans/{no}/rejectRejectpendingcustomer, rejectedOnDate, note
POST /core/loans/{no}/withdrawRecord that the applicant withdrewpendingcustomer, withdrawnOnDate, note
POST /core/loans/{no}/disbursePay out, without crediting savingsapprovedcustomer, actualDisbursementDate, transactionAmount, payment details
POST /core/loans/{no}/disburse-to-savingsPay out into the linked savings accountapproved and linkedcustomer, actualDisbursementDate, transactionAmount
POST /core/loans/{no}/undo-disbursalReverse a payoutactivecustomer, note
POST /core/loans/{no}/recover-guaranteesRecover overdue amounts from guarantorsactive, overduecustomer, note
POST /core/loans/{no}/transactions/repaymentEnter a repaymentactivecustomer, transactionAmount, transactionDate, payment details
POST /core/loans/{no}/transactions/waive-interestWaive interestactivecustomer, transactionAmount, transactionDate
POST /core/loans/{no}/transactions/write-offWrite the loan offactivecustomer, transactionDate, note
POST /core/loans/{no}/transactions/undo-write-offUndo a write-offwritten offcustomer, note
GET /core/loans/{no}/transactions/prepay-templateHow much closes the loan on a dateactivecustomer; transactionDate, dateFormat, locale
POST /core/loans/{no}/transactions/recovery-paymentPayment received after a write-offwritten offcustomer, transactionAmount
POST /core/loans/{no}/transactions/refund-by-cashRefund money in cashactive, a payment madecustomer, transactionAmount
POST /core/loans/{no}/transactions/foreclosureClose early by settling everythingactivecustomer, transactionDate, note
GET /core/loans/{no}/transactions/{transactionId}Read one transactionanycustomer
POST /core/loans/{no}/transactions/{transactionId}/adjustReverse a transaction and book a replacementcustomer, transactionAmount (0 reverses only), transactionDate
POST /core/loans/{no}/schedule/calculateSchedule with variations, saves nothingpendingcustomer, exceptions
POST /core/loans/{no}/schedule/variationsSave term variationspendingcustomer, exceptions
DELETE /core/loans/{no}/schedule/variationsRemove all variationspendingcustomer
POST /core/loan-reschedulesAsk for a rescheduleactivecustomer, loanAccountNo, rescheduleFromDate, submittedOnDate, rescheduleReasonId, change fields
GET /core/loan-reschedules/{requestId}Read a requestcustomer
GET /core/loan-reschedules/{requestId}/previewPreview the new planrequest pendingcustomer
POST /core/loan-reschedules/{requestId}/approveApprove a requestrequest pendingcustomer, approvedOnDate
POST /core/loan-reschedules/{requestId}/rejectReject a requestrequest pendingcustomer, rejectedOnDate
Related routes you will use alongside:
RouteWhat it does
POST /security/authenticateGet an access token.
GET /core/account/{nuban}Read a savings account (balance, owner).
GET /core/paymenttypesList payment type ids for paymentTypeId.

9. Common errors and what to do#

StatuserrorWhyWhat to do
400The Following Parameters Contain ErrorsA field failed validation. Every failing message is in parameters, such as "Exactly one of clientId or nuban is required" or "transactionAmount is required".Fix the fields named.
400Exactly one of clientId or nuban is requiredOn a GET or DELETE, both or neither of clientId and nuban was in the query.Send exactly one.
400<field> must be in the format yyyy-MM-ddA date is in another format.Use yyyy-MM-dd.
400Validation errors exist.The bank's loan system rejected the request. Each reason is in errors: wrong status, a date out of range, an amount too large, and so on.Read errors, then compare with the status table in section 3.2.
401UnauthorizedThe token is missing, wrong or expired.Get a new token (section 2.1).
401Request Source Not AuthorizedYour IP address is not allowed.Ask the bank to add it.
401Access Denied or No Access To Core BankingYour merchant account is not set up for this operation, or for loans at all.Contact support.
403Loan Account Does Not Belong To This CustomerThe loanAccountNo and the clientId or nuban you sent do not match. When booking, it also means linkedNuban is not an account of the customer.Check both numbers. Nothing was changed.
403User has no authority to: <PERMISSION>Your API user is not allowed to do this operation.Ask the bank to grant the named permission.
404Loan Account Not FoundNo loan has that loanAccountNo.Check the number; list the customer's loans.
404Savings account with external identifier <nuban> does not existThe nuban matches no account.Check the NUBAN.
404Client with identifier <clientId> does not existNo customer has that clientId (customer list).Check the id.
500An unexpected error occurredThe request could not be completed.Try again; if it keeps failing, contact support with the time of the call.
Mistakes that cause trouble:
A loan was booked but the response had no loanAccountNo: list the customer's loans and look for the new one before booking again.
A repayment is refused with "Validation errors exist.": check that the loan is Active, that the date is not in the future,
and that it is not before the last transaction on the loan.
403 on a loan you are sure belongs to the customer: the customer is checked by the id or NUBAN you send; use the NUBAN of one of
that customer's savings accounts.

10. Good to know#

Approval workflows. The bank can require a second person to approve some loan operations. When that is on, the response may
carry a commandId instead of the change: the request was accepted and is waiting for approval, and the loan has not changed
yet. Check the loan's status later.
Where the values come from. Some inputs are fixed by the bank, and there is no call to list them. Ask the bank for:
loan product ids (productId), loan officer ids (toLoanOfficerId), reschedule reason ids (rescheduleReasonId)
and the repayment strategy codes for transactionProcessingStrategyCode. Payment type ids come from GET /core/paymenttypes.
Lists are not paginated. GET /core/loans returns all of a customer's loans in one answer.
Look before you repeat. There is no idempotency key (section 2.6).
Test with the quote first. POST /core/loans/schedule/calculate and the pre-closure and preview calls change nothing, so
they are safe to try as often as you like.

Where to go next#

Field-by-field detail of every route: the API reference, in the folder Core Banking / Loans of the gateway OpenAPI
document (docs/api/gateway-openapi.json), the Apidog project (docs/api/core-banking-services.apidog.json) and the standalone Core
Banking document (docs/api/core-banking-services.openapi.json). Each operation lists its fields and every error it can return,
with an example.
Getting started with the API in general (sign-in, terminology): the Introduction, Terminologies and Authentication pages of the
Apidog project.
Setting up access, product ids, officers and permissions: the bank's support.
Modified at 2026-11-08 06:09:22
Previous
Get States
Next
Submit Loan Application
Built with