Base: the server’s own address (locally http://127.0.0.1:8000 with dev.sh, or :8080 for the compose
stack). Every route needs Authorization: Bearer <session token> except those marked “no sign-in”. In a
browser, only the server’s own origin is allowed (403 origin_not_allowed otherwise). The OpenAPI schema is at
/v1/openapi.json.
Conventions: money in integer minor units; ids are UUIDs; editable rows carry version (mismatch → 409); creates
take a client-made id and are idempotent; errors are {"detail": "<code>"}. Within /v1 routes and fields are
only added.
The server and reference data
Method
Path
Notes
GET
/v1/health
{"ok":true}; no sign-in
GET
/v1/instance
Name, server_version, api_version, signup (invite/open), setup_needed; no sign-in. Clients check it first
GET
/v1/currencies
ISO 4217 codes and decimals
Getting in (no sign-in)
Method
Path
Notes
POST
/v1/setup
The first account, with the setup code from the log; becomes admin. Optional server_name
{"disabled": true/false}; disabling ends their sessions
POST
/v1/admin/users/{id}/reset
A one-time reset token (24 hours)
GET, POST
/v1/admin/invites
POST: note, days (1 to 30), server (the admin’s origin) → code, link, QR
DELETE
/v1/admin/invites/{id}
Revoke
GET
/v1/admin/update
Running and newest version, whether the updater runs, its last status
POST
/v1/admin/update
Ask the updater to update now (409 updater_off without it)
Books and their contents
Method
Path
Notes
GET, POST
/v1/books
POST creates with starter categories unless unticked
GET, PATCH, DELETE
/v1/books/{book_id}
GET
/v1/books/{book_id}/dashboard
Everything the home screen shows, in one call, with rates_source (some sources need a credit)
GET, POST
/v1/books/{book_id}/accounts
POST takes an opening balance and optional contact_id
PATCH
/v1/books/{book_id}/accounts/{id}
Includes archive
GET, POST
/v1/books/{book_id}/categories
PATCH, DELETE
/v1/books/{book_id}/categories/{id}
DELETE archives a used category
GET, POST
/v1/books/{book_id}/transactions
Filters: account_id (with running balance), category_id (with sub-categories), from, to, type, q; pages by after
GET, PUT, DELETE
/v1/books/{book_id}/transactions/{id}
GET
/v1/books/{book_id}/transactions/{id}/history
Every saved version
GET, POST
/v1/books/{book_id}/contacts
With each person’s balances
PATCH, DELETE
/v1/books/{book_id}/contacts/{id}
DELETE archives a linked person
GET, POST
/v1/books/{book_id}/budgets
With current progress
PATCH, DELETE
/v1/books/{book_id}/budgets/{id}
Sharing a book
Method
Path
Notes
GET
/v1/books/{book_id}/members
Every member: name (username or “Deleted user”), display_name, role, pending, me
POST
/v1/books/{book_id}/members
Owners: username, role (default editor) → the member list. 409 cannot_invite for an unknown username and for someone already in the book, alike; rate limited
PATCH
/v1/books/{book_id}/members/{user_id}
Owners: role. 409 owner_required for the last owner
DELETE
/v1/books/{book_id}/members/{user_id}
Owners remove anyone or cancel an invite; anyone leaves with their own id. 409 owner_required for the last owner
GET
/v1/me/book-invites
Invites I haven’t answered: book, role, who invited me
POST
/v1/me/book-invites/{book_id}/accept
Become a member
DELETE
/v1/me/book-invites/{book_id}
Decline; the invite goes away, nobody is told
Source: msaving/backend/app/api/routes/v1/. This page is the list: a new route comes with a paired docs PR that
adds it here.