← Back to Billbook

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 name
  • amount (number, required) — must be positive
  • type (string, optional, default expense) — one of expense / income / prepaid_expense / pending_income
  • category (string, optional) — category name
  • note (string, optional) — free-form note
  • created_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 / mismatched
  • 402 — subscription expired; renewal required to write
  • 405 — method not supported (e.g. GET on /api/ingest)
  • 501 — server is missing SUPABASE_SERVICE_ROLE_KEY
  • 500 — 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_id is always set to the token owner; it can't be forged
  • org_id is 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.