Remote Bookkeeping API
Last updated: May 10, 2026
Log entries from cURL, iOS Shortcuts, or automation scripts using a personal API token. The token is write-scoped — it can insert transactions and read the category / tag lists, but it cannot read or delete any transaction data.
1. Get a token
Sign in as an admin, open Settings → 🔑 Remote Bookkeeping API, and click "Generate token".
The plaintext token is shown exactly once — copy it immediately. The server only stores its SHA-256 hash, so a lost token can only be replaced by rotating (which invalidates the old one).
Token format: bb_<userId>_<secret>. Send it on every request as Authorization: Bearer <token>.
2. POST /api/ingest — insert one transaction
Request body is a JSON object with these fields:
title(string, required) — transaction nameamount(number, required) — must be positivetype(string, optional, defaultexpense) — one ofexpense/income/prepaid_expense/pending_incomecategory(string, optional) — category namenote(string, optional) — free-form notecreated_at(string, optional) — ISO 8601; defaults to now
curl -X POST https://billbook.me/api/ingest \
-H "Authorization: Bearer bb_<userId>_<secret>" \
-H "Content-Type: application/json" \
-d '{"title":"Coffee","amount":120,"type":"expense","category":"Food"}'
# response: 201
{ "id": "...", "created_at": "..." }3. GET /api/ingest/categories — list categories
Returns the admin's category names in dashboard dropdown order. Response contains only the names — no ids, user ids, or transaction data.
curl https://billbook.me/api/ingest/categories \
-H "Authorization: Bearer bb_<userId>_<secret>"
# response: 200
{ "categories": ["Food", "Rent", "..."] }4. GET /api/ingest/tags — recently used tag prefixes
Returns the most recently updated tag prefixes (the #XXX markers embedded in titles). Defaults to 20 entries; pass ?limit= to override (max 100).
Returns an empty array instead of an error if the tag_prefixes table doesn't exist yet.
curl "https://billbook.me/api/ingest/tags?limit=20" \
-H "Authorization: Bearer bb_<userId>_<secret>"
# response: 200
{ "tags": ["#management-fee", "#travel", "..."] }5. Error codes
All errors are JSON: { "error": "message" }.
401— missing authorization, malformed token, or token revoked / mismatched402— subscription expired; renewal required to write405— method not supported (e.g. GET on/api/ingest)501— server is missingSUPABASE_SERVICE_ROLE_KEY500— other internal error
6. Security and limits
By design, the token can only write to its own organization — it cannot impersonate other orgs or read existing data:
user_idis always set to the token owner; it can't be forgedorg_idis always the owner's org; cross-org writes are impossible- Tokens are only issued to admin role (manager / staff get 403)
- Responses never leak other rows — only
{ id, created_at }is returned - Revocation is immediate; rotate any time from Settings
7. iOS Shortcut example
Add a "Get Contents of URL" action, set URL to https://billbook.me/api/ingest, method POST, headers Authorization: Bearer bb_... and Content-Type: application/json, and request body as JSON: {"title":"Coffee","amount":120,"type":"expense"}. Wire title and amount to Shortcut input or Ask Each Time variables.
This document is a quick reference; the backend code is the source of truth for actual behavior.