Full programmatic access to the Sodlapp loan management platform — create borrowers, book loans, post repayments, pull analytics, and automate your entire lending workflow.
const response = await fetch('https://api.sodlapp.com/v2/loans', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_••••••••••••••', 'Content-Type': 'application/json' }, body: JSON.stringify({ customer_id: 'cust_0kp4mB9zR', product_id: 'prod_civil_12m', amount: 500000, tenor_months: 12, disbursement: 'bank_transfer' }) }); const { data } = await response.json(); // → { loan_id: "ln_A8x4Rq7", status: "Approved", // emi: 46250, first_due: "2025-03-01" }
Nine resource domains — each with full CRUD, filtering, pagination, and webhook support built in.
Create loan applications, run eligibility checks, trigger the multi-tier approval workflow, and receive approval/rejection webhooks.
CRUD on customer records, KYC document uploads, BVN/NIN verification status, internal credit score retrieval, and communication logs.
Post single or bulk repayments, retrieve full EMI schedules, query overdue accounts by DPD bucket, and trigger receipt generation.
Query PAR 30/60/90/90+ buckets, flag accounts, apply penalty charges, waive fees with approval trail, and subscribe to overdue webhooks.
Pull portfolio exposure by ministry, branch, or sector. Supports drill-down filtering, league table ordering, and Excel export links.
Programmatic access to dashboards, disbursement vs collection charts, officer scorecards, and scheduled report configurations.
Create/update staff accounts, assign roles & privileges, manage branch assignments, and retrieve officer performance metrics.
Subscribe to events — loan.approved, payment.received, loan.delinquent, kyc.expired — with HMAC-signed delivery and retry logic.
Query the role manifest, manage privilege assignments, pull full tamper-evident audit logs with actor, timestamp, and IP filtering.
All endpoints are prefixed with https://api.sodlapp.com/v2. All responses return {"success": bool, "data": ..., "meta": ...}.
Authorization: Bearer {api_key} header. Sandbox environment: https://sandbox.api.sodlapp.com/v2 — data does not affect production.Creates a new loan record in Draft status for the given customer and loan product. The response includes the computed EMI and the generated repayment schedule dates. Triggers an approval.pending webhook if auto-submit is enabled.
cust_). Borrower must have valid KYC status.max_tenor.bank_transfer | cash | cheque. Defaults to bank_transfer.{
"success": true,
"data": {
"loan_id": "ln_A8x4Rq7mZ",
"status": "Draft",
"amount": 500000,
"emi": 46250,
"first_due": "2025-03-01",
"schedule_url": "https://api.sodlapp.com/v2/loans/ln_A8x4Rq7mZ/schedule"
}
}
Upload a payroll deduction CSV and post multiple repayments in a single batch. The API auto-matches each row to a loan by IPPS number, previews matched/unmatched rows, and returns a reconciliation summary. No payments are posted until confirm: true is passed.
ipps_number, amount, payment_date.false (preview mode). Pass true to commit and post all matched rows.{
"success": true,
"data": {
"rows_total": 142,
"rows_matched": 138,
"rows_unmatched": 4,
"total_amount": 6450000,
"confirmed": false,
"preview_id": "prev_8Kx2mB9z"
}
}
Returns PAR ratios across all DPD buckets for the authenticated institution (or a specific branch). Includes total outstanding, count of delinquent loans, and the PAR percentage for each bucket. Computed in real-time from the current loan book.
YYYY-MM-DD. Compute PAR as of a historical date. Defaults to today.{
"success": true,
"data": {
"as_of": "2025-02-28",
"portfolio_total": 148600000,
"par_30": { "ratio": 0.112, "outstanding": 16643200, "count": 24 },
"par_60": { "ratio": 0.068, "outstanding": 10104800, "count": 14 },
"par_90": { "ratio": 0.031, "outstanding": 4606600, "count": 6 },
"par_90plus":{ "ratio": 0.009, "outstanding": 1337400, "count": 2 }
}
}
Sodlapp uses API key authentication via the standard Authorization: Bearer header. Keys are scoped by environment and permission level.
GET /v2/loans HTTP/1.1 Host: api.sodlapp.com Authorization: Bearer sk_live_4Bk9mNzRxP2qYwVcD7aLtJ0eH Content-Type: application/json Accept: application/json
| Key Type | Prefix | Scope | Permissions |
|---|---|---|---|
| Live Key | sk_live_ | Production | Full read/write on all permitted endpoints |
| Sandbox Key | sk_test_ | Sandbox | Full read/write, isolated test data, no real transactions |
| Read-Only Key | sk_ro_ | Production | GET requests only. No create/update/delete. Safe for analytics pipelines. |
| Webhook Secret | whsec_ | Webhook | Used only for HMAC signature verification of incoming webhook payloads. |
| Plan | Requests / min | Burst limit | Bulk endpoint |
|---|---|---|---|
| Starter | 60 / min | 80 burst | 500 rows/batch |
| Growth | 300 / min | 400 burst | 2,000 rows/batch |
| Enterprise | Custom | Negotiated | Unlimited |
X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. When the limit is exceeded, a 429 Too Many Requests is returned with a Retry-After value in seconds.Subscribe to platform events and receive signed HTTP POST payloads to your endpoint within milliseconds of event occurrence.
{
"event": "loan.approved",
"id": "evt_7Xk2pQ4nM",
"created": "2025-02-28T14:32:11Z",
"livemode": true,
"data": {
"loan_id": "ln_A8x4Rq7mZ",
"customer_id":"cust_0kp4mB9zR",
"amount": 500000,
"status": "Approved",
"approved_by":"team_4Jx9kR"
}
}
const crypto = require('crypto'); function verifyWebhook(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return `sha256=${expected}` === signature; } // In your handler: const sig = req.headers['x-sodlapp-signature']; if (!verifyWebhook(req.rawBody, sig, process.env.WEBHOOK_SECRET)) { return res.status(401).send('Unauthorized'); }
Drop-in libraries for the most popular runtimes. All SDKs are open-source, actively maintained, and automatically generated from the OpenAPI spec.
import { Sodlapp } from '@sodlapp/sdk'; const client = new Sodlapp({ apiKey: process.env.SODLAPP_API_KEY, env: 'live' // or 'sandbox' }); // Book a loan const loan = await client.loans.create({ customer_id: 'cust_0kp4mB9zR', product_id: 'prod_civil_12m', amount: 500_000, tenor_months: 12 }); // Post a repayment await client.repayments.create({ loan_id: loan.data.loan_id, amount: 46_250 }); // Pull PAR analytics const par = await client.analytics.par.get(); console.log(`PAR30: ${(par.data.par_30.ratio * 100).toFixed(1)}%`);
All errors follow a consistent structure with machine-readable error codes and human-readable messages.
{
"success": false,
"error": {
"code": "LOAN_AMOUNT_EXCEEDS_LIMIT",
"message": "Requested amount ₦800,000 exceeds product maximum of ₦600,000",
"field": "amount", // present on validation errors
"status": 422
}
}
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST | Malformed JSON or missing required fields |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 403 | FORBIDDEN | Key lacks permission for this action |
| 404 | NOT_FOUND | Resource with given ID does not exist |
| 409 | CONFLICT | Duplicate resource (e.g. email already exists) |
| 422 | VALIDATION_ERROR | Field-level validation failure — see field in response |
| 429 | RATE_LIMITED | Too many requests. Check Retry-After header. |
| 500 | INTERNAL_ERROR | Server error — automatically logged and alerted to our team |
All breaking changes are announced 90 days in advance. Non-breaking additions are added without versioning.
GET /analytics/par with historical as_of parameter for trend analysisloan.delinquent, kyc.expired, report.readyconfirm: false returns reconciliation summary without postingGET /customers/{id}/credit-score — programmatic access to internal risk scoringGET /loansloan_type field deprecated — use product_id instead. Removed in v3.cust_, ln_, team_)docs.sodlapp.com/migrate-v1-v2.