# API mapping Inspected on 23 Oct 2026, read-only, against the Apache Fineract instance at `https://demo.mifos.community` (build 1.16.0-SNAPSHOT, `develop` @ 52e8b4a). The instance publishes no OpenAPI document (`/fineract-provider/api-docs` returns empty `paths`), so the endpoints below come from the existing Mifos X web app services in `src/app/**/*.service.ts` and were confirmed with live calls. ## Backend basics - **Location in repo:** none. The backend is Apache Fineract, an external service. This repo (`mifos-web-app`) is its Angular 20 front end. - **Base URL (dev):** `{host}/fineract-provider/api/v1`. The dev proxy (`proxy.conf.js`) forwards `/fineract-provider` to `https://demo.mifos.community`; `proxy.localhost.conf.js` targets `https://localhost:8443`. Nothing runs on localhost:8443 on this machine. - **Tenant header (every request):** `Fineract-Platform-TenantId: default` - **Auth (staff):** `POST /authentication` with body `{ "username", "password" }` returns `{ userId, username, base64EncodedAuthenticationKey, officeId, officeName, roles[], permissions[], shouldRenewPassword, isTwoFactorAuthenticationRequired }`. Send `Authorization: Basic ` on every call. This is stateless: no cookie and no server session. A wrong or expired key returns **401**. OAuth2 is optional (`environment.oauth`). - **Auth (borrower, mobile):** `POST /self/authentication` (self-service user linked to a client). It returns the same Basic key, and every borrower call goes under `/self/*`. The endpoint is live: bad credentials return 401. - **User roles:** free-form, defined per tenant (`GET /roles`). Access is enforced by **permissions** (e.g. `APPROVE_LOAN`, `DISBURSE_LOAN`, `REPAYMENT_LOAN`, `ALL_FUNCTIONS`) returned at login. The demo has *Super user*, *Self Service User*, *Client*, *Admin* and test roles. **Officer / Branch manager / Credit manager / Finance do not exist yet.** Route guards should map them to permissions; creating the roles is tenant configuration, not a code change. - **Money format from the API:** JSON numbers in major currency units with decimals (e.g. `16650`, `888.49`), plus a `currency` object `{ code, decimalPlaces, displaySymbol }`. Requests take numbers (or strings parsed with `locale`). **Demo data is in INR/USD/VND, not TZS**, and TZS must be enabled on the tenant (`PUT /currencies`). - **Date format from the API:** responses use arrays `[yyyy, m, d]` (e.g. `[2026,10,23]`). Requests use strings plus `"dateFormat": "dd MMMM yyyy", "locale": "en"` in the body. - **Errors:** `{ httpStatusCode, defaultUserMessage, errors: [{ parameterName, defaultUserMessage, userMessageGlobalisationCode }] }` - **Loan lifecycle (native):** submit (100 pending approval) → `approve` (200) → `disburse` (300 active) → repayments → closed (600). Only one approval step; there is no multi-stage chain. ## Endpoint map Status: ✅ found · 🟡 partial (needs derivation or config; gap noted) · ❌ missing → mock + `MISSING_ENDPOINTS.md` | Screen | UI data / action | Method + endpoint | Response fields used | Status | |---|---|---|---|---| | Dashboard | Logged-in officer, branch | `POST /authentication` (stored session) | `username`, `officeName`, `roles`, `permissions` | ✅ (full name needs `GET /users/{userId}` → `firstname`, `lastname`) | | Dashboard | KPI: portfolio outstanding | `GET /runreports/Active Loans - Summary?R_officeId=&R_currencyId=-1&…&genericResultSet=false` | outstanding principal, loan count | 🟡 value only; **change vs last month missing** | | Dashboard | KPI: active borrowers, new this month | `GET /runreports/Active Loan Clients`, `GET /runreports/ClientTrendsByMonth?R_officeId=` | row count, `count` per month | 🟡 | | Dashboard | KPI: on-time repayment % | — | — | ❌ | | Dashboard | KPI: PAR 30 % + loans to watch | `GET /runreports/Portfolio at Risk?R_parType=1&…` | `Portfolio at Risk %`, overdue principal | 🟡 PAR "type" ≠ fixed 30 days; loans-to-watch count missing | | Dashboard | Collections vs expected, 6 months | `GET /runreports/Demand Vs Collection?R_officeId=` | `AmountDue`, `AmountPaid` (single total, today only) | ❌ no monthly series | | Dashboard | Repayments by channel | — (loan transactions carry `paymentDetailData.paymentType`, but there is no aggregate) | — | ❌ | | Dashboard | 5 newest applications | `GET /loans?orderBy=submittedOnDate&sortOrder=DESC&limit=5` | `id`, `clientName`, `loanProductName`, `principal`, `status`, `timeline.submittedOnDate` | 🟡 **credit score**, business, location, "Needs guarantor" missing | | Dashboard | Due today (+ overdue count) | `GET /runreports/Expected Payments By Date - Basic?R_officeId=&R_loanOfficerId=-1&R_startDate=&R_endDate=&genericResultSet=false` | `Name`, `Loan Account Number`, `Installment`, `Total Due`, `Total Overdue`, `Due Date` | ✅ ("week X of Y" = `Installment` / `numberOfRepayments` from loan) | | Dashboard | Send SMS reminders | `POST /smscampaigns` (+ `/smscampaigns/{id}?command=activate`) with report trigger "Loan payments due" | campaign id | 🟡 needs an external SMS gateway configured on the tenant; demo has none | | Dashboard | Search name/phone/loan ID | `GET /search?query=&resource=clients,loans&exactMatch=false` | `entityId`, `entityType`, `entityName`, `entityAccountNo`, `entityMobileNo` | ✅ | | Borrower | Profile header | `GET /clients/{id}` | `displayName`, `mobileNo`, `officeName`, `activationDate`, `externalId` | ✅ occupation/area via `GET /clients/{id}/addresses` or a datatable | | Borrower | Verification chips | `GET /clients/{id}/identifiers` (NIDA/licence = identifier types) | `documentType.name`, `status` | 🟡 "M-Pesa linked" missing | | Borrower | Credit score, risk band, factors, eligible limit | — (`/creditBureauIntegration` exists only for the ThitsaWorks bureau plugin) | — | ❌ | | Borrower | Active loan + schedule | `GET /clients/{id}/accounts` → `GET /loans/{loanId}?associations=repaymentSchedule,transactions` | `accountNo`, `status`, `principal`, `annualInterestRate`/`interestRatePerPeriod`, `summary.*`, `repaymentSchedule.periods[]` (`dueDate`, `totalDueForPeriod`, `complete`, `obligationsMetOnDate`, `totalPaidLateForPeriod`) | ✅ | | Borrower | Payment history | same loan call → `transactions[]` where `type.repayment` | `date`, `amount`, `paymentDetailData.paymentType.name`, `paymentDetailData.receiptNumber` | 🟡 "posted by" needs `GET /audits?loanId=` (`maker`) | | Borrower | Call | `mobileNo` → `tel:` | — | ✅ | | Borrower | Statement PDF | `GET /runreports/{name}?output-type=PDF` needs the Pentaho plugin; no statement report on demo | — | ❌ | | Application | Customer summary | `GET /clients/{id}` | as above | ✅ | | Application | Loan products | `GET /loanproducts`, `GET /loans/template?templateType=individual&clientId=&productId=` | `name`, `description`, `interestRatePerPeriod`, `min/maxPrincipal`, `min/maxNumberOfRepayments`, `repaymentFrequencyType`, `currency` | ✅ | | Application | Repayment calculator / schedule | `POST /loans?command=calculateLoanSchedule` (no persistence) | `totalPrincipalExpected`, `totalInterestCharged`, `totalFeeChargesCharged`, `totalRepaymentExpected`, `periods[1].totalDueForPeriod`, `periods[1].dueDate` | ✅ verified: 100,000 × 6 months → interest 1,500, total 101,500 | | Application | Income / affordability | — | — | ❌ (verified income not in core; could be a client datatable if one exists) | | Application | Upload document | `POST /clients/{id}/documents` or `/loans/{id}/documents` (multipart: `name`, `file`) | `resourceId` | ✅ | | Application | Guarantor + collateral | `POST /loans/{id}/guarantors`, `GET /loans/{id}/guarantors`, `/loans/{id}/collaterals` | `id`, `status` | ✅ | | Application | Guarantor consent SMS | — | — | ❌ | | Application | Checklist statuses (Verified/Awaiting/Missing) | — | — | ❌ | | Application | Save draft | — (Fineract has no draft state; submit creates status 100) | — | ❌ | | Application | Submit for approval | `POST /loans` (body = calculator body + `clientId`, `productId`) | `loanId`, `resourceId` | ✅ | | Approval | Queue | `GET /loans?status=100&limit=&orderBy=submittedOnDate` (verified: `status` filters; `loanStatus`/`sqlSearch` are ignored) | `id`, `clientName`, `loanProductName`, `principal`, `timeline.submittedOnDate` | ✅ "flagged" missing | | Approval | Header + detail | `GET /loans/{id}?associations=all` | `accountNo`, `loanProductName`, `clientName`, `principal`, `status` | ✅ | | Approval | Approval chain (4 stages, who/when) | Native: one approve step (`timeline.approvedByUsername`, `approvedOnDate`). Maker-checker (`/makercheckers`) is per permission, not staged | — | ❌ multi-stage chain | | Approval | Risk checks | guarantor → `/loans/{id}/guarantors`; duplicate → `/clients/{id}/accounts`; NIDA → `/clients/{id}/identifiers` | — | 🟡 bureau, score, affordability ❌ | | Approval | Mobile money cash flow, 12 weeks | — | — | ❌ | | Approval | Officer's note | `GET/POST /loans/{id}/notes` | `note`, `createdByUsername`, `createdOn` | ✅ | | Approval | Approve | `POST /loans/{id}?command=approve` `{ approvedOnDate, approvedLoanAmount, note, dateFormat, locale }` | `changes.status` | ✅ | | Approval | Decline | `POST /loans/{id}?command=reject` `{ rejectedOnDate, note, … }` (shows in `/audits`) | | ✅ | | Approval | Request more info | — (only a note via `/loans/{id}/notes`) | — | 🟡 no status change | | Approval | Disburse (finance) | `POST /loans/{id}?command=disburse` `{ actualDisbursementDate, transactionAmount, paymentTypeId, … }` | | ✅ | | Reports | Arrears ageing buckets | `GET /runreports/Aging Summary (Arrears in Months)?R_officeId=&R_currencyId=-1` | `Days In Arrears`, `No Of Loans`, `Principal Overdue`, … | 🟡 buckets are monthly, not 1–30/31–60/61–90/90+; provision % missing | | Reports | PAR 30 by branch | `GET /runreports/Portfolio at Risk by Branch?R_parType=1&…` | `Office/Branch`, `Portfolio at Risk %` | 🟡 `%` is null on demo | | Reports | Gross portfolio, disbursed, collected, write-offs | `Active Loans - Summary`, `Funds Disbursed Between Dates Summary by Office`, `Demand Vs Collection`, `Written-Off Loans` | | 🟡 | | Reports | Loan officer table | `GET /staff?isLoanOfficer=true` + `GET /runreports/FieldAgentStats` | | 🟡 collection rate missing | | Reports | Excel / BoT export | `GET /runreports/{name}?exportCSV=true` (CSV only; PDF/XLS need Pentaho) | file | 🟡 CSV ✅, BoT return ❌ | | Mobile | Borrower home | `GET /self/clients`, `GET /self/clients/{id}/accounts`, `GET /self/loans/{id}?associations=repaymentSchedule,transactions` | balance, `numberOfRepayments`, paid periods, next `dueDate`/`totalDueForPeriod` | ✅ (needs a self-service user per borrower) | | Mobile | Credit score banner | — | — | ❌ | | Mobile | Mobile money push (STK) + status | — Fineract has no M-Pesa/Airtel/Mixx integration. The repayment is recorded afterwards with `POST /loans/{id}/transactions?command=repayment` (staff key, `paymentTypeId`, `receiptNumber`) | — | ❌ | | Mobile | Loan request | `GET /self/loanproducts`, `POST /self/loans?command=calculateLoanSchedule`, `POST /self/loans` | as staff calculator | ✅ | | Mobile | Instant decision | — every request goes to "pending approval" | — | ❌ → always show "Ombi limepokelewa" | ## Summary - **Found (core flow works end to end):** login, search, borrower and loan detail, schedule, products, **schedule calculator**, submit / approve / reject / disburse, repayment posting, notes, documents, guarantors, due today, self-service login, loan request. - **Missing (will be mocked):** credit score and factors, mobile money STK push and status, multi-stage approval chain, cash flow, credit bureau, affordability/income, drafts, consent SMS, dashboard trends (6-month collections, channel split, month-over-month changes), statement PDF, BoT return, provision %. - **Configuration, not code:** TZS currency, roles (officer / branch manager / credit manager / finance), an SMS gateway, self-service users, payment types named M-Pesa / Airtel Money / Mixx by Yas / Cash.