Loan Application
Submit a loan after a completed credit assessment, accept terms, set up the repayment mandate, or apply for multiple children at once.
Unlike the Partner API, these endpoints use EdPay’s own schoolId / studentId
GUIDs, not your externalSchoolId / externalStudentId. Your UI typically gets
these from EdPay’s own school/student selection screens (or from a prior lookup)
rather than from your own system’s ids directly.
Submit loan application
POST /api/loansOne-shot submit. Same end state as the internal loan-draft submit.
Documents (product-gated)
Upload files via Utils → Upload file (POST /api/uploads),
then pass the returned S3 key strings:
| Product | Required keys |
|---|---|
| K-12 (partner / booked school) | utilityBill + idCard |
| Exam (ProfessionalBody) | examInvoice, utilityBill, bankStatement, idCard, payslips, not admissionLetter |
| Graduate (Institution) | same as Exam plus admissionLetter |
otherDocuments is always optional.
Headers
| Header | Value |
|---|---|
Accept | application/json |
Authorization | Bearer {parentAccessToken} |
Content-Type | application/json |
Request body
| Field | Description |
|---|---|
schoolId / studentId | EdPay GUIDs for the school and student. |
creditAssessmentId | Optional, from a Completed credit assessment. Omit to submit into manual review. |
loanType | Termly or Session. |
term | e.g. "FirstTerm". |
academicSession | e.g. "2025/2026". |
tenure | OneMonth … SixteenMonths. |
utilityBill / idCard | S3 keys from Upload file, required for K-12. |
requestedAmount | Optional, partial K-12 fees need at least 40% of the catalog amount; institution fees can request up to maxCustomAmount. Omit (null) to request the full catalog amount. |
nextOfKin | name, phone, relationship, occupation. |
Sample below is K-12 (typical partner school):
{
"schoolId": "00000000-0000-0000-0000-000000000010",
"studentId": "00000000-0000-0000-0000-000000000011",
"creditAssessmentId": "{{assessmentId}}",
"loanType": "Termly",
"term": "FirstTerm",
"academicSession": "2025/2026",
"tenure": "SixMonths",
"utilityBill": "{{utilityBillKey}}",
"idCard": "{{idCardKey}}",
"requestedAmount": null,
"nextOfKin": {
"name": "Ngozi Eze",
"phone": "08012345678",
"relationship": "Spouse",
"occupation": "Teacher"
}
}Response 201
{
"success": true,
"message": "Resource created successfully",
"data": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"applicationNumber": "LN-20250901-0001",
"schoolName": "Greenfield Academy",
"studentName": "Ada Eze",
"className": "JSS 2",
"academicSession": "2025/2026",
"loanType": "Termly",
"term": "FirstTerm",
"tuitionAmount": 250000.00,
"discountAmount": 0,
"loanAmount": 250000.00,
"isCustomAmount": false,
"interestRate": 12.5,
"totalRepayableAmount": 281250.00,
"monthlyRepaymentAmount": 46875.00,
"tenure": "SixMonths",
"tenureMonths": 6,
"status": "Submitted",
"termsAccepted": false,
"isMandateSetup": false,
"isDisbursed": false,
"outstandingBalance": 281250.00,
"createdAt": "2025-09-01T10:30:00Z",
"monthlyInterestRatePercent": 2.08,
"totalInterestAmount": 31250.00,
"insuranceFeePercent": 0,
"insuranceFeeAmount": 0,
"firstInstallmentAmount": 46875.00
},
"errors": [],
"statusCode": 201
}| Field | Description |
|---|---|
id | This is {loanId} in every other endpoint on this page. |
applicationNumber | Human-readable reference. |
status | Starts at Submitted, see Key enums for the full list. |
termsAccepted / isMandateSetup / isDisbursed | Flip to true as the parent completes Accept terms and Setup repayment mandate, and once EdPay disburses. |
totalRepayableAmount / monthlyRepaymentAmount / outstandingBalance | The financing breakdown, interest included. |
Try it
Accept terms
POST /api/loans/{loanId}/accept-termsParent accepts the loan’s terms and conditions. Required before
mandate setup when status is Submitted.
Headers
| Header | Value |
|---|---|
Accept | application/json |
Authorization | Bearer {parentAccessToken} |
Content-Type | application/json |
Request body
Empty, this is a confirmation call.
{}Response 200
{
"success": true,
"message": "Request successful",
"data": "Terms and conditions accepted.",
"errors": [],
"statusCode": 200
}Note data here is a plain string, not an object, unlike most other endpoints
on this page.
Try it
Setup repayment mandate
POST /api/loans/{loanId}/setup-mandateInitialize the direct-debit mandate. Body needs parent address (saved on the
profile if empty). Bank account is chosen during Mono authorization, do not
send accountNumber / bankCode.
Response includes authorizationUrl, redirect the parent there, then poll
Verify mandate.
Headers
| Header | Value |
|---|---|
Accept | application/json |
Authorization | Bearer {parentAccessToken} |
Content-Type | application/json |
Request body
| Field | Description |
|---|---|
loanApplicationId | Same as {loanId} in the path. |
address | Parent address, saved on profile if empty. |
{
"loanApplicationId": "{{loanId}}",
"address": "12 Example Street, Ikeja, Lagos"
}Response 200
{
"success": true,
"message": "Request successful",
"data": {
"mandateId": "mono_mandate_abc123",
"authorizationUrl": "https://connect.withmono.com/mandate/authorize/xyz789",
"message": "Mandate initiated. Please complete authorization via the provided URL."
},
"errors": [],
"statusCode": 200
}| Field | Description |
|---|---|
authorizationUrl | Redirect the parent here to complete bank authorization. |
mandateId | Reference for the mandate provider. |
Try it
Verify mandate
GET /api/loans/mandates/verify?reference={loanId}Poll after the Mono mandate redirect (?reference=...&status=...). Confirms
mandate activation on the loan.
Headers
| Header | Value |
|---|---|
Accept | application/json |
Authorization | Bearer {parentAccessToken} |
Query
| Param | Description |
|---|---|
reference | Usually the {loanId} from setup. |
Try it
Submit batch loan (multi-child)
POST /api/loans/batchK-12 multi-child only. Same docs as single K-12 application: utilityBill +
idCard from Upload file, applied to every loan in the batch.
Headers
| Header | Value |
|---|---|
Accept | application/json |
Authorization | Bearer {parentAccessToken} |
Content-Type | application/json |
Request body
| Field | Description |
|---|---|
schoolId | EdPay GUID, same school for every child in the batch. |
creditAssessmentId | From a Completed credit assessment (optional, omit for manual review). |
loanType / term / academicSession / tenure | Same meaning as Submit loan application. |
utilityBill / idCard | S3 keys from Upload file. |
students | Array of { studentId, requestedAmount }. |
nextOfKin | Optional, same shape as Submit loan application, or null. |
{
"schoolId": "00000000-0000-0000-0000-000000000010",
"creditAssessmentId": "{{assessmentId}}",
"loanType": "Termly",
"term": "FirstTerm",
"academicSession": "2025/2026",
"tenure": "SixMonths",
"utilityBill": "{{utilityBillKey}}",
"idCard": "{{idCardKey}}",
"students": [
{ "studentId": "00000000-0000-0000-0000-000000000011", "requestedAmount": null },
{ "studentId": "00000000-0000-0000-0000-000000000012", "requestedAmount": 120000 }
],
"nextOfKin": null
}Response 201
{
"success": true,
"message": "Resource created successfully",
"data": {
"batchId": "b8e3f1a2-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"batchReference": "BATCH-20250901-0001",
"schoolName": "Greenfield Academy",
"totalLoanAmount": 470000.00,
"totalCatalogAmount": 500000.00,
"studentCount": 2,
"loans": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"applicationNumber": "LN-20250901-0001",
"schoolName": "Greenfield Academy",
"studentName": "Ada Eze",
"className": "JSS 2",
"academicSession": "2025/2026",
"loanType": "Termly",
"term": "FirstTerm",
"tuitionAmount": 250000.00,
"discountAmount": 0,
"loanAmount": 250000.00,
"isCustomAmount": false,
"interestRate": 12.5,
"totalRepayableAmount": 281250.00,
"monthlyRepaymentAmount": 46875.00,
"tenure": "SixMonths",
"tenureMonths": 6,
"status": "Submitted",
"termsAccepted": false,
"isMandateSetup": false,
"isDisbursed": false,
"outstandingBalance": 281250.00,
"createdAt": "2025-09-01T10:30:00Z",
"monthlyInterestRatePercent": 2.08,
"totalInterestAmount": 31250.00,
"insuranceFeePercent": 0,
"insuranceFeeAmount": 0,
"firstInstallmentAmount": 46875.00
},
{
"id": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"applicationNumber": "LN-20250901-0002",
"schoolName": "Greenfield Academy",
"studentName": "Chidi Eze",
"className": "Primary 4",
"academicSession": "2025/2026",
"loanType": "Termly",
"term": "FirstTerm",
"tuitionAmount": 250000.00,
"discountAmount": 0,
"loanAmount": 220000.00,
"isCustomAmount": true,
"interestRate": 12.5,
"totalRepayableAmount": 247500.00,
"monthlyRepaymentAmount": 41250.00,
"tenure": "SixMonths",
"tenureMonths": 6,
"status": "Submitted",
"termsAccepted": false,
"isMandateSetup": false,
"isDisbursed": false,
"outstandingBalance": 247500.00,
"createdAt": "2025-09-01T10:30:00Z",
"monthlyInterestRatePercent": 2.08,
"totalInterestAmount": 27500.00,
"insuranceFeePercent": 0,
"insuranceFeeAmount": 0,
"firstInstallmentAmount": 41250.00
}
]
},
"errors": [],
"statusCode": 201
}Each entry in loans is shaped exactly like Submit loan application’s data, isCustomAmount: true here reflects the second child’s explicit requestedAmount.
Try it
Get loan batch
GET /api/loans/batch/{batchId}Detail for a multi-student batch application.
Headers
| Header | Value |
|---|---|
Accept | application/json |
Authorization | Bearer {parentAccessToken} |
Response 200
Same shape as Submit batch loan’s data above.