Full API documentation

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 →

51 endpoints 45 Mifos X standard 6 companion BFF 21 datatables 10 endpoint groups 101 MCP tools

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.

Group-centric model. The savings group is a Fineract Group (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).
How this is generated. /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.
/mifos-bridge — companion development

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).

branchdev

🏦 Live instances

All development + device-verification runs against a real Mifos X instance:

primarymifos-bank-2 (tenant mifos-bank-2)
fallbacksandbox (tenant default)

join-with-code and meeting-conduct → meeting-summary were exercised live on mifos-bank-2 (companion invitations datatable + groups/savings/loan posts).

Companion componentLanguageResponsibility
Group-banking companion facadeGoPer-call user credential intake; organizer-vs-member authz; group orchestration with a service credential
Datatable & domain MCP toolsPythonRegister + CRUD the 21 datatables; VSLA/ROSCA domain ops (share-out, rotation, auction)
MCP tool surface101 tools — full programmatic access, one mcp_tool per endpoint below
Demo access

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.

NameLogin (phone)PasswordRole / Persona
Amina Otieno+254700000001DemoExplore@2026Organizer + Treasurer — primary VSLA showcase; Demo Explore logs in as this user
Joseph Mwangi+254700000002DemoChair@2026Chairperson — approves loans, chairs meetings
Grace Wanjiru+254700000003DemoMember@2026Member — savings + active disbursed loan
Faith Njeri+254700000004DemoOrganizer@2026Organizer — founds & runs the group
Peter Kamau+254700000005DemoSecretary@2026Secretary — meeting minutes & attendance
David Ochieng+254700000010DemoField@2026MFI field officer — cross-group oversight

POST /self/authentication end-user login

authentication.yaml · self_authenticate · use phone number as username
curl -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 roles
curl -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" } ] }
Same credentials, both instances. 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.
Endpoint groups

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-code

Unified self-signup — a single login; the per-group role resolves after auth.

POST/authentication
POST/self/authentication
GET/datatables/dt_member_role/{clientId}

🏘️ Groups 8 ops

apis/groups-centers.yaml · group-list, group-dashboard, group-create, meeting-conduct

