No description
  • Rust 92%
  • Shell 7.1%
  • PLpgSQL 0.5%
  • Dockerfile 0.4%
Find a file
Kerry Hatcher dfea074e34
All checks were successful
build / image (push) Successful in 4s
docs(claude-md): point the superproject link at Forgejo, not the stale GitHub mirror
The GitHub repo is deliberately kept as a mirror, but its main is frozen at
b08f1f0 (pre-thor-deployment) with a .gitmodules that still points every
submodule back at GitHub — the same trap finding 3 caught in install.md.
An agent following this link lands on stale code, not the current tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGisjtKSjUcVGw1jX1Hf8M
2026-08-07 13:39:27 -04:00
.forgejo/workflows ci: build and publish the api image with podman 2026-08-07 02:24:11 -04:00
migrations feat(oauth): device authorization and the device_code grant 2026-08-05 13:08:19 -04:00
src docs(oauth): correct the .well-known path-insertion comment for API_ORIGIN=/api 2026-08-07 12:30:21 -04:00
tests fix: let user rm remove someone who has posted 2026-08-06 01:18:16 -04:00
.dockerignore ci: build and publish the api image with podman 2026-08-07 02:24:11 -04:00
.gitignore feat: double-entry ledger API with postgres and meeting reports 2026-08-03 13:33:56 -04:00
Cargo.lock feat: build a webauthn relying party at boot 2026-08-03 22:54:09 -04:00
Cargo.toml feat(oauth): reap expired codes, tokens, sessions, and challenges 2026-08-05 23:12:47 -04:00
CLAUDE.md docs(claude-md): point the superproject link at Forgejo, not the stale GitHub mirror 2026-08-07 13:39:27 -04:00
compose.yaml refactor: publish postgres on 38083 2026-08-03 15:53:21 -04:00
Dockerfile fix(ci): fully qualify Dockerfile base images 2026-08-07 02:29:34 -04:00
LICENSE Initial commit 2026-08-03 12:58:06 -04:00
README.md refactor: publish postgres on 38083 2026-08-03 15:53:21 -04:00
script-auth.sh feat(seed): two users and both token states 2026-08-06 00:09:00 -04:00
seed.sh feat(seed): two users and both token states 2026-08-06 00:09:00 -04:00
smoke.sh feat(oauth): add RFC 7009 token revocation 2026-08-05 21:52:58 -04:00

openbooks-api

Rust API for a family or club to track a bank account, cash, and expenses on a real double-entry ledger — without anyone needing to know what double-entry means.

Very early POC: no auth, no RBAC, CORS wide open. Don't put real money data in it.

Run it

docker compose up -d          # postgres on host port 38083
cargo run                     # migrations run on startup; listens on :38081
./smoke.sh                    # end-to-end check (needs jq)

Config via env: DATABASE_URL, BIND_ADDR.

How the ledger works

One table of entries, each with a signed amount_cents: debit is positive, credit is negative. A transaction is balanced when its entries sum to zero, and a deferred constraint trigger in Postgres enforces that — so no API bug can write crooked books.

Money is stored as integer cents. No floats anywhere.

Reports flip the sign on income, liability, and equity accounts so every statement line reads as a positive number.

Endpoints

Method Path Notes
GET /health
GET /accounts
POST /accounts {name, kind}, kind is asset/liability/equity/income/expense
GET /transactions?from=&to= entries nested per transaction
POST /transactions see below
DELETE /transactions/{id} removes both legs
GET /reports/balances?from=&to= balance per account
GET /reports/income-statement?from=&to= the meeting report: money in, money out, net
GET /reports/balance-sheet?as_of= what we own vs. owe
POST /mcp MCP endpoint, see below

Recording $45 of pizza paid from checking:

curl -X POST localhost:38081/transactions -H 'content-type: application/json' -d '{
  "occurred_on": "2026-03-05",
  "description": "Meeting pizza",
  "entries": [
    {"account_id": 7, "amount_cents":  4500},
    {"account_id": 1, "amount_cents": -4500}
  ]
}'

Unbalanced or single-legged transactions come back 400.

MCP

POST /mcp speaks MCP over Streamable HTTP, so an assistant can read the books and record transactions. Six tools: list_accounts, account_balances, income_statement, balance_sheet, list_transactions, and record_transaction.

Point a client at http://localhost:38081/mcp — the repo's .mcp.json already does. Request/response only; there's no SSE stream, so GET /mcp returns 405.

Each tool calls the same function as the matching HTTP handler, so the two interfaces can't disagree about the ledger — smoke.sh asserts they return identical numbers. record_transaction goes through the same validation, so unbalanced entries come back as JSON-RPC -32602 rather than being written.

curl -s localhost:38081/mcp -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq -r '.result.tools[].name'

Same no-auth caveat as the rest of the API: anything that can reach the port can write to the books.

Chart of accounts

Migration 0002 seeds Checking Account, Cash Box, Dues, Donations, Fundraising, Supplies, Food, Rent, Fees, and Opening Balance. Add your own via POST /accounts.