API Docs¶
braatzledger API, version v1. 23 endpoints, listed below and machine-readable at /openapi.json (OpenAPI 3.1).
One list, no second
This page, /openapi.json and the MCP tool set are all generated from the
same endpoint index the server serves at /api/v1. None of them
can name an endpoint the others don't.
Authentication¶
Authorization: Bearer
Writes¶
POST with Content-Type: application/json; a read-only token is refused (403); the same capability functions the rooms call, the actor recorded as human:
Endpoints¶
GET /api/v1/me¶
MCP tool: get_me
the books' summary: lines, booked, archived, pending (listed, not yet posted, in no total), total_cents, by_category, and auth (which credential let you in)
Parameters: —
GET /api/v1/transactions¶
MCP tool: list_transactions
rows of C, paged; total_cents is over the WHOLE matched set, never the page; each row carries in_books, exclude_reason and state (in books | pending | superseded | retracted | archived) -- a pending line is listed and in no total
Parameters: from — ISO date, inclusive lower bound on posted_onto — ISO date, inclusive upper boundcat — exact category label; 'uncategorised' for the queueq — substring of the descriptionpage — 1-basedper_page — rows per page, capped at 100
GET /api/v1/transactions/{side}/{src_id}¶
MCP tool: get_transaction
one line, its REQ-8 lineage in words, and for an A line the provider's verbatim record (raw)
Parameters: side (in path) one of A, B — A = the feed's line, B = its archived twinsrc_id (in path) — the line's id on that side
GET /api/v1/accounts¶
MCP tool: list_accounts
the sub-accounts the feed has delivered lines for; pending is counted from the BOOKS (lines the feed's own judgement still holds as pending), so a line that has since posted is not in it
Parameters: —
GET /api/v1/sync-history¶
MCP tool: list_sync_history
bank-sync runs, newest first
Parameters: limit — runs to return, capped at 200
GET /api/v1/rules¶
MCP tool: list_rules
the live rulebook, retired revisions included, in the order it DECIDES: the bottom row wins when two rules match a line (priority descending -- the matcher's walk, reversed)
Parameters: —
GET /api/v1/exceptions¶
MCP tool: list_exceptions
lines no live rule and no live op has explained
Parameters: —
GET /api/v1/categories¶
MCP tool: list_categories
the tenant's own categories as the books use them, each with its booked total_cents and line count, most money first; 'uncategorised' is the queue (there is no house chart of accounts here: a label exists once a line carries it)
Parameters: —
GET /api/v1/taxonomy¶
MCP tool: list_category_taxonomy
the tenant's category TAXONOMY as the New category dialog defines it -- name, cat_key, header_key, header_name and the header's sort_order -- live revisions only (a retired category is one the books have stopped deriving, and offering it back is offering a word the taxonomy has retracted), ordered by header then name so it renders the same way twice. This is what create_category and create_categories write into, and it answers whether or not any line carries the label yet; list_categories is the BOOKS' aggregate and answers only labels lines actually carry, with their totals. 404 when this deployment's tap does not request the categories read
Parameters: —
GET /api/v1/usage¶
MCP tool: get_usage
the two meters a plan would bill on (inventory E4): bank connections in use against the cap this surface enforces, and transaction volume over a trailing window -- the LARGER of money in and money out, never their sum and never the net, which is the basis the meter measured on 2026-09-01 uses
Parameters: days — trailing window in days, default 30, capped at 365
GET /api/v1/reports/pnl-rolling¶
MCP tool: get_pnl_rolling
the Daily Rolling P&L as STRUCTURE, not prose: two windows (7d, 30d), each with the same six sections (revenue, cogs, gross_margin, biz_expenses, net_income, owners_draw) carrying current and prior, their category lines, and every booked transaction that contributed to each line. gross_margin and net_income are DERIVED (derived: true) and re-carry their components' lines with the sign each component enters with, so the twelve section current numbers are what a coefficient compares -- do not also sum the derived sections' lines. lineage_basis says whether the paper-verified lineage was readable (it is unreadable from this door: v_c_ledger does not carry reconciled_state, which changes no section number); lists_source names which category lists classified the rows
Parameters: tenant — must be the credential's own tenant; omit it and the credential decidesas_of — ISO date the two windows end on, in America/Chicago; default today
POST /api/v1/reports/pnl-parity¶
MCP tool: record_pnl_parity
grade the DIY n8n oracle's Daily Rolling P&L against braatzledger's own and file ONE deterministic verdict for the day: the difference coefficient over the twelve section numbers, the per-section deltas, and the localization of every delta down to the transactions that explain it (RECATEGORIZED / AMOUNT_DIFF / WINDOW_EDGE / MISSING_ON_BRAATZ / MISSING_ON_ORACLE). POST oracle alone and this surface computes ITS OWN side from its own books -- our numbers are not something a caller gets to supply. braatz may be sent anyway, and is then CONFIRMED rather than used: if its twelve section numbers disagree with what these books say, the request is 409 and nothing is filed. A second POST for the same day UPSERTS and returns a higher revision
Parameters: oracle required — the ORACLE's STRUCTURED report -- what GET /api/v1/reports/pnl-rolling emits in shape, computed by the n8n workflow. A rendered card or the n8n node output is 400: the comparator needs section -> category -> transactionbraatz — optional, and an ASSERTION rather than an input: this surface recomputes its own side and 409s on a disagreementas_of — ISO date the windows end on; default the oracle report's own as_of, else today in America/Chicago
POST /api/v1/categorize¶
MCP tool: categorize
set one line's category -- an APPENDED judgement in the op ledger, never an edit; 'uncategorized' unsets; the archive side (B) is refused by the ledger
Parameters: side required one of A, Bsrc_id required — the line's id on that sidecategory required
POST /api/v1/adjudicate¶
MCP tool: adjudicate
resolve ONE exception and mint a versioned rule from it (REQ-8): the line gets its category and every future match is decided by the rule
Parameters: a_id required — the exception's A-side idrule_key required — your name for the rule; a new revision if it existsmatch_kind required one of description_exact, description_regex, amount_signmatch_value requiredcategory requiredpriority — lower decides first; default 100
POST /api/v1/run-rules¶
MCP tool: run_rules
run every live rule over the uncategorised lines; returns how many it decided (a second run decides 0 -- idempotent by construction)
Parameters: —
POST /api/v1/move-rule-to-end¶
MCP tool: move_rule_to_end
make ONE rule the decider for every line it matches (fintable's 'move to end'): an APPENDED revision whose priority is below every other live rule's, never an edit; a rule already deciding appends nothing; touches no line already decided -- the next run-rules is where it shows
Parameters: rule_key required — the rule's key as list_rules shows it
POST /api/v1/retire-rule¶
MCP tool: retire_rule
switch ONE rule off (fintable's 'Delete this rule'): an APPENDED revision with retired=true, never a delete -- the transactions it already decided KEEP their categories, it decides no further lines, and the retired revision stays in list_rules so "we turned it off" never looks like "there was never a rule"
Parameters: rule_key required — the rule's key as list_rules shows it
POST /api/v1/new-category¶
MCP tool: create_category
add ONE category to the tenant's taxonomy under an existing header (fintable's New category dialog): an appended definer call, the ledger's own refusal relayed verbatim when the name already lives under another header; 404 when this deployment's tap does not request the verb
Parameters: name required — the category as the books will spell itheader required one of cogs, expense, income, draw, transfer — the header key it groups under
POST /api/v1/new-categories¶
MCP tool: create_categories
add SEVERAL categories in one call (fintable's create_bulk_categories): every row is validated BEFORE anything is written, then appended in order -- the response carries created (the rows that landed, with their ids) and stops at the first ledger refusal, which it names
Parameters: categories required — objects with name and header, as POST /api/v1/categories takes them, at most 100
POST /api/v1/create-rule¶
MCP tool: create_rule
file a rule with NO transaction to hang it on (fintable's Create rule; G1's ledger.create_rule): it decides every matching line nobody has already decided, and every future match -- born_from_a_id NULL, which adjudicate can never produce; an APPENDED revision when the key already exists, never an edit (ruling #135)
Parameters: rule_key required — your name for the rule; a new revision if it existsmatch_kind required one of description_exact, description_regex, amount_signmatch_value requiredcategory requiredpriority — lower decides first; default 100
POST /api/v1/create-rules¶
MCP tool: create_rules
file SEVERAL rules in one call (fintable's create_bulk_rules): every row is validated BEFORE anything is written, then appended in order -- the response carries created and stops at the first ledger refusal, which it names
Parameters: rules required — objects with rule_key, match_kind, match_value, category and optional priority, as POST /api/v1/rules takes them, at most 100
POST /api/v1/export¶
MCP tool: export_books
push the books to their destination (fintable's Force Re-Sync / resync_spreadsheets): when a spreadsheet destination is connected this APPENDS a resync op against it and returns the op id; otherwise it renders the CSV bundle ledgerExport ships and returns its receipt -- the row counts and the two URLs the same books are downloaded from, plus destinations: known when the destination list was read and found empty, unreadable when it could not be read at all -- the CSV bundle is real either way, but only known means nothing was connected. Booked lines only; pending is never exported
Parameters: destination_id — which connected destination to re-sync; the newest live one by default
POST /api/v1/sync-now¶
MCP tool: sync_now
ask for a bank sync now: 202 with state recorded|already-pending (one request per tenant at a time), 409 when nothing is connected; it is a REQUEST the watcher consumes -- list_sync_history is what says it ran
Parameters: —
In build now¶
Named rather than drawn, so the list above is only what answers today:
- public data API
- OAuth app registry
- disconnect a bank -- one-way at the provider, so it stays the Accounts room's two-click form and is not an API verb