Group create, group config + corpus datatables, group detail + roles. Reads go through the companion BFF; raw /groups/* is the write path.

POST/groups
GET/groups/{groupId}?associations=…
POST/datatables/dt_group_config/{groupId}
PUT/datatables/dt_group_corpus/{groupId}

🗓️ Meetings 5 ops

apis/meetings.yaml · meeting-calendar, meeting-conduct, meeting-summary, previous-meeting-review

Schedule read, attendance, meeting record, and cycle advance — the meeting engine.

GET/datatables/dt_meeting_schedule/{groupId}
POST/datatables/dt_meeting_attendance/{clientId}
POST/datatables/dt_meeting_record/{groupId}

💳 Loans 9 ops

apis/loans.yaml · loan-list, loan-apply, loan-detail, meeting-conduct

Apply, group-vote, approve, disburse, reject, list, and repay.

GET/loans?groupId={groupId}
POST/loans
POST/loans/{loanId}?command=disburse
POST/loans/{loanId}/transactions?command=repayment

👥 Clients & members 9 ops

apis/clients-members.yaml · member-list, member-profile, member-add

Member (client) create, group association, role datatable, photo, and accounts.

POST/clients
POST/groups/{groupId}?command=associateClients
GET/clients/{clientId}/accounts

💰 Savings 7 ops

apis/savings.yaml · savings-dashboard, meeting-conduct, personal-savings, member-savings-detail

Group-linked accounts, deposits, and balances. Summaries via companion BFF; raw savings are the write path.

POST/savingsaccounts
POST/savingsaccounts/{id}/transactions?command=deposit
GET/savingsaccounts/{id}

🪙 Share-out 3 ops

apis/share-out.yaml · share-out-preview, share-out-execute

Cycle share-out / rotation preview then execute.

GET/companion/groups/{groupId}/shareout/preview
POST/companion/groups/{groupId}/shareout/execute

🙋 End-user self-service 4 ops

apis/self-service.yaml · personal-dashboard, personal-savings, personal-loans, loan-request

Member-scoped reads over their own accounts + loan requests.

GET/self/savingsaccounts
GET/self/loans
POST/datatables/dt_loan_request/{clientId}

🔄 Offline sync 1 op

apis/offline-sync.yaml · sync-status

One batched POST replays the durable offline queue in order (Fineract Batch API).

POST/batches

🧑‍💼 Field officer 2 ops · v1.1

apis/field-officer.yaml · field-officer-dashboard

Supervisory reads + KPI report across an officer's assigned groups.

GET/groups?staffId={staffId}
GET/runreports/FieldOfficerGroupReport
Companion backend (BFF)

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 groupOperationsBacking
COMP-AUTHself-register · login → groups+roles · mego/tools/companion_auth.go
COMP-GRPcreate · activate · associate-clients · assign-role · assign-staffgo/tools/companion_groups.go
COMP-CALschedule read · attendance · meeting recordgo/tools/companion_calendar.go
COMP-DTdatatable register · create · read · update · delete rowsgo/tools/datatables.go
COMP-DISTshare-out / rotation execute · ROSCA rotation nextclient computes, server re-validates + executes
Full endpoint reference · for the backend engineer

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.

GroupMethod & pathMCP toolUsed by screen
authentication 3POST /authenticationauthenticatelogin (admin)
POST /self/authenticationself_authenticatelogin (end_user)
GET /datatables/dt_member_role/{clientId}get_member_rolelogin (post-auth role resolution)
groups-centers 8GET /groups?staffId={staffId}&paged=true&limit=50list_all_groupsgroup-list
GET /groups/{groupId}?associations=clientMembers,groupRolesget_groupgroup-dashboard
GET /groups/{groupId}/accountslist_accountsgroup-dashboard
GET /datatables/dt_group_corpus/{groupId}get_group_corpusgroup-dashboard
GET /datatables/dt_group_config/{groupId}get_group_configgroup-dashboard, group-create
POST /groupscreate_groupgroup-create
POST /datatables/dt_group_config/{groupId}upsert_group_configgroup-create
PUT /datatables/dt_group_corpus/{groupId}update_group_corpusmeeting-conduct
meetings 5GET /datatables/dt_meeting_schedule/{groupId}get_meeting_schedulemeeting-calendar
POST /datatables/dt_meeting_record/{groupId}record_meetingmeeting-conduct (Step 7 close)
POST /datatables/dt_meeting_attendance/{clientId}record_attendancemeeting-conduct (Step 1)
GET /datatables/dt_meeting_record/{groupId}?meetingNumber={n-1}list_meetingsprevious-meeting-review
PUT /datatables/dt_group_config/{groupId}advance_cyclemeeting-summary (end of cycle)
loans 9GET /groups/{groupId}/loans?associations=repaymentSchedule,guarantorslist_group_loansloan-list
GET /loans?groupId={groupId}&loanStatus={status}&limit={limit}&offset={offset}list_loansloan-list, meeting-conduct (Step 4)
GET /loans/template?activeOnly=true&clientId={clientId}&groupId={groupId}get_loan_app_templateloan-apply
POST /loanscreate_new_loanloan-apply
POST /loans/{loanId}?command=approveapprove_loanmeeting-conduct (Step 5)
POST /loans/{loanId}?command=disbursedisburse_loanmeeting-conduct (Step 5)
POST /loans/{loanId}?command=rejectreject_loanmeeting-conduct (Step 5)
POST /datatables/dt_loan_vote/{loanId}record_loan_votemeeting-conduct (Step 5)
POST /loans/{loanId}/transactions?command=repaymentmake_repaymentmeeting-conduct (Step 3)
offline-sync 1POST /batchessend_batchsync-status
end-user self-service 4GET /self/clients/{clientId}get_self_clientpersonal-dashboard
GET /self/savingsaccountslist_self_savingspersonal-savings
GET /self/loanslist_self_loanspersonal-loans
POST /datatables/dt_loan_request/{clientId}submit_loan_requestloan-request
clients-members 9GET /groups/{groupId}/clients?limit&offsetlist_all_groupsmember-list
GET /clients/{clientId}get_clientmember-profile
GET /clients/{clientId}/accountsget_client_acctsmember-profile
GET /datatables/dt_member_role/{clientId}get_member_rolemember-profile
PUT /datatables/dt_member_role/{clientId}assign_member_rolemember-profile (chair-only)
POST /clientscreate_new_clientmember-add
POST /datatables/dt_member_role/{clientId}assign_member_rolemember-add
POST /groups/{groupId}?command=associateClientsadd_member_to_groupmember-add
POST /clients/{clientId}/imagescreate_datatable_entrymember-add (photo)
savings 7GET C /companion/groups/{groupId}/savingscompanion_get_group_savings_summarysavings-dashboard (Group)
GET C /companion/groups/{groupId}/savings/individualcompanion_get_individual_savings_summarysavings-dashboard (Individual)
GET C /companion/groups/{groupId}/members/{memberId}/savingscompanion_get_member_savings_detailmember-savings-detail
POST /savingsaccountscreate_savingsmember-add, group-create
POST /savingsaccounts/{accountId}?command=approveapprove_activate_savingsmember-add
POST /savingsaccounts/{savingsId}/transactions?command=depositdepositmeeting-conduct (Step 2)
GET /savingsaccounts/{savingsId}?associations=transactionsget_savings_txnssavings-dashboard
share-out 3GET C /companion/groups/{groupId}/shareout/previewcompanion_get_shareout_previewshare-out-preview
POST C /companion/groups/{groupId}/shareout/executecompanion_execute_shareoutshare-out-execute
POST C /companion/groups/{groupId}/rotation/executecompanion_execute_rotation_payoutshare-out-execute
field-officer v1.1 2GET /groups?staffId={staffId}&paged=true&limit&offsetlist_all_groupsfield-officer-dashboard
GET /runreports/FieldOfficerGroupReport?R_staffId={staffId}&output-type=CSVrun_fineract_reportfield-officer-dashboard
Signatures & example bodies. Every endpoint above has a typed 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.
Request / response schemas

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 · authenticate
request  { "username": "string", "password": "string" }
response { "userId": Long, "username": "string",
           "authenticated": boolean, "officeId": Long,
           "token": "string" }

POST /self/authentication

authentication.yaml · self_authenticate
request  { "username": "string", "password": "string" }
response { "clientId": Long, "authenticated": boolean,
           "selfServiceUserDetails": object, "token": "string" }

POST /clients

clients-members.yaml · create_new_client
request  { "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_group
request  { "name": string, "officeId": Long,
           "staffId": Long, "active": boolean,
           "activationDate": date }
response { "resourceId": Long, "fineractGroupId": Long }

POST /savingsaccounts/{id}/transactions?command=deposit

savings.yaml · deposit
request  { "transactionDate": String,
           "transactionAmount": Long,
           "paymentTypeId": Int,   // 1 = Cash
           "locale": String, "dateFormat": String }

GET /datatables/dt_member_role/{clientId}

authentication.yaml · get_member_role
response { "role": string,        // treasurer | chairperson
                             // | field_officer | member
           "client_type": string, // admin | end_user
           "group_id": Long,      // the (member, group) key
           "joined_date": date }
Datatable connection flows · entity-relationship map

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).

m_officebranch / tenant m_groupsavings group m_clientmember m_loanloan account m_savings_accountsavings account
m_groupGROUP · the savings group · 14 tables
dt_group_config1:1
dt_group_corpus1:1
dt_meeting_record1:N
dt_meeting_schedule1:N
dt_share_out1:N
dt_social_fund1:1
dt_sync_metadata1:1
dt_group_loan_policy1:1
dt_group_type_config1:1
dt_companion_invitations1:N
dt_rosca_rotation1:N
dt_rosca_auction1:N
dt_vsla_cycle1:1
dt_welfare_fund1:1
m_clientCLIENT · the member · 5 tables
dt_member_role1:N
dt_meeting_attendance1:N
dt_loan_request1:N
dt_member_ceiling_override1:1
dt_notification1:N
m_loanLOAN · the loan · 2 tables
dt_loan_vote1:1
dt_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)

Datatables — the missing-API extension layer

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.

DatatableParent · entityRowsFeatureColumns
dt_group_configm_group · GROUP1:1group-managementcycle_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_corpusm_group · GROUP1:1corpus-trackingmeeting_number, opening_balance, total_inflows, total_outflows, closing_balance, corpus_balance, cash_on_hand, last_updated_meeting, last_updated_date
dt_meeting_recordm_group · GROUP1:Nmeeting-lifecyclemeeting_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 companionm_group · GROUP1:Nmeeting-lifecyclemeeting_number, scheduled_date, cadence, status — AL-RULE companion-served
dt_share_outm_group · GROUP1:Nshare-outcycle_number, total_pool, profit_earned, status, executed_date
dt_social_fundm_group · GROUP1:1social-fundcurrent_balance, total_inflows, total_disbursed, last_updated
dt_sync_metadatam_group · GROUP1:1offline-synclast_sync_at, pending_count, conflict_count, sync_version
dt_group_loan_policym_group · GROUP1:1loan-ceilingsmax_loan_multiplier, max_loan_amount, min_savings_ratio, max_term_weeks, interest_rate, requires_guarantor
dt_group_type_config companionm_group · GROUP1:1group-type-configslug, 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 companionm_group · GROUP1:Nmember-invitations, unified-authtoken, group_id, inviter_client_id, invited_email_phone, role_to_assign, expires_at, accepted_at
dt_rosca_rotation companionm_group · GROUP1:Npluggable-distributioncycle_number, position, recipient_client_id, recipient_name, amount, scheduled_date, paid_at, payout_order_method
dt_rosca_auction companionm_group · GROUP1:Npluggable-distributioncycle_number, meeting_number, bidder_client_id, bid_amount, bid_status, bid_at
dt_vsla_cycle companionm_group · GROUP1:1pluggable-distribution, share-outcycle_number, share_value, total_shares_outstanding, total_interest_earned, cycle_start_date, cycle_end_date, status
dt_welfare_fund companionm_group · GROUP1:1social-fundcurrent_balance, total_contributions, total_disbursements, per_member_contribution, benefit_waiting_period_days, last_updated
dt_member_rolem_client · CLIENT1:Nunified-auth, member-onboardingrole, client_type, group_id, joined_date, is_active
dt_meeting_attendancem_client · CLIENT1:Nmeeting-lifecycle, fines-trackingmeeting_number, status, fine_amount, savings_collected, repayment_amount
dt_loan_requestm_client · CLIENT1:Nend-user-dashboardamount, purpose, duration_weeks, requested_at, status, reviewed_at
dt_member_ceiling_overridem_client · CLIENT1:1loan-ceilingsmax_loan_amount, max_loan_multiplier, reason, effective_date
dt_notificationm_client · CLIENT1:Nnotificationstitle, body, type, is_read, event_created_at, deep_link
dt_loan_votem_loan · LOAN1:1loan-managementmeeting_number, votes_for, votes_against, votes_abstain, chairperson_approved, outcome
dt_loan_guarantorm_loan · LOAN1:Nloan-guaranteesguarantor_client_id, guarantor_name, share_pct, guaranteed_amount
All 21 register migrations ship in 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. entitySubTypeCLIENT | GROUP | LOAN.
Datatable API signature · the generic CRUD contract

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.

OperationMethod & pathBody / notes
Register (migration)POST /datatables{ datatableName, apptableName, entitySubType, multiRow, columns[] } — COMP-DT-001, run once per table
ReadGET /datatables/{datatable}/{apptableId}Returns the row (1:1) or all rows (1:N) attached to that parent id
Create rowPOST /datatables/{datatable}/{apptableId}{ <column>: <value>, …, locale, dateFormat }
UpdatePUT /datatables/{datatable}/{apptableId}[/{rowId}]{rowId} required for 1:N tables; omitted for 1:1
DeleteDELETE /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 }
Fineract typing. Column types are lowercase 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_atevent_created_at on dt_notification).