The MifosSave API surface
Every endpoint MifosSave calls — reconciled from all 32 screens and generated via
/mifos-bridge. Layered over Mifos X 1.8.x (Fineract) and orchestrated by the MifosSave companion BFF.
Try it live in the API Explorer →
Backend
Mifos X — the open-source core banking platform. Every client, group, savings account, loan, and transaction is a real Mifos X record with a full audit trail.
Base URLs
Active (mifos-bank-2):
https://mifos-bank-2.mifos.community/fineract-provider/api/v1
tenant header: mifos-bank-2
Sandbox:
https://sandbox.mifos.community/fineract-provider/api/v1
tenant header: default
Companion BFF:
https://mifossave-companion.onrender.com/companion
Auth
HTTP Basic + bearer token. Per-call user credentials; the companion tier performs group orchestration with a service credential.
m_group, keyed by
groupId) — there is no Center entity in the per-group unit (SoT:
idea-layer/server/ENTITY_MODEL.yaml). Where Fineract exposes no native API at the organizer's self-service
access level, the capability is modeled as a companion-served datatable — e.g. the meeting schedule is
dt_meeting_schedule on m_group (AL-RULE; SoT: server/ACCESS_MODEL.yaml)./mifos-bridge derives the contract from each screen's
api.yaml across all 32 screens, materializes any missing apis/*.yaml group file, and emits
runnable register migrations for all 21 datatables (server-layer/migrations/). As features grow,
re-running /mifos-bridge re-derives this surface — the site tracks it via
SITE_DEPLOY.yaml#content_sources.The companion backend (in active development)
MifosSave talks only to a companion API — an extended mcp-mifosx server that executes
Mifos X calls with a service credential on behalf of the user. It exposes 101 MCP tools for full programmatic
access. This is where /mifos-bridge materializes the server side of every screen contract.
🔧 Repository & PR
Companion facade (Go) + Mifos X datatable & domain MCP tools (Python).
🏦 Live instances
All development + device-verification runs against a real Mifos X instance:
mifos-bank-2)default)join-with-code and meeting-conduct → meeting-summary were exercised live on mifos-bank-2 (companion invitations datatable + groups/savings/loan posts).
| Companion component | Language | Responsibility |
|---|---|---|
| Group-banking companion facade | Go | Per-call user credential intake; organizer-vs-member authz; group orchestration with a service credential |
| Datatable & domain MCP tools | Python | Register + CRUD the 21 datatables; VSLA/ROSCA domain ops (share-out, rotation, auction) |
| MCP tool surface | — | 101 tools — full programmatic access, one mcp_tool per endpoint below |
6 live demo accounts — try it now
Six accounts are seeded on both the mifos-bank-2 active instance and the sandbox — one per user
type. Credentials are identical on both. Authenticate via POST /self/authentication (end-user login,
phone as username) or the companion BFF wrapper — both return a bearer token for subsequent calls. Use the
API Explorer to run requests interactively.
| Name | Login (phone) | Password | Role / Persona |
|---|---|---|---|
| Amina Otieno | +254700000001 | DemoExplore@2026 | Organizer + Treasurer — primary VSLA showcase; Demo Explore logs in as this user |
| Joseph Mwangi | +254700000002 | DemoChair@2026 | Chairperson — approves loans, chairs meetings |
| Grace Wanjiru | +254700000003 | DemoMember@2026 | Member — savings + active disbursed loan |
| Faith Njeri | +254700000004 | DemoOrganizer@2026 | Organizer — founds & runs the group |
| Peter Kamau | +254700000005 | DemoSecretary@2026 | Secretary — meeting minutes & attendance |
| David Ochieng | +254700000010 | DemoField@2026 | MFI field officer — cross-group oversight |
POST /self/authentication end-user login
authentication.yaml · self_authenticate · use phone number as usernamecurl -X POST \
https://mifos-bank-2.mifos.community/fineract-provider/api/v1/self/authentication \
-H "Content-Type: application/json" \
-H "fineract-platform-tenantid: mifos-bank-2" \
-u "mifos:password" \
-d '{"username":"+254700000001","password":"DemoExplore@2026"}'
# → { "clientId": 12, "authenticated": true, "token": "…" }
# Use token as: Authorization: Basic base64(username:token)
POST C /companion/auth/login companion
companion BFF · unified login wrapper · returns sessionToken + groups + resolved rolescurl -X POST \
https://mifossave-companion.onrender.com/companion/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "+254700000001",
"password": "DemoExplore@2026",
"tenant": "mifos-bank-2"
}'
# → { "sessionToken": "…", "clientId": 12,
# "groups": [ { "groupId": 1, "role": "treasurer" } ] }
mifos-bank-2 (tenant mifos-bank-2)
is the primary instance for live device testing; the sandbox (tenant default) is the fallback. All 6
accounts hold roles in the seeded Mwangaza Women's Group (VSLA archetype). No account setup needed — sign
in directly or tap Demo Explore in the app for the offline-seeded guest session.10 API surfaces
One group per materialized apis/*.yaml. Endpoint counts are the mifos-bridge manifest totals; representative operations shown.
🔐 Authentication 3 ops
apis/authentication.yaml · login-signup, join-with-codeUnified self-signup — a single login; the per-group role resolves after auth.
🏘️ Groups 8 ops
apis/groups-centers.yaml · group-list, group-dashboard, group-create, meeting-conductGroup create, group config + corpus datatables, group detail + roles. Reads go through the companion BFF; raw /groups/* is the write path.
🗓️ Meetings 5 ops
apis/meetings.yaml · meeting-calendar, meeting-conduct, meeting-summary, previous-meeting-reviewSchedule read, attendance, meeting record, and cycle advance — the meeting engine.
💳 Loans 9 ops
apis/loans.yaml · loan-list, loan-apply, loan-detail, meeting-conductApply, group-vote, approve, disburse, reject, list, and repay.
👥 Clients & members 9 ops
apis/clients-members.yaml · member-list, member-profile, member-addMember (client) create, group association, role datatable, photo, and accounts.
💰 Savings 7 ops
apis/savings.yaml · savings-dashboard, meeting-conduct, personal-savings, member-savings-detailGroup-linked accounts, deposits, and balances. Summaries via companion BFF; raw savings are the write path.
🪙 Share-out 3 ops
apis/share-out.yaml · share-out-preview, share-out-executeCycle share-out / rotation preview then execute.
🙋 End-user self-service 4 ops
apis/self-service.yaml · personal-dashboard, personal-savings, personal-loans, loan-requestMember-scoped reads over their own accounts + loan requests.
🔄 Offline sync 1 op
apis/offline-sync.yaml · sync-statusOne batched POST replays the durable offline queue in order (Fineract Batch API).
🧑💼 Field officer 2 ops · v1.1
apis/field-officer.yaml · field-officer-dashboardSupervisory reads + KPI report across an officer's assigned groups.
MifosSave orchestration tools
A companion layer above Mifos X handles per-call user credentials, group orchestration, and the
datatable-backed reads VSLA/ROSCA logic needs. Its five tool groups expose 101 MCP tools that execute the whole
surface; 6 endpoints are first-class companion-BFF reads/writes (the /companion/* paths).
| Tool group | Operations | Backing |
|---|---|---|
COMP-AUTH | self-register · login → groups+roles · me | go/tools/companion_auth.go |
COMP-GRP | create · activate · associate-clients · assign-role · assign-staff | go/tools/companion_groups.go |
COMP-CAL | schedule read · attendance · meeting record | go/tools/companion_calendar.go |
COMP-DT | datatable register · create · read · update · delete rows | go/tools/datatables.go |
COMP-DIST | share-out / rotation execute · ROSCA rotation next | client computes, server re-validates + executes |
Every endpoint, mapped
The complete signature set derived by /mifos-bridge from every screen's api.yaml —
all 51 endpoints, each with method, path, the companion mcp_tool that executes it, and the app
screen it serves. Request/response bodies are in the per-group specs (idea-layer/server/apis/*.yaml);
paths are relative to the base URL. C marks a companion-BFF (/companion/*) endpoint.
| Group | Method & path | MCP tool | Used by screen |
|---|---|---|---|
| authentication 3 | POST /authentication | authenticate | login (admin) |
POST /self/authentication | self_authenticate | login (end_user) | |
GET /datatables/dt_member_role/{clientId} | get_member_role | login (post-auth role resolution) | |
| groups-centers 8 | GET /groups?staffId={staffId}&paged=true&limit=50 | list_all_groups | group-list |
GET /groups/{groupId}?associations=clientMembers,groupRoles | get_group | group-dashboard | |
GET /groups/{groupId}/accounts | list_accounts | group-dashboard | |
GET /datatables/dt_group_corpus/{groupId} | get_group_corpus | group-dashboard | |
GET /datatables/dt_group_config/{groupId} | get_group_config | group-dashboard, group-create | |
POST /groups | create_group | group-create | |
POST /datatables/dt_group_config/{groupId} | upsert_group_config | group-create | |
PUT /datatables/dt_group_corpus/{groupId} | update_group_corpus | meeting-conduct | |
| meetings 5 | GET /datatables/dt_meeting_schedule/{groupId} | get_meeting_schedule | meeting-calendar |
POST /datatables/dt_meeting_record/{groupId} | record_meeting | meeting-conduct (Step 7 close) | |
POST /datatables/dt_meeting_attendance/{clientId} | record_attendance | meeting-conduct (Step 1) | |
GET /datatables/dt_meeting_record/{groupId}?meetingNumber={n-1} | list_meetings | previous-meeting-review | |
PUT /datatables/dt_group_config/{groupId} | advance_cycle | meeting-summary (end of cycle) | |
| loans 9 | GET /groups/{groupId}/loans?associations=repaymentSchedule,guarantors | list_group_loans | loan-list |
GET /loans?groupId={groupId}&loanStatus={status}&limit={limit}&offset={offset} | list_loans | loan-list, meeting-conduct (Step 4) | |
GET /loans/template?activeOnly=true&clientId={clientId}&groupId={groupId} | get_loan_app_template | loan-apply | |
POST /loans | create_new_loan | loan-apply | |
POST /loans/{loanId}?command=approve | approve_loan | meeting-conduct (Step 5) | |
POST /loans/{loanId}?command=disburse | disburse_loan | meeting-conduct (Step 5) | |
POST /loans/{loanId}?command=reject | reject_loan | meeting-conduct (Step 5) | |
POST /datatables/dt_loan_vote/{loanId} | record_loan_vote | meeting-conduct (Step 5) | |
POST /loans/{loanId}/transactions?command=repayment | make_repayment | meeting-conduct (Step 3) | |
| offline-sync 1 | POST /batches | send_batch | sync-status |
| end-user self-service 4 | GET /self/clients/{clientId} | get_self_client | personal-dashboard |
GET /self/savingsaccounts | list_self_savings | personal-savings | |
GET /self/loans | list_self_loans | personal-loans | |
POST /datatables/dt_loan_request/{clientId} | submit_loan_request | loan-request | |
| clients-members 9 | GET /groups/{groupId}/clients?limit&offset | list_all_groups | member-list |
GET /clients/{clientId} | get_client | member-profile | |
GET /clients/{clientId}/accounts | get_client_accts | member-profile | |
GET /datatables/dt_member_role/{clientId} | get_member_role | member-profile | |
PUT /datatables/dt_member_role/{clientId} | assign_member_role | member-profile (chair-only) | |
POST /clients | create_new_client | member-add | |
POST /datatables/dt_member_role/{clientId} | assign_member_role | member-add | |
POST /groups/{groupId}?command=associateClients | add_member_to_group | member-add | |
POST /clients/{clientId}/images | create_datatable_entry | member-add (photo) | |
| savings 7 | GET C /companion/groups/{groupId}/savings | companion_get_group_savings_summary | savings-dashboard (Group) |
GET C /companion/groups/{groupId}/savings/individual | companion_get_individual_savings_summary | savings-dashboard (Individual) | |
GET C /companion/groups/{groupId}/members/{memberId}/savings | companion_get_member_savings_detail | member-savings-detail | |
POST /savingsaccounts | create_savings | member-add, group-create | |
POST /savingsaccounts/{accountId}?command=approve | approve_activate_savings | member-add | |
POST /savingsaccounts/{savingsId}/transactions?command=deposit | deposit | meeting-conduct (Step 2) | |
GET /savingsaccounts/{savingsId}?associations=transactions | get_savings_txns | savings-dashboard | |
| share-out 3 | GET C /companion/groups/{groupId}/shareout/preview | companion_get_shareout_preview | share-out-preview |
POST C /companion/groups/{groupId}/shareout/execute | companion_execute_shareout | share-out-execute | |
POST C /companion/groups/{groupId}/rotation/execute | companion_execute_rotation_payout | share-out-execute | |
| field-officer v1.1 2 | GET /groups?staffId={staffId}&paged=true&limit&offset | list_all_groups | field-officer-dashboard |
GET /runreports/FieldOfficerGroupReport?R_staffId={staffId}&output-type=CSV | run_fineract_report | field-officer-dashboard |
request_body and
response in idea-layer/server/apis/{group}.yaml — e.g. POST /authentication →
{ username, password } → { userId, username, authenticated, officeId, token }. Hand these
specs to the backend engineer as the contract; the companion (mcp-mifosx#417) implements them.JSON contracts
Typed request and response bodies for the core write endpoints — the exact JSON the companion
exchanges with Mifos X. Full schemas for all endpoints live in idea-layer/server/apis/*.yaml.
POST /authentication
authentication.yaml · authenticaterequest { "username": "string", "password": "string" }
response { "userId": Long, "username": "string",
"authenticated": boolean, "officeId": Long,
"token": "string" }POST /self/authentication
authentication.yaml · self_authenticaterequest { "username": "string", "password": "string" }
response { "clientId": Long, "authenticated": boolean,
"selfServiceUserDetails": object, "token": "string" }POST /clients
clients-members.yaml · create_new_clientrequest { "firstname": String, "lastname": String,
"mobileNo": String, "active": Boolean,
"activationDate": String, "officeId": Long,
"groupId": Long, "locale": String,
"dateFormat": String }
response { "resourceId": Long, "clientId": Long }POST /groups
groups-centers.yaml · create_grouprequest { "name": string, "officeId": Long,
"staffId": Long, "active": boolean,
"activationDate": date }
response { "resourceId": Long, "fineractGroupId": Long }POST /savingsaccounts/{id}/transactions?command=deposit
savings.yaml · depositrequest { "transactionDate": String,
"transactionAmount": Long,
"paymentTypeId": Int, // 1 = Cash
"locale": String, "dateFormat": String }GET /datatables/dt_member_role/{clientId}
authentication.yaml · get_member_roleresponse { "role": string, // treasurer | chairperson
// | field_officer | member
"client_type": string, // admin | end_user
"group_id": Long, // the (member, group) key
"joined_date": date }How the tables connect
Each of the 21 datatables attaches to a native Mifos X entity via that entity's primary key — the
apptableId in the path is the foreign key. Below: the Fineract entity backbone, then every
datatable grouped under the parent it hangs off, with its cardinality (1:1 single-row · 1:N
multi-row).
dt_group_config1:1dt_group_corpus1:1dt_meeting_record1:Ndt_meeting_schedule1:Ndt_share_out1:Ndt_social_fund1:1dt_sync_metadata1:1dt_group_loan_policy1:1dt_group_type_config1:1dt_companion_invitations1:Ndt_rosca_rotation1:Ndt_rosca_auction1:Ndt_vsla_cycle1:1dt_welfare_fund1:1dt_member_role1:Ndt_meeting_attendance1:Ndt_loan_request1:Ndt_member_ceiling_override1:1dt_notification1:Ndt_loan_vote1:1dt_loan_guarantor1:N🔗 The join in practice
GET /datatables/dt_group_corpus/{groupId}
→ the corpus row(s) for that group
GET /datatables/dt_member_role/{clientId}
→ the role row(s) for that client
The datatable name + parent apptableId in the path is the foreign-key lookup — Fineract resolves the join server-side. No separate join call.
⚙️ One write, many connected tables
A full meeting-conduct close writes across parents in one flow, each keyed off the same group/client/loan ids:
dt_meeting_attendance (per m_client)
→ savings deposit (m_savings_account)
→ loan approve/disburse (m_loan) + dt_loan_vote
→ dt_group_corpus update (m_group)
→ dt_meeting_record close (m_group)
21 Mifos X datatables — full schema
Where Mifos X has no native table for VSLA/ROSCA state, MifosSave provisions a Mifos X
datatable — each with a runnable register migration (server-layer/migrations/datatables/),
run once after deploy. 15 Fineract-native domain tables + 6 companion tables, re-parented per
server/DATATABLE_REGISTRY.yaml: m_group (14) · m_client (5) · m_loan (2).
Every column below is verbatim from the register manifest.
| Datatable | Parent · entity | Rows | Feature | Columns |
|---|---|---|---|---|
dt_group_config | m_group · GROUP | 1:1 | group-management | cycle_number, cycle_length_weeks, contribution_amount, contribution_min, contribution_max, loan_multiplier, interest_rate, fine_amount, meeting_frequency, cycle_start_date, cycle_end_date |
dt_group_corpus | m_group · GROUP | 1:1 | corpus-tracking | meeting_number, opening_balance, total_inflows, total_outflows, closing_balance, corpus_balance, cash_on_hand, last_updated_meeting, last_updated_date |
dt_meeting_record | m_group · GROUP | 1:N | meeting-lifecycle | meeting_number, meeting_date, opening_balance, closing_balance, total_inflows, total_outflows, total_savings_collected, total_repayments_received, total_loans_disbursed, total_fines_collected, attendance_count, notes |
dt_meeting_schedule companion | m_group · GROUP | 1:N | meeting-lifecycle | meeting_number, scheduled_date, cadence, status — AL-RULE companion-served |
dt_share_out | m_group · GROUP | 1:N | share-out | cycle_number, total_pool, profit_earned, status, executed_date |
dt_social_fund | m_group · GROUP | 1:1 | social-fund | current_balance, total_inflows, total_disbursed, last_updated |
dt_sync_metadata | m_group · GROUP | 1:1 | offline-sync | last_sync_at, pending_count, conflict_count, sync_version |
dt_group_loan_policy | m_group · GROUP | 1:1 | loan-ceilings | max_loan_multiplier, max_loan_amount, min_savings_ratio, max_term_weeks, interest_rate, requires_guarantor |
dt_group_type_config companion | m_group · GROUP | 1:1 | group-type-config | slug, display_name, pool_model, payout_order_method, shareout_formula, contribution_model, share_value, internal_lending_enabled, loan_multiplier_cap, social_fund_enabled, cycle_model, meeting_cadence, formality …27 cols |
dt_companion_invitations companion | m_group · GROUP | 1:N | member-invitations, unified-auth | token, group_id, inviter_client_id, invited_email_phone, role_to_assign, expires_at, accepted_at |
dt_rosca_rotation companion | m_group · GROUP | 1:N | pluggable-distribution | cycle_number, position, recipient_client_id, recipient_name, amount, scheduled_date, paid_at, payout_order_method |
dt_rosca_auction companion | m_group · GROUP | 1:N | pluggable-distribution | cycle_number, meeting_number, bidder_client_id, bid_amount, bid_status, bid_at |
dt_vsla_cycle companion | m_group · GROUP | 1:1 | pluggable-distribution, share-out | cycle_number, share_value, total_shares_outstanding, total_interest_earned, cycle_start_date, cycle_end_date, status |
dt_welfare_fund companion | m_group · GROUP | 1:1 | social-fund | current_balance, total_contributions, total_disbursements, per_member_contribution, benefit_waiting_period_days, last_updated |
dt_member_role | m_client · CLIENT | 1:N | unified-auth, member-onboarding | role, client_type, group_id, joined_date, is_active |
dt_meeting_attendance | m_client · CLIENT | 1:N | meeting-lifecycle, fines-tracking | meeting_number, status, fine_amount, savings_collected, repayment_amount |
dt_loan_request | m_client · CLIENT | 1:N | end-user-dashboard | amount, purpose, duration_weeks, requested_at, status, reviewed_at |
dt_member_ceiling_override | m_client · CLIENT | 1:1 | loan-ceilings | max_loan_amount, max_loan_multiplier, reason, effective_date |
dt_notification | m_client · CLIENT | 1:N | notifications | title, body, type, is_read, event_created_at, deep_link |
dt_loan_vote | m_loan · LOAN | 1:1 | loan-management | meeting_number, votes_for, votes_against, votes_abstain, chairperson_approved, outcome |
dt_loan_guarantor | m_loan · LOAN | 1:N | loan-guarantees | guarantor_client_id, guarantor_name, share_pct, guaranteed_amount |
server-layer/migrations/datatables/datatables.manifest.json
alongside a demo-data seed. Provision once via the datatable API (below) after deploying a Mifos X instance with
self-service enabled. entitySubType ∈ CLIENT | GROUP | LOAN.Reading & writing any datatable
Every datatable above is reached through the same five Fineract datatable operations. {datatable}
is the table name, {apptableId} is the parent entity's primary key (groupId / clientId /
loanId), and {rowId} is required only for 1:N multi-row tables.
| Operation | Method & path | Body / notes |
|---|---|---|
| Register (migration) | POST /datatables | { datatableName, apptableName, entitySubType, multiRow, columns[] } — COMP-DT-001, run once per table |
| Read | GET /datatables/{datatable}/{apptableId} | Returns the row (1:1) or all rows (1:N) attached to that parent id |
| Create row | POST /datatables/{datatable}/{apptableId} | { <column>: <value>, …, locale, dateFormat } |
| Update | PUT /datatables/{datatable}/{apptableId}[/{rowId}] | {rowId} required for 1:N tables; omitted for 1:1 |
| Delete | DELETE /datatables/{datatable}/{apptableId}[/{rowId}] | Delete the 1:1 row, one 1:N row, or all rows for the parent |
POST /datatables register
server-layer/migrations/datatables/datatables.manifest.json{ "datatableName": "dt_group_corpus",
"apptableName": "m_group",
"entitySubType": "GROUP",
"multiRow": false,
"columns": [
{ "name":"closing_balance","type":"decimal" },
{ "name":"corpus_balance", "type":"decimal" } ] }GET /datatables/dt_group_corpus/{groupId} read
groups-centers.yaml · get_group_corpus→ the running fund balance row for that group
response { "closing_balance": Decimal,
"corpus_balance": Decimal,
"cash_on_hand": Decimal,
"last_updated_meeting": Int }string · number · decimal · boolean · date · datetime · text
(confirmed against sandbox.mifos.community); entitySubType is mandatory on modern Fineract. Reserved
names are remapped (e.g. created_at → event_created_at on dt_notification).