WashikaDAU API
A REST API over Tanzania’s community savings infrastructure. Read groups and their rosters, see exactly who has contributed and who hasn’t, follow every nTZS transaction, inspect wallet balances, and track governance proposals with live vote tallies.
Quickstart
Create a key in the partner dashboard, then send it as a bearer token. Keys are shown once at creation and only a hash is stored, so save it somewhere safe.
- 1Sign in and open the partner dashboard.
- 2Create a key with the read (and optionally write) scope.
- 3Send it as an Authorization: Bearer header on every request.
# Every request carries your key
export WD_KEY="wd_live_xxxxxxxxxxxxxxxxxxxxxxxx"
curl -H "Authorization: Bearer $WD_KEY" \
"https://washikadau.com/api/v1/stats"
# Who hasn't paid this month?
curl -H "Authorization: Bearer $WD_KEY" \
"https://washikadau.com/api/v1/groups/30/contributions?period=2026-07&include_unpaid=true"Conventions
Envelope
Success returns { data, meta }. Failure returns { error: { code, message } }.
Money
Always an integer number of TZS. Field names end in _tzs. No floats, no currency strings.
Time
Every timestamp is an ISO-8601 UTC string.
Paging
?limit= (1–100, default 25) and ?offset=. meta carries total and has_more.
Isolation
Your key reads and writes only the groups and members you created. Anything outside your tenant returns 404.
Your data is your own
Every group and member you create through the API belongs to your organisation alone. You cannot see other partners’ records, and you cannot see the groups and people who joined WashikaDAU directly — a new key starts with an empty tenant. Begin with POST /api/v1/members/create, then create a group for them to belong to.
Rate limit: 120 requests per minute per key. Exceeding it returns 429 rate_limited with a Retry-After header.
API reference
Platform
Aggregate figures across every group, member and transaction.
/api/v1/statsreadPlatform-wide totals
Group and member counts, money processed, money currently held in group treasuries, contributions collected and proposal counts.
{
"data": {
"groups": { "total": 23, "active": 23 },
"members": { "total": 193, "with_business": 189 },
"money": {
"volume_processed_tzs": 3587538,
"held_in_groups_tzs": 1261000,
"contributions_collected_tzs": 0
},
"proposals": { "total": 23, "funded": 4 },
"generated_at": "2026-07-27T23:24:15.596Z"
}
}Groups
Savings groups: create them, read their configuration, treasury and totals.
/api/v1/groupsreadList groups
Logos are stored inline, so list responses return `has_logo` and omit `logo_url`. Fetch a single group to get the image.
Query parameters
| status | string | Filter by group status, e.g. `active`. |
| q | string | Case-insensitive search on group name. |
| limit | integer | 1–100. Defaults to 25. |
| offset | integer | Rows to skip. Defaults to 0. |
curl -H "Authorization: Bearer $WD_KEY" \
"https://washikadau.com/api/v1/groups?status=active&limit=2"/api/v1/groups/{id}readRetrieve a group
`{id}` accepts the numeric id or the human group code (e.g. `JKM-ZK79KQ`). Includes treasury balance, lifetime collected/disbursed totals and proposal counts.
{
"data": {
"id": 32,
"name": "THE BOYS FC",
"code": "JKM-ZK79KQ",
"contribution": { "amount_tzs": 10000, "frequency": "weekly" },
"voting_threshold": { "numerator": 3, "denominator": 5 },
"member_count": 1,
"treasury_balance_tzs": 0,
"totals": { "collected_tzs": 0, "disbursed_tzs": 0, "transactions_90d": 0 },
"proposals": { "total": 0, "open": 0, "funded": 0 }
}
}/api/v1/groupswriteCreate a group
The leader must be one of your own members; they are added to the group in the same transaction. A join code is generated automatically.
Body
| name* | string | Group name, unique within your tenant. |
| monthly_contribution_tzs* | integer | Contribution amount per cycle, in TZS. |
| leader_member_id* | integer | Member who becomes the leader. Use `leader_user_id` instead if they have a WashikaDAU login. |
| contribution_frequency | "monthly" | "weekly" | Defaults to `monthly`. |
| voting_numerator | integer | Defaults to 3. |
| voting_denominator | integer | Defaults to 5. |
curl -X POST "https://washikadau.com/api/v1/groups" \
-H "Authorization: Bearer $WD_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Kilimo Pamoja",
"monthly_contribution_tzs": 20000,
"leader_member_id": 42,
"contribution_frequency": "monthly"
}'/api/v1/groups/{id}/membersreadList group members
Query parameters
| role | string | `leader`, `mwenyekiti`, `katibu`, `mwekahazina`, `mwanachama`. |
| status | string | Membership status, e.g. `active`. |
| limit | integer | 1–100. Defaults to 25. |
| offset | integer | Rows to skip. |
/api/v1/groups/{id}/members/addwriteAdd a member to a group
Body
| member_id* | integer | An existing member id. |
| role | string | leader, mwenyekiti, katibu, mwekahazina or mwanachama (default). |
| status | string | Defaults to `active`. |
/api/v1/groups/{id}/proposalsreadList proposals with vote tallies
Query parameters
| status | "open" | "closed" | Filter by voting status. |
| type | string | `general`, `ask`, `spend` or `prodcast`. |
| limit | integer | 1–100. Defaults to 25. |
| offset | integer | Rows to skip. |
{
"data": [{
"id": 16,
"title": "Pesa ya majaribio",
"type": "spend",
"status": "closed",
"created_by": { "member_id": 69, "name": "Victor" },
"payment": { "amount_tzs": 6000, "status": "completed", "executed_at": "2026-06-29T06:09:16.644Z" },
"votes": { "yes": 1, "no": 0, "abstain": 0, "total": 1 }
}],
"meta": { "total": 2, "limit": 1, "offset": 0, "has_more": true }
}Contributions
Who has paid their contribution — and who has not.
/api/v1/groups/{id}/contributionsreadContribution ledger
Returns paid contributions plus a `summary` of amounts and counts. Pass `include_unpaid=true` with a `period` to also get the roster of active members who have not paid for that month.
Query parameters
| period | string (YYYY-MM) | Restrict to one contribution month. |
| status | string | `paid`, `pending`, `overdue`. |
| member_id | integer | Restrict to one member. |
| include_unpaid | boolean | Requires `period`. Adds `meta.unpaid_members`. |
| limit | integer | 1–100. Defaults to 25. |
| offset | integer | Rows to skip. |
curl -H "Authorization: Bearer $WD_KEY" \
"https://washikadau.com/api/v1/groups/30/contributions?period=2026-07&include_unpaid=true"
# meta.summary -> { paid_tzs, paid_count, unpaid_count }
# meta.unpaid_members -> [{ id, full_name, username }]/api/v1/groups/{id}/contributions/recordwriteRecord a contribution
Writes the contribution ledger only — it does not move money. To do both, also call POST /api/v1/transfers with purpose "contribution". Returns 409 if a row already exists for that member and period.
Body
| member_id* | integer | Must already belong to the group. |
| amount_tzs* | integer | Positive whole TZS. |
| period* | string (YYYY-MM) | Contribution month. |
| status | "paid" | "pending" | "overdue" | Defaults to `paid`. |
| payment_method | string | Free text, defaults to `api`. |
| reference | string | Your own payment reference. |
Proposals & voting
Group governance: open proposals, read them, and cast votes.
/api/v1/proposalsreadList proposals across all groups
Query parameters
| group_id | integer | Restrict to one group. |
| status | "open" | "closed" | Filter by voting status. |
| type | string | general, ask, spend or prodcast. |
| funded | boolean | `true` returns only funded proposals. |
/api/v1/proposals/{id}readRetrieve a proposal
Includes the type-specific metadata (funding goal, timeline, expected impact, vendor) and a vote block carrying the group threshold, how many yes votes are required, and whether it has passed.
{
"data": {
"id": 16,
"title": "Pesa ya majaribio",
"type": "spend",
"status": "closed",
"group_name": "Ali & Vic Admi",
"payment": { "amount_tzs": 6000, "status": "completed" },
"votes": {
"yes": 1, "no": 0, "abstain": 0, "total": 1,
"eligible_voters": 2, "required_yes": 2,
"threshold": "3/5", "passed": false
}
}
}/api/v1/proposals/{id}writeClose or reopen voting
Refuses to reopen a proposal whose payment already completed.
Body
| status* | "open" | "closed" | New voting status. |
/api/v1/groups/{id}/proposals/createwriteOpen a proposal
Required fields depend on type. general needs only a title; ask needs amount_tzs; spend needs amount_tzs plus a recipient_phone, recipient_member_id or vendor_name; prodcast needs funding_goal_tzs. The author must be an active member of the group.
Body
| created_by_member_id* | integer | Author — must be an active group member. |
| title* | string | Short headline. |
| description | string | Body text. |
| type | "general" | "ask" | "spend" | "prodcast" | Defaults to `general`. |
| amount_tzs | integer | Required for ask and spend. |
| recipient_member_id | integer | Payee, for spend. |
| recipient_phone | string | Mobile-money payee, for spend. |
| vendor_name | string | Supplier name, for spend. |
| expense_category | string | Free text, for spend. |
| funding_goal_tzs | integer | Required for prodcast. |
| timeline | string | e.g. "6 months", for prodcast. |
| expected_impact | string | For prodcast. |
curl -X POST "https://washikadau.com/api/v1/groups/32/proposals/create" \
-H "Authorization: Bearer $WD_KEY" \
-H "Content-Type: application/json" \
-d '{
"created_by_member_id": 126,
"title": "Buy a shared irrigation pump",
"type": "spend",
"amount_tzs": 450000,
"vendor_name": "Kilimo Supplies Ltd",
"expense_category": "Equipment"
}'/api/v1/proposals/{id}/votesreadList ballots
Every member who voted, what they chose and when — plus the running tally.
/api/v1/proposals/{id}/voteswriteCast or change a vote
One ballot per member: voting again replaces the earlier choice. Returns 409 voting_closed if voting has ended, or 403 not_eligible if the member is not an active member of the group.
Body
| member_id* | integer | Voter — must be an active group member. |
| vote* | "yes" | "no" | "abstain" | The ballot. |
Members
Directory data. Phone numbers, emails and national IDs are never returned.
/api/v1/members/createwriteOnboard a member
The entry point for an integration: your tenant starts empty, so create a member before creating a group to lead. The member belongs to you and is visible only to your keys.
Query parameters
| full_name* | string | The person’s name. |
| phone | string | Tanzanian number. Stored, never returned. |
| location | string | Town or region. |
| business_name | string | Their business, if any. |
| business_type | string | Sector or trade. |
| status | string | `active` (default), `pending` or `inactive`. |
/api/v1/membersreadList members
Query parameters
| q | string | Search on full name or username. |
| group_id | integer | Only members of this group. |
| status | string | Member status. |
| has_business | boolean | `true` to return only members running a business. |
| limit | integer | 1–100. Defaults to 25. |
| offset | integer | Rows to skip. |
/api/v1/members/{id}readRetrieve a member
Includes every group membership with role, the wallet balance, and lifetime contributions paid.
Wallets & transactions
nTZS balances and the full money ledger. All amounts are integer TZS.
/api/v1/wallets/{ownerType}/{ownerId}readWallet balance
`ownerType` is `member`, `group` or `investor`. Includes a 30-day money-in / money-out summary. Platform `master` and `fee` accounts are not exposed.
{
"data": {
"owner_type": "group",
"owner_id": 30,
"owner_name": "Ali & Vic Admi",
"balance_tzs": 5000,
"provisioned": true,
"last_30_days": { "money_in_tzs": 0, "money_out_tzs": 6000, "transaction_count": 1 }
}
}/api/v1/depositswriteDeposit — mobile money to nTZS
Triggers an STK push to the payer. Returns 202 with a pending transaction: the balance is credited only when the provider confirms settlement, so an abandoned push never creates money. Poll GET /api/v1/transactions/{id} for the outcome.
Body
| member_id* | integer | Member being credited on settlement. |
| amount_tzs* | integer | At least 100 TZS. |
| phone* | string | 07XXXXXXXX or 2557XXXXXXXX. |
curl -X POST "https://washikadau.com/api/v1/deposits" \
-H "Authorization: Bearer $WD_KEY" \
-H "Content-Type: application/json" \
-d '{ "member_id": 126, "amount_tzs": 50000, "phone": "0712345678" }'/api/v1/withdrawals/quotewritePrice a cash-out
Mandatory before withdrawing — the provider rejects any payout without a fresh quote. Quotes expire after about five minutes. Shows the recipient name on the mobile-money account plus the full fee breakdown.
Body
| member_id* | integer | Member the funds come from. |
| amount_tzs* | integer | Net amount the recipient receives. |
| phone* | string | Recipient mobile-money number. |
{
"data": {
"quote_id": "q_9f2c…",
"expires_at": "2026-07-28T10:05:00Z",
"recipient_name": "DAVID MACHUCHE",
"receive_amount_tzs": 50000,
"provider_fee_tzs": 1000,
"platform_fee_tzs": 0,
"total_debit_tzs": 50000,
"member_balance_tzs": 82000,
"sufficient_funds": true
}
}/api/v1/withdrawalswriteWithdraw — nTZS to mobile money
Debits the member and pays out. Returns 409 with invalid_quote / quote_stale / quote_mismatch if the quote has expired or its terms changed — fetch a fresh quote and retry. Returns 402 insufficient_balance if the member cannot cover the amount plus fees.
Body
| member_id* | integer | Member being debited. |
| amount_tzs* | integer | Must match the quote. |
| phone* | string | Must match the quote. |
| quote_id* | string | From POST /api/v1/withdrawals/quote. |
/api/v1/transferswriteMove money inside the platform
Atomic database transfer between two wallet accounts — nothing touches the chain. Provide exactly one sender and one recipient. Use purpose "contribution" for member → group, "p2p" for member → member, "disbursement" for group → member.
Body
| amount_tzs* | integer | Positive whole TZS. |
| purpose* | "contribution" | "p2p" | "disbursement" | What the movement represents. |
| from_member_id | integer | Sender. Mutually exclusive with from_group_id. |
| from_group_id | integer | Sender. Mutually exclusive with from_member_id. |
| to_member_id | integer | Recipient. Mutually exclusive with to_group_id. |
| to_group_id | integer | Recipient. Mutually exclusive with to_member_id. |
# Member pays their contribution into the group treasury
curl -X POST "https://washikadau.com/api/v1/transfers" \
-H "Authorization: Bearer $WD_KEY" \
-H "Content-Type: application/json" \
-d '{
"from_member_id": 126,
"to_group_id": 32,
"amount_tzs": 10000,
"purpose": "contribution"
}'/api/v1/transactions/{id}readRetrieve one transaction
Accepts our numeric id or the provider’s external id. `settled` reflects the ledger’s own view — true once the money has actually been applied to a balance.
/api/v1/transactionsreadList transactions
Query parameters
| group_id | integer | Either side of the transfer. |
| member_id | integer | Either side of the transfer. |
| type | string | `deposit`, `withdrawal`, `transfer`, `disbursement`. |
| purpose | string | `deposit`, `withdrawal`, `contribution`, `disbursement`, `p2p`, `fee`. |
| status | string | Provider status, e.g. `completed`. |
| since | ISO-8601 | Only transactions at or after this time. |
| until | ISO-8601 | Only transactions at or before this time. |
| limit | integer | 1–100. Defaults to 25. |
| offset | integer | Rows to skip. |
Errors
Failures use standard HTTP status codes and a stable machine-readable code. Branch on the code, show the message.
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_api_key | No Authorization header was sent. |
| 401 | invalid_api_key | The key does not exist or has been revoked. |
| 403 | insufficient_scope | The key lacks the scope this endpoint needs. |
| 404 | not_found | No resource with that identifier. |
| 409 | group_exists | A group with that name already exists. |
| 422 | invalid_request | A parameter is missing or malformed. The message names the field. |
| 422 | quote_required | A withdrawal was sent without a quote_id. |
| 409 | invalid_quote | The withdrawal quote expired or was malformed. Fetch a fresh one. |
| 409 | quote_stale | Pricing moved since the quote was issued. Re-quote and retry. |
| 402 | insufficient_balance | The sender cannot cover the amount plus fees. |
| 409 | already_member | That member is already in the group. |
| 409 | already_recorded | A contribution already exists for that member and period. |
| 502 | provider_error | The mobile-money provider rejected the request. |
| 503 | wallet_unavailable | The wallet provider is not configured. |
| 409 | voting_closed | Voting on that proposal has ended. |
| 403 | not_eligible | That member is not an active member of the group. |
| 409 | already_executed | The proposal was already paid out and cannot be reopened. |
| 429 | rate_limited | Over 120 requests/minute. Retry after the Retry-After header. |
| 500 | internal_error | Unexpected server error. Safe to retry. |
What the API will not return
Member phone numbers, email addresses and national ID details are never exposed, on any endpoint, at any scope. Neither are proposal file attachments or the platform’s own master and fee wallet accounts. If you need data that is deliberately withheld here, talk to us rather than scraping around it.