Reference
Documentation
Learn how every feature in QSavings works — from authentication and group creation to contributions, loans, campaigns, and platform administration.
Authentication
QSavings uses phone-based authentication. Users register with their phone number, verify via OTP, and receive JWT tokens for all subsequent requests.
Registration & Login
| Field | Default | Purpose |
|---|---|---|
| Phone Number | +250... | The primary identity. Must be a valid Rwandan phone number. Used for login and OTP verification. No email required. |
| Full Name | — | Display name shown throughout the platform — in group member lists, contribution records, and loan applications. |
| Password | — | Must meet strength rules (min 8 chars, mixed case, digit). Used alongside phone number for login. |
Token Management
| Field | Default | Purpose |
|---|---|---|
| Access Token | 1 hour | Short-lived JWT sent with every request. Contains user ID, phone, platform role, and KYC status. Expires after 1 hour for security. |
| Refresh Token | 30 days | Long-lived token stored hashed in the database. Used to obtain new access tokens without re-entering credentials. |
| OTP Code | 6 digits | One-time code sent to the phone number during registration. Verifies that the user owns the phone number. |
Savings Groups
A savings group is the core unit in QSavings. It defines the rules for contributions, loans, and cycles. The person who creates a group automatically becomes its chairperson.
Core Identity
| Field | Default | Purpose |
|---|---|---|
| Name | — | The display name of the savings group (e.g. "Family Ntwari Savings"). Used throughout the UI to identify the group. |
| Description | — | Optional text explaining the group’s purpose, rules, or goals. Helps members understand what the group is about. |
| Currency | RWF | The currency all financial transactions in the group are denominated in. Currently fixed to Rwandan Franc. |
Contribution Rules
| Field | Default | Purpose |
|---|---|---|
| Share Value | — | The monetary value of one “share” (e.g. 10,000 RWF). When a member contributes, they buy a number of shares. grossAmount = sharesCount × shareValue. This standardizes contributions so every member contributes in equal units. |
| Contribution Frequency | monthly | How often members are expected to contribute — weekly or monthly. Drives the group’s savings cycle rhythm. |
Loan Rules
| Field | Default | Purpose |
|---|---|---|
| Max Loan Multiplier | 3 | How much a member can borrow relative to their total contributions. If a member has contributed 100,000 RWF and the multiplier is 3, they can borrow up to 300,000 RWF. This limits risk exposure for the group. |
| Loan Interest Rate % | 2 | The interest rate charged on loans. When disbursed, the system calculates: totalRepayment = principal + (principal × interestRate / 100). This is how the group earns returns on its pooled savings. |
| Penalty Rate % | 5 | The percentage charged on late loan repayments. If a repayment is overdue past the grace period, a penalty of outstandingAmount × penaltyRate / 100 is applied. Discourages delinquency. |
| Grace Period (days) | 7 | Number of days after a repayment due date before the penalty kicks in. Gives borrowers a buffer for minor delays without financial consequence. |
Cycle Management
| Field | Default | Purpose |
|---|---|---|
| Cycle Duration (months) | 12 | The length of one full savings cycle. At the end of a cycle, groups typically do a "share-out" — distributing accumulated savings and profits back to members proportional to their shares. Then a new cycle begins. |
| Cycle Start Date | Today | When the current cycle begins. Auto-set to the creation date. Used to calculate cycle end date (cycleStartDate + cycleDurationMonths) and determine which contributions/loans belong to the current cycle. |
Roles & Governance
Each group has a committee that governs operations. Roles are assigned per-group and checked on every request — they are never stored in the JWT token for security.
Committee Roles
| Field | Default | Purpose |
|---|---|---|
| Chairperson | Auto-assigned | The group creator. Has full administrative rights: invite members, manage settings, and oversee all operations. Every group has exactly one. |
| Treasurer | Assigned | Responsible for verifying and rejecting contributions. Can also approve or reject loan applications. Ensures financial accountability. |
| Secretary | Assigned | Can verify contributions and review loan applications. Supports the treasurer in maintaining accurate records. |
Access Control
| Field | Default | Purpose |
|---|---|---|
| Member | On join | Can record contributions, apply for loans, pledge to campaigns, and view group data. Cannot verify contributions or approve loans. |
| Platform Admin | — | System-wide role (not per-group). Can view all users, groups, revenue, and audit logs. Cannot interfere with group-level operations. |
Contributions
Contributions are the core savings mechanism. Members buy shares at a fixed value. A platform fee is automatically deducted, and the net amount is credited to the group pool.
Formula
grossAmount = shares × shareValue | fee = gross × 5% | net = gross − feeHow Contributions Work
| Field | Default | Purpose |
|---|---|---|
| Shares Count | — | The number of shares the member is buying. The gross amount is calculated as sharesCount × group shareValue. This ensures standardized, equal-unit contributions. |
| Gross Amount | Calculated | The total contribution before fees. Example: 2 shares × 10,000 RWF = 20,000 RWF gross. |
| Platform Fee (5%) | Calculated | Automatically deducted from the gross amount. Fee = grossAmount × 0.05. Recorded in the platform_fees table and the immutable ledger. |
| Fee Rate Snapshot | Locked at record | The contribution fee rate is snapshotted onto the contribution row at the moment it is recorded. If a platform admin changes the rate later, your existing pending or verified contribution is unaffected — it keeps the rate that was in effect when you created it. |
| Net Amount | Calculated | The amount actually credited to the group pool. netAmount = grossAmount − fee. Example: 20,000 − 1,000 = 19,000 RWF. |
Verification Workflow
| Field | Default | Purpose |
|---|---|---|
| Pending | On record | Initial status when a member records a contribution. Awaits committee verification. |
| Verified | — | A committee member (treasurer or secretary) confirms the contribution is valid. Only verified contributions count toward loan eligibility. |
| Rejected | — | A committee member rejects the contribution with a reason (e.g. incorrect amount). The member can correct and resubmit. The original platform fee is reversed: a contribution_fee_reversal entry is appended so the platform_fees aggregate for the rejected contribution nets to zero. |
Loans
Members borrow against their verified contributions. Loans require unanimous committee approval, and repayments are tracked with interest and penalty enforcement.
Formula
maxLoan = verifiedContributions × multiplier − activeBalance | repayment = principal + (principal × interest%)Eligibility
| Field | Default | Purpose |
|---|---|---|
| Max Loan Amount | Calculated | maxLoan = totalVerifiedContributions × group maxLoanMultiplier − activeLoansBalance. If a member contributed 100,000 RWF verified and the multiplier is 3, they can borrow up to 300,000 RWF minus any outstanding loan balance. |
| Active Loan Block | — | A member can only have one active loan per group at a time. They must fully repay before applying again. |
| Defaulted Loan Block | — | If a member has any defaulted loans, they are blocked from new applications until resolved. |
Approval Process
| Field | Default | Purpose |
|---|---|---|
| Committee Review | ALL must act | Every committee member must review the application. ALL must approve for the loan to proceed. ANY single rejection causes the entire loan to be rejected. |
| Serializable Isolation | — | Approval decisions use database serializable isolation to prevent race conditions when multiple committee members review simultaneously. |
| Review Comment | — | Each committee member can leave a comment explaining their approval or rejection reason. Recorded in the audit log. |
Loan Lifecycle
| Field | Default | Purpose |
|---|---|---|
| Pending | On apply | Loan application submitted. Awaiting committee review. |
| Under Review | — | At least one committee member has reviewed, but not all have acted yet. |
| Approved | — | All committee members approved. Ready for disbursement from the group pool. |
| Disbursed | — | Funds released to the borrower. Repayment schedule begins. Interest calculated: totalRepayment = principal + (principal × interestRate / 100). |
| Interest Fee Snapshot | Locked at apply | The platform's loan-interest fee rate (default 2%) is captured onto the loan at the moment the borrower applies. Every future repayment uses that snapshotted rate — even if a platform admin changes the group's fee config later, the borrower's quoted cost is locked from day one. |
| Completed | — | All repayments made. The loan is fully settled. Member can now apply for a new loan. |
| Rejected | — | At least one committee member rejected the application. The member can apply again with adjustments. |
Social Fund Campaigns
Campaigns are group-level fundraisers for members in need. Any member can be the beneficiary. Other members pledge amounts, and progress is tracked against a target.
Campaign Setup
| Field | Default | Purpose |
|---|---|---|
| Title | — | A short name for the campaign (e.g. “Medical Fund for Alice”). Displayed on campaign cards and lists. |
| Description | — | Details about why the campaign was created and how the funds will be used. Helps members decide whether to pledge. |
| Target Amount | — | The fundraising goal in RWF. Progress is tracked as a percentage of this target. Example: 500,000 RWF. |
| Beneficiary | — | The group member who will receive the collected funds. Selected from the current member list. |
Pledges & Fees
| Field | Default | Purpose |
|---|---|---|
| Pledge Amount | — | The amount a member commits to the campaign. Members can pledge any amount. Multiple pledges are allowed. |
| Platform Fee (5%) | Calculated | Deducted from total collected funds when the campaign completes. fee = totalCollected × 0.05. Recorded in ledger. |
| Progress | 0% | Visual percentage showing collectedAmount / targetAmount. Displayed as a progress bar on campaign cards. |
Campaign Lifecycle
| Field | Default | Purpose |
|---|---|---|
| Active | On create | Campaign is open for pledges. Members can contribute funds toward the target. |
| Completed | — | Target reached or committee closes the campaign. Funds (minus platform fee) are released to the beneficiary. |
| Cancelled | — | Campaign terminated before completion. Pledges may be refunded depending on group rules. |
Platform Administration
Platform admins have a bird’s-eye view of the entire system. They can monitor users, groups, revenue, and audit trails but cannot interfere with group-level operations.
Dashboard Metrics
| Field | Default | Purpose |
|---|---|---|
| Total Users | — | Count of all registered users on the platform, including verified and unverified accounts. |
| Total Groups | — | Count of all savings groups created across the platform. Includes active and inactive groups. |
| Total Contributions | — | Sum of all verified contributions across all groups, in RWF. Gives a sense of platform savings volume. |
| Platform Revenue | — | Total fees collected: contribution fees (5%) + loan interest fees (2%) + campaign fees (5%). Broken down by fee type. |
Fee Configuration
| Field | Default | Purpose |
|---|---|---|
| Per-Group Override | Optional | Platform admins can override any default fee rate (contribution, loan interest, campaign, payout) on a per-group basis through the group's Settings page. Stored in group_config_overrides and resolved at runtime with Redis caching. |
| Effective From | Now | Every fee change carries an effective date. Defaults to the moment the change is applied, but admins can schedule changes for a future date. The admin UI exposes a date-time picker for fee rows. |
| Snapshot Rule | — | Fee changes apply only to NEW contributions and loans recorded on or after the effective date. Existing in-flight loans and pending contributions are NEVER retroactively affected — their fee rate was snapshotted onto the row when they were originated. The admin UI shows a confirmation dialog re-stating this guarantee before any fee change is committed. See backend ADR-002. |
Audit & Compliance
| Field | Default | Purpose |
|---|---|---|
| Audit Log | — | Immutable record of every state change: contribution verifications, loan approvals, campaign completions, role assignments. Includes actor, action, entity, and timestamp. |
| Ledger Entries | — | Append-only financial ledger. Every franc in, out, or transferred is recorded. Cannot be edited or deleted. Ensures full financial traceability. |
| Platform Fees | — | Detailed breakdown of every fee charged: which transaction, fee type (contribution/loan/campaign), gross amount, fee amount, and net amount. |
Security & Data Integrity
QSavings is designed for financial trust. Every transaction is recorded immutably, every mutation is idempotent, and every role check happens at request time.
Authentication Security
| Field | Default | Purpose |
|---|---|---|
| JWT Tokens | 1h / 30d | Access tokens expire after 1 hour. Refresh tokens last 30 days and are stored hashed in the database. Short-lived tokens minimize damage from token theft. |
| Phone Verification | Required | Users must verify their phone number via OTP before accessing the platform. Prevents fake account creation. |
| Per-Request Role Check | — | Group roles are never stored in the JWT. Every request checks the database for the user’s current role in the target group. Prevents stale permissions. |
Data Integrity
| Field | Default | Purpose |
|---|---|---|
| Idempotency Keys | UUID v4 | Every mutation (POST/PUT) requires a unique idempotency key. If the same key is sent twice, the second request returns the original result without re-processing. Prevents duplicate transactions. |
| Immutable Ledger | — | All financial transactions write to an append-only ledger. Entries cannot be updated or deleted. Provides an unalterable audit trail for every franc. |
| Serializable Transactions | — | Critical operations like loan approvals use serializable database isolation. Prevents race conditions and ensures consistency. |
| RFC 7807 Errors | — | All errors return a standard Problem Details JSON object with type, title, status, detail, and instance. Makes error handling predictable for all clients. |