API overview
Every route is under /v1 and, except the two health checks, needs a signed-in user’s access token
(Authorization: Bearer <token>). The full, always-current reference is the Swagger page at /docs on a
running API; this page explains the rules that apply everywhere and what exists today.
Rules that apply to every endpoint
| Rule | What it means for you |
|---|---|
| Money is whole kobo | 150000 means NGN 1,500. A decimal, zero, negative or over-the-maximum amount is rejected with a 400, never rounded. The maximum is 2,000,000,000 kobo (NGN 20,000,000). |
| One error shape | Every failure is { "code", "message", "details" }. details is a list of plain-words problems on a 400. Server faults never reveal internals. |
| Ids you generate | An entry’s id is a UUID created by the client. Sending the same entry twice never duplicates it. Ids are only unique per person, so one user can never collide with, or probe for, another’s. |
| Your timezone | Today, this week (Monday to Sunday) and this month are worked out in the person’s own timezone (PATCH /v1/me), not the server’s. |
| Dates | ISO 8601 with a time and offset, for example 2026-10-16T09:30:00Z. An entry cannot be dated more than a day in the future. |
| Paging | Lists use a cursor: pass the nextCursor you were given as ?cursor=. Paging is stable even when many entries share a timestamp. |
| Soft delete | Deleting an entry hides it everywhere but keeps the row, so offline devices can learn about the deletion. Repeating a delete is safe. |
| Unknown fields are rejected | Sending a field the endpoint does not know (for example userId or role) is a 400. This is deliberate. |
| You only ever see your own data | A request for someone else’s record behaves exactly like a request for one that does not exist. |
What exists today
| Area | Endpoints |
|---|---|
| Health | GET /health, GET /health/ready (no sign-in needed) |
| Me | GET /me, PATCH /me (name, language, income pattern, timezone only) |
| Entries | GET /entries (filters, search, paging), POST /entries, GET/PATCH/DELETE /entries/{id}, POST /entries/sync |
| Accounts | GET/POST /accounts, PATCH/DELETE /accounts/{id} (labels you keep; never bank links) |
| Categories | GET/POST /categories, PATCH/DELETE /categories/{id} (23 built-in Nigerian categories plus your own) |
| Capture | POST /capture/text, GET /capture/{id}, POST /capture/{id}/confirm |
| Budgets | GET/POST /budgets, PATCH/DELETE /budgets/{id}, GET /budgets/safe-to-spend |
| Insights | GET /insights/summary, GET /insights/weekly |
| Habits | GET /habits |
| Notifications | GET /notifications/preferences, PATCH /notifications/preferences/{type}, POST/DELETE /push/subscribe |
| Privacy | GET /me/consents, PUT /me/consents/{purpose}, GET /me/export, DELETE /me |
What does not exist yet
These need an outside account, a provider decision or real data first, and are Planned:
| Feature | Waiting for |
|---|---|
Voice logging (/capture/voice) | A speech-to-text provider, and a set of real voice notes to measure accuracy |
| WhatsApp logging and linking | A WhatsApp Business number and Meta account |
Forwarded bank-alert parsing (/capture/alert) | Real alert samples from each bank, so the parsers are not invented |
Receipt photos (/capture/receipt) | Phase 2 |
The AI assistant (/assistant/messages) | An AI provider and the PII-stripping gateway |
| Sending push and email reminders | A job queue (Redis, or an alternative) and an email provider. The rules for when to send, and the at-most-once guarantee, are built and tested |
| Groups (ajo), organisations, billing | Phase 2 |
Calling it from the web app
The web app never calls the API from the browser with a token. It signs in on the server, keeps the session in a secure cookie, and its own server forwards calls to the API. See ADR 0005.
Last updated on