MCP reference
LedgerMCP is an MCP server: your AI agent is the bookkeeper. Point any MCP client at
https://ledgermcp.com/mcp with an API key from Settings → Agent access as a Bearer token.
One key covers your whole account; read-only keys can run every list and report but never write.
This page is generated from the live tool registry.
Connect
Two ways in: sign in (OAuth — nothing to paste, no key to manage) or a Bearer key from Settings → Agent access.
claude.ai — Settings → Connectors → Add custom connector → https://ledgermcp.com/mcp, then sign in when prompted.
ChatGPT — enable Developer mode, then Settings → Connectors → create → point it at https://ledgermcp.com/mcp and sign in. Custom MCP connectors need a paid plan (Plus/Pro/Business/Enterprise) with connectors enabled.
Claude Code / Desktop and other MCP clients — paste a key:
{
"mcpServers": {
"ledgermcp": {
"type": "http",
"url": "https://ledgermcp.com/mcp",
"headers": { "Authorization": "Bearer lmcp_YOUR_KEY" }
}
}
}
Ground rules
Amounts are integer cents (positive = money out of the bank). Dates are YYYY-MM-DD. Postings are immutable — corrections are reversals, never edits. Posting writes take an idempotency key, so retries are safe. Every write is audit-logged and one-click reversible by the account owner.
Read tools (26)
list_businesses
List every business (set of books) in this account. Archived (deactivated) businesses are hidden by default; pass include_archived to see them too — their books remain reachable by id.
include_archived | true = include archived (deactivated) businesses |
list_accounts
Chart of accounts for a business.
business_idrequired | Which business (books) to operate on — see list_businesses |
type | — |
list_transactions
Feed transactions (bank/CSV/manual staging rows) with status and posting info. Unposted rows may carry suggested_category_account_id — the category this merchant was LAST posted to (learned from the books, updates on every correction). Treat it as a strong prior for categorize, not an order; still sanity-check the amount/description. Paginates via cursor/limit (max 200). Rows may carry client_note: a review-only member explaining what a charge was; trust it when categorizing.
business_idrequired | Which business (books) to operate on — see list_businesses |
status | — |
account_id | Filter to one bank account |
date_from | — |
date_to | — |
search | — |
tag_id | only rows carrying this tag — see list_tags |
category_id | only rows posted to this category account |
amount_min_cents | absolute amount lower bound, cents |
amount_max_cents | absolute amount upper bound, cents |
flagged_duplicate | — |
flagged_transfer | — |
holding_area | true = personal-mode holding area only |
excluded | true = ONLY soft-excluded (duplicate-resolved) rows — they're hidden otherwise; they carry reviewed:true, so combine with status:'all' |
fields | compact = id/date/amount/description/posted/reviewed only (~10x fewer tokens) — use it for sweeps and triage, then get_transaction for the rows you act on |
cursor | — |
limit | — |
get_transaction
Full detail for one transaction, including attachments and audit history.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | — |
get_balance
Computed balance of one ledger account (from postings, never stored).
business_idrequired | Which business (books) to operate on — see list_businesses |
account_idrequired | — |
as_of | — |
run_pnl
Profit & loss for a date range. compare_previous adds the preceding period: a calendar month/quarter/year range compares to the previous month/quarter/year (whole units, honouring month lengths); any other range compares to the immediately preceding window of the same day-length. Pass chart:true to also return an inline bar chart of the biggest expense categories — great when presenting the P&L to the user (skip it for pure number-crunching to save tokens).
business_idrequired | Which business (books) to operate on — see list_businesses |
date_fromrequired | — |
date_torequired | — |
compare_previous | — |
compare | 'previous' = the immediately preceding same-length period; 'yoy' = the SAME window one year earlier |
chart | also render an inline expenses-by-category bar chart (image) — use when showing the user |
export_report
Hand the owner (or their CPA) a downloadable report. format 'csv' returns the report's CSV text (works for every report type below). format 'link' mints a signed, EXPIRING public web link to a nicely-formatted report they can open, print to PDF, or email to their accountant (pnl, balance-sheet, schedule-c only). Date args by report: pnl/tags use date_from+date_to; balance-sheet/trial-balance use as_of; 1099/schedule-c use year. Omitted dates default to a sensible range (all-time / today / current year).
business_idrequired | Which business (books) to operate on — see list_businesses |
reportrequired | — |
formatrequired | 'csv' = raw CSV text; 'link' = a signed printable/shareable URL (pnl, balance-sheet, schedule-c only) |
date_from | — |
date_to | — |
as_of | — |
year | tax year for 1099 / schedule-c |
expires_days | link expiry (default 30) |
run_balance_sheet
Balance sheet as of a date.
business_idrequired | Which business (books) to operate on — see list_businesses |
as_ofrequired | — |
run_trial_balance
Trial balance: every account as a debit or credit balance at a date, with totals (they always agree — the DB enforces balance).
business_idrequired | Which business (books) to operate on — see list_businesses |
as_of | — |
get_account_transactions
The general-ledger register for one account: postings (date, memo, debit/credit, reversal flag) in a date range — most recent 500; narrow the date range on busy accounts, the cap is silent. hide_reversed drops fully-cancelled pairs (an entry and its reversal net to zero — e.g. a mistake and its undo); totals are unaffected either way.
business_idrequired | Which business (books) to operate on — see list_businesses |
account_idrequired | — |
date_from | — |
date_to | — |
hide_reversed | true = omit entries that were reversed and the reversals themselves |
run_schedule_c
Sole-proprietor tax prep: the year's P&L regrouped by IRS Schedule C line. Returns gross receipts, per-line expense totals, and UNMAPPED expense accounts each with a suggested_line — map them with update_account {schedule_c_line} (confirm suggestions with the user; category-to-line mapping is a tax judgment the OWNER'S preparer owns, not you). Mapping is presentation only; postings are untouched.
business_idrequired | Which business (books) to operate on — see list_businesses |
year | tax year; defaults to the current year |
run_1099_report
Year-end 1099 report: total paid per vendor-kind tag, with over_threshold and missing-W9 flags. The reporting threshold is year-dependent (returned as threshold_cents: $2,000 for 2026+, $600 for 2025 and earlier — read the per-tag over_threshold flag, don't assume a fixed figure). `vendors` covers only tags classified kind:'vendor'; `unclassified` lists kindless tags that received money-out payments that year (same shape) — classify the real payees with update_tag.
business_idrequired | Which business (books) to operate on — see list_businesses |
yearrequired | — |
run_tag_report
Per-tag money totals in a date range: money out, money in, net, and transaction count for every tag with activity (pending and soft-excluded rows ignored). Omit the dates for all time.
business_idrequired | Which business (books) to operate on — see list_businesses |
date_from | — |
date_to | — |
list_tags
Tags — the entities money is tracked against (vendors, projects, clients). Includes kind (vendor/project/client, null = general), W-9 status, and transaction counts; per-tag money totals come from run_tag_report.
business_idrequired | Which business (books) to operate on — see list_businesses |
search | — |
list_reconciliations
Reconciliation history (newest first): each row is one statement tie-out with statement vs book balances and the difference (0 = tied out AT THE TIME). Rows with statement_filename carry the pinned statement file. A difference-0 row proves the period as of when it ran — but a later back-dated entry can silently break it, so treat proven periods as settled unless verify_books' reconciliations_still_tie check flags one; re-run reconcile_account for any it names.
business_idrequired | Which business (books) to operate on — see list_businesses |
bank_account_id | — |
list_audit_log
Append-only log of agent writes (actor, tool, args, resulting entry, reversal state). Returns {entries, next_cursor}; page with cursor/limit (max 200, newest first). Filter by entry_id or transaction_id to trace exactly what touched one entry or row (e.g. why it was reversed).
business_idrequired | Which business (books) to operate on — see list_businesses |
actor | — |
entry_id | — |
transaction_id | — |
date_from | — |
date_to | — |
cursor | — |
limit | — |
list_bank_accounts
Bank accounts (Plaid-connected and offline) with ledger balances and unreviewed counts — the targets for imports and manual adds. Connected accounts also carry bank_reported_balance_cents + bank_balance_as_of: the BANK'S OWN claimed balance cached at each sync (for credit accounts, the amount owed). Compare it with balance_cents (the ledger's computed figure); when they disagree, run reconcile_account to find out why — never 'fix' the ledger to match by posting a plug.
business_idrequired | Which business (books) to operate on — see list_businesses |
list_conventions
The book's OPERATING MANUAL: durable, book-specific rules written by past sessions (yours and other agents') — 'Zelle to Irina on the 1st is Rent', 'HOSTINGER charges get the Business tag'. The rules that keep any agent on any device booking this business the same way.
business_idrequired | Which business (books) to operate on — see list_businesses |
list_questions
Open questions on this book — things a past session (any agent's) needed the human to answer: the mystery wire, the unlabeled deposit. Relay open ones, and record the human's answer with resolve_question so later sessions have it instead of re-asking. status:'all' includes answered/dismissed history (answers are durable artifacts).
business_idrequired | Which business (books) to operate on — see list_businesses |
status | default 'open' |
verify_books
Books health audit: independently re-derives the ledger invariants (every entry balances, trial balance agrees, accounting equation holds, no stale/orphaned links) straight from the rows. Returns {ok, checks:[{name, ok, detail}]}. NECESSARY, NOT SUFFICIENT: green proves internal consistency only — books can balance perfectly and still not match the bank. reconcile_account per account against a statement ending balance is what ties the books to reality. Run both after big cleanups.
business_idrequired | Which business (books) to operate on — see list_businesses |
find_matches
Match candidates for one transaction: possible duplicates (same account/amount, ±3 days), transfer counterparts (opposite amount, other account, ±5 days, unposted), posted_transfer_counterparts (same shape but ALREADY posted as a transfer — the late-arriving-leg case), and split_transfer_candidates (this row equals the SUM of several own-account legs — e.g. one payment paying down two loans; each candidate lists leg_transaction_ids). Suggest-only — act with resolve_duplicate, confirm_transfer, or record_transfer; for a posted counterpart, record_transfer with counterpart_transaction_id attaches this row to the existing entry (no new posting); for a split_transfer_candidate, record_transfer with counterpart_transaction_ids books the one combined transfer.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | — |
get_receipt
Retrieve the receipt(s) filed against a transaction — each attachment with its filename and a short-lived (5-minute) signed download URL you can fetch or hand to the user. The counterpart to attach_receipt: use it to pull up proof of an expense ('show me the receipt for that charge'). get_transaction reports the attachment COUNT; this returns the actual files. [] means none attached.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | — |
budget_report
This month’s budgets with live progress: target, spent, carryover (if rollover), and available per category. Overspend is information, not an error — help the user adjust, never scold.
business_idrequired | Which business (books) to operate on — see list_businesses |
list_recurring
Detected recurring charges and income (subscriptions, rent, paychecks): cadence, expected amount, next due date, monthly recurring total, recurring_income, double_charges, and price_changes (latest occurrence vs the one before — surface these to the user, they are the most-loved alert in personal finance). Detection is conservative (≥3 steady occurrences); confirm or dismiss items with resolve_recurring. CONFIRMED items carry missed:true (+ missed_due) when a due date passed with no matching row — a bill that did not arrive is worth telling the user about immediately; check whether it moved, failed, or was cancelled.
business_idrequired | Which business (books) to operate on — see list_businesses |
include_dismissed | also return dismissed items (to find an id and re-confirm it) |
cash_calendar
Daily cash and credit balances: the last 45 days of ACTUALS (real ledger balances, each day's real transactions attached, on_card marking card activity) then a 30-day forward projection — cash walks detected recurring bills/paychecks; the credit line walks card-based recurring charges and detected card payments (a payment moves BOTH lines down). Returns {start_balance_cents, credit_owed_cents, days:[{date, balance_cents, credit_cents, projected, events}], lowest:{date, balance_cents} (forward cash minimum — the "will I make rent" answer)}. Deterministic — everyday future spending is NOT modeled; treat the projection as the floor and layer what-ifs yourself. Pass chart:true to also return the balance-over-time line chart as an inline image (solid = actuals, dashed = projection) — ideal when showing the user their runway.
business_idrequired | Which business (books) to operate on — see list_businesses |
chart | also render the daily balance line chart (image) — use when showing the user |
list_assets
Fixed-asset registry: each asset with cost, useful life, monthly straight-line amount, accumulated depreciation, current book value, and months_behind (unbooked depreciation months due).
business_idrequired | Which business (books) to operate on — see list_businesses |
Write tools (37)
report_issuewrite
Report a bug, request a feature, or ask the LedgerMCP team a product question — straight from real usage. Reach for it the moment a tool behaves wrong or is missing something, or when the user wishes the product did X ('I wish it could…'). This goes to the LedgerMCP team, NOT the book owner (that's ask_user) — it's how the product gets fixed and improved, so when something's rough, say so here. NEVER put the user's financial figures or personal info in it unless they explicitly ask you to.
kindrequired | — |
titlerequired | one-line summary |
detailrequired | what happened / what is wanted — include the steps and the tool if it is a bug |
context | optional: the tool involved + a severity. No financial data or PII. |
allocate_transactionwrite
This transaction belongs to ANOTHER book you manage. TWO modes, picked by direction: • PERSONAL → BUSINESS ('move to business'): OMIT target_category_account_id. The personal row posts a clean move-out (DEBIT 'To business' equity / CREDIT the personal bank — no personal expense, no receivable), and a NEW uncategorized transaction lands in the business book's review queue for you to categorize THERE (it credits the business's 'Personal accounts' equity). Use this for a business cost paid from a personal account. • BUSINESS → BUSINESS (allocate): PASS an expense category in the target. This book books 'Owed to me' (a receivable) and the target posts expense ↔ Owner's Contribution. Money-OUT rows only; owner/full access to BOTH books; personal → personal is NOT allowed. Works on an already-categorized row (auto-reverses first; a TRANSFER leg must be un-transferred first). Response carries the ids.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | — |
target_business_idrequired | the book this spend belongs to (list_businesses) |
target_category_account_id | BUSINESS→BUSINESS only: an EXPENSE category in the TARGET book. OMIT for personal→business — you categorize it in the business afterward. |
memo | — |
idempotency_keyrequired | — |
record_transferwrite
Record one feed row as a transfer to/from another own account. PASS counterpart_transaction_id whenever you know the other leg (get it from find_matches or list_transactions) — same-amount rows are common and the automatic match can link the wrong one. Named row must be on the counterpart account and equal-and-opposite in amount. If the named row is ALREADY POSTED as a transfer that covers this account (a card payment posted from checking before the card feed existed), this row ATTACHES to that entry — nothing double-posts; the response carries attached_to_existing:true. Otherwise the named row must be unposted. Without a named row: auto-links a matching row when one exists, else posts the bank↔bank entry against this leg alone (never income/expense). Response echoes linked_counterpart — check it. SPLIT TRANSFER: when ONE payment fans out to SEVERAL of your own accounts (e.g. a single debit that pays down two loans at the same bank), pass counterpart_transaction_ids (the rows on those accounts, from their feeds) instead — their amounts must sum to this row, and one balanced transfer entry is posted linking every row. Legs already booked as their own separate transfers are folded in automatically; a leg posted as a category/split must be reversed first. Response echoes linked_counterparts.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | the SOURCE row (the single bank movement) |
counterpart_bank_account_id | the other account (single-leg transfer; omit for a split transfer) |
counterpart_transaction_id | the exact feed row that is the other leg — always pass when known (single-leg transfer) |
counterpart_transaction_ids | SPLIT TRANSFER: two or more own-account rows whose amounts sum to the source row — one payment across several accounts |
idempotency_key | — |
create_bank_accountwrite
Create an OFFLINE account (no Plaid behind it), each with its own ledger account. Not just cash: `kind` picks what it is and where it lands on the dashboard / net worth — checking & savings are spendable cash; credit_card & loan are debts; investment is a brokerage/retirement account; property is a home, car, or any other owned asset. Seed its current value with opening_balance_cents. (For a home worth $850k: kind:"property", opening_balance_cents:85000000.)
business_idrequired | Which business (books) to operate on — see list_businesses |
namerequired | — |
kind | checking/savings = cash; credit_card/loan = debt; investment = brokerage/retirement; property = a home/car/other owned asset. Defaults to checking. |
opening_balance_cents | current value/balance as of opening_date — auto-posts against Opening Balance Equity (a home's value, a loan's payoff, an account balance) |
opening_date | — |
update_bank_accountwrite
Rename a bank account (e.g. a savings account created under the wrong label). The backing ledger account renames in lockstep so reports read the same name. Postings and balances are untouched.
business_idrequired | Which business (books) to operate on — see list_businesses |
bank_account_idrequired | — |
namerequired | — |
set_balancewrite
Set a balance-only account (brokerage, loan, any account tracked by statement value) to a known figure as of a date. Computes the book balance and posts the difference against offset_account_id in one balanced entry — no hand-computed mark-to-market plugs. Pick the offset by what the change IS: Investment Gains/Losses for market movement, Interest Expense for loan growth, Owner's Contribution for corrections you can't attribute. Returns previous/new balance and the delta posted; delta 0 posts nothing. NOT for accounts with transaction feeds — categorize their rows instead, or the next statement won't tie.
business_idrequired | Which business (books) to operate on — see list_businesses |
bank_account_idrequired | — |
balance_centsrequired | the statement value of the account, integer cents, in NATURAL report sign (NOT the feed's money-out convention): an asset like a brokerage worth $2,000 is +200000; a LIABILITY like a loan or card owing $500 is +50000 (the amount owed, positive) |
as_of | defaults to today |
offset_account_idrequired | the account the change posts against — see list_accounts |
memo | — |
idempotency_key | — |
add_transactionwrite
Add one manual feed transaction (it lands in the review queue; categorize it to post).
business_idrequired | Which business (books) to operate on — see list_businesses |
bank_account_idrequired | — |
daterequired | — |
descriptionrequired | — |
amount_centsrequired | positive = money out of the account |
memo | — |
import_transactionswrite
Bulk-import normalized rows (from a CSV or statement the user gave you). Dedupes on batch_id + row CONTENT (date, amount, description — NOT row_index), so re-importing the same batch is a no-op even if the rows are reordered; genuinely repeated rows (same date/amount/description) within one batch are all kept, matched by order of appearance. Editing a row and re-importing under the SAME batch_id adds a NEW row (the content changed) rather than replacing — to correct a batch, undo_import then re-import under a fresh batch_id. Returns transactions: [{row_index, transaction_id}] for EVERY row in the batch (fresh and previously-imported alike) — use those ids directly; never re-match rows by date/amount/description. Max 10000 rows. Response also carries transfer_sweep.unbooked_possible_legs — nonzero means suspected transfer legs await booking.
business_idrequired | Which business (books) to operate on — see list_businesses |
bank_account_idrequired | — |
batch_idrequired | stable id for this file/statement — reuse it on retry |
rowsrequired | — |
undo_importwrite
Retract an ENTIRE import batch: removes every non-excluded transaction imported under this batch_id (posted rows are reversed first — postings stay immutable), so you can re-import corrected rows under a NEW batch_id. 404 when nothing matches: legacy imports before batch tracking cannot be batch-undone.
business_idrequired | Which business (books) to operate on — see list_businesses |
batch_idrequired | the batch_id the rows were imported with (import_transactions) |
update_transactionwrite
Edit display metadata: description, payee, memo. Date and amount edits on POSTED manual/CSV rows auto-reverse and re-post the entry against the same category (Plaid rows and split/transfer rows refuse ledger-field edits; sign flips refused).
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | — |
description | — |
payee | — |
memo | — |
date | — |
amount_cents | manual/CSV rows only; sign must not flip (positive = money out) |
split_transactionwrite
Post one feed transaction across SEVERAL categories in a single balanced entry. Lines must sum exactly to the transaction amount; lines may be income/expense/equity, a LIABILITY (a loan payment splits into a principal line on the loan liability + an interest-expense line), or an ASSET (a mixed purchase where part of the money buys a Fixed asset). Your own bank/card accounts are never a split line — that is a transfer. OR pass loan_auto instead of lines for a loan payment: the SERVER computes interest (current loan balance x rate/12, needs loan_rate_bps set via update_account) and principal — never hand-amortize. Combine loan_auto with dry_run to preview the breakdown. Re-splitting reverses and posts fresh.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | — |
lines | explicit split lines; omit when using loan_auto |
loan_auto | server-computed loan payment split: the loan liability (with loan_rate_bps set) + the interest expense account |
memo | — |
idempotency_keyrequired | — |
dry_run | true = PREVIEW ONLY: returns the exact lines that would post with each account's balance delta; nothing is written, nothing is audit-logged |
sync_connectionswrite
Pull fresh transactions from every Plaid bank connection in this business. Response includes transfer_sweep.unbooked_possible_legs: suspected transfer legs not yet booked — when nonzero, run the transfer pass (find_matches → confirm_transfer/record_transfer).
business_idrequired | Which business (books) to operate on — see list_businesses |
categorize_transactionwrite
THE workhorse: post (or re-post) the journal entry for a feed transaction. category_account_id must be a category from list_accounts — income/expense, equity (Owner’s Draw), a LIABILITY like a loan (a loan advance categorized to the loan credits it; the money-in row posts debit bank / credit loan), or an ASSET (lending a friend money on a personal book categorizes to "Owed to me" (1300) — it is a balance-sheet move, not spending, so net worth stays right; the repayment later categorizes back against the same account). Never one of your own bank/card accounts — transfers go through record_transfer/confirm_transfer. Recategorizing auto-reverses the prior entry and REQUIRES a fresh idempotency_key (reuse a key only to retry an errored call). Also pulls personal-mode holding items into the books (they post against Owner’s Contribution).
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | — |
category_account_idrequired | — |
memo | — |
idempotency_key | required to actually post; not needed when dry_run:true (a preview writes nothing). A fresh key per action; reuse only to retry an errored call. |
dry_run | true = PREVIEW ONLY: returns the exact lines that would post with each account's balance delta; nothing is written, nothing is audit-logged |
bulk_categorizewrite
Categorize MANY transactions in one call (the migration workhorse) — each item posts independently with its own category and idempotency_key, so one bad row never blocks the rest. Returns per-item results: {transaction_id, entry_id | error}. Pass dry_run:true to PREVIEW every item (each result carries preview:{would_post…}, nothing is written, and idempotency_key is not needed) — rehearse a big batch before committing it. Max 100 items per call; loop for more. Same semantics as categorize_transaction per item.
business_idrequired | Which business (books) to operate on — see list_businesses |
itemsrequired | — |
dry_run | true = PREVIEW every item (would-post lines per row), write nothing, no keys needed |
mark_reviewedwrite
Set or clear the reviewed flag. Only posted (categorized/split/transfer), holding, or ZERO-AMOUNT rows can be marked reviewed — zero-amount rows (bank notices like "Interest Rate Change") can never post, so reviewing IS how you dismiss them. Categorizing already sets the flag — EXCEPT categorizing to an "Uncategorized" placeholder, which posts but deliberately keeps reviewed=false so the row stays in the queue until a real category lands. Use this mainly to un-review, dismiss notices, or flag holding items. Pass transaction_ids (up to 200) to mark MANY at once — returns {updated, skipped_uncategorized}; one id via transaction_id for a single row.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_id | — |
transaction_ids | mark many rows in one call (up to 200); use instead of transaction_id |
reviewedrequired | — |
attach_receiptwrite
Attach a receipt (image or PDF) to a transaction. Pass the file as base64 in content_base64 OR a public https url to fetch — plus a filename like "gusto-2026-07.pdf". Handy right after categorize_transaction so the proof lives with the expense; the receipt is stored privately and travels with the books export.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | — |
filenamerequired | e.g. receipt.pdf or square-payout.jpg |
content_base64 | base64-encoded file bytes (no data: prefix) |
url | a public https URL to fetch the receipt from instead of base64 |
post_entrywrite
Manual journal entry (adjustments, draws, contributions, opening balances). Each line carries exactly one of debit_cents or credit_cents; Σdebits must equal Σcredits, ≥2 lines.
business_idrequired | Which business (books) to operate on — see list_businesses |
daterequired | — |
memorequired | — |
linesrequired | — |
idempotency_keyrequired | — |
dry_run | true = PREVIEW ONLY: returns the exact lines that would post with each account's balance delta; nothing is written, nothing is audit-logged |
reverse_entrywrite
Post the linked mirror of an entry (the only way to undo — postings are immutable). If the entry was a feed posting (a categorized/split/transfer row), reversing it also CLEARS that row back to uncategorized and returns it to the review queue (entry_id/category nulled, reviewed=false) — the same reset as an uncategorize, so re-categorize it afterward if you meant to redo rather than remove. The response echoes the reversed entry date, memo, and amount; CHECK it matches what you intended before moving on.
business_idrequired | Which business (books) to operate on — see list_businesses |
entry_idrequired | — |
reasonrequired | — |
create_accountwrite
Add a chart-of-accounts category. Code is auto-allocated when omitted.
business_idrequired | Which business (books) to operate on — see list_businesses |
namerequired | — |
typerequired | — |
subtype | free-form label; system-recognized values: 'contribution' (Owner's Contribution), 'opening_balance', 'bank'/'credit_card' (set automatically for bank-backed accounts — don't use); 'investment' (asset accounts only: shows in the dashboard's investments block, the calendar's wide scope, and staleness nudges) |
code | — |
confirm_transferwrite
Confirm two feed transactions as one transfer between own accounts: posts a single bank↔bank entry (never income/expense) and marks both rows. Fee-bearing transfers (Square/Stripe instant payouts, wire fees): when the legs differ, pass fee_account_id (an expense account, e.g. Bank Fees) and the difference posts there in the same entry. Retry-safe: if this pair is already confirmed, it returns the existing entry with already_confirmed:true instead of erroring, so a retry after a timeout is a no-op.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_id_arequired | — |
transaction_id_brequired | — |
fee_account_id | expense account that absorbs the out/in difference when the legs are unequal (instant-payout/wire fees) |
idempotency_key | fresh key for this confirmation; reuse ONLY to retry a call whose outcome you never saw |
resolve_duplicatewrite
"duplicate" soft-excludes the row (reversibly; a posted duplicate is auto-reversed). "not_duplicate" clears the flag. Nothing is ever deleted. Pass transaction_ids (up to 200) to resolve MANY the same way in one call — each resolves independently, returning per-item {transaction_id, excluded_duplicate | error}.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_id | — |
transaction_ids | resolve many rows in one call (up to 200); use instead of transaction_id |
resolutionrequired | — |
tag_transactionwrite
Add or remove a tag on a transaction. Tags are entities (vendor, project, client) whose money totals roll up. tag_name creates-or-matches by name; kind classifies the tag when this call CREATES it (existing tags keep theirs — use update_tag to change). Pass transaction_ids (up to 200) to tag or untag MANY rows in one call — the tag is created/matched ONCE and applied to every row (returns {tagged/removed, ...}). Bulk untag needs tag_id (nothing to create by name).
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_id | — |
transaction_ids | tag/untag many rows in one call (up to 200); use instead of transaction_id |
tag_id | — |
tag_name | — |
kind | classification applied only when tag_name creates a new tag |
remove | true = remove this tag (tag_id required) |
update_tagwrite
Rename and/or reclassify a tag. kind 'vendor' (1099-relevant payee), 'project', 'client', or null (general) — only vendor-kind tags appear in run_1099_report's vendors list. Renaming into an existing name is rejected (tag merge/delete is human-only, in the web UI).
business_idrequired | Which business (books) to operate on — see list_businesses |
tag_idrequired | — |
name | — |
kind | null clears the classification |
update_accountwrite
Rename and/or archive (or unarchive) a chart-of-accounts category, retype an asset's display bucket, or set loan terms. Archiving hides it from pickers; postings and balances are untouched — accounts are never deleted (that is human-only, and only when empty). subtype moves an ASSET between dashboard/net-worth buckets (investment / property / other_asset, or null for a plain asset) with no reverse-and-recreate churn — postings are unaffected. loan_rate_bps (LIABILITY accounts only) is the annual rate in basis points (6.49% = 649); once set, split_transaction with loan_auto computes each payment's interest/principal split from the live balance.
business_idrequired | Which business (books) to operate on — see list_businesses |
account_idrequired | — |
name | — |
archived | — |
subtype | asset display bucket: 'investment' (brokerage/retirement), 'property' (home/car/held asset), 'other_asset', or null for a plain asset — changes where it shows on the dashboard/net worth; postings unaffected. (System subtypes like 'bank'/'opening_balance' can't be set here.) |
loan_rate_bps | annual rate in basis points on a liability account (6.49% = 649); null clears |
schedule_c_line | IRS Schedule C line this expense account rolls up to ('8' Advertising, '24b' Meals, '27a' Other…); run_schedule_c lists valid lines and suggestions; null clears |
reconcile_accountwrite
Tie one bank account out to a statement. Whole-balance mode (default): computes the account's book (ledger) balance as of statement_date and stores it against the statement's ending balance. Worksheet mode: pass cleared_transaction_ids — the posted feed rows (from list_transactions {status:'reviewed', account_id}) that appear on the statement — and the cleared balance is checked against the statement instead, stamping those rows cleared so they drop off future reconciliations. Either way, difference_cents 0 = reconciled; nonzero returns a ranked suspects object (unposted rows in the period + unresolved duplicate flags, each with explains_difference:true when that single row accounts for the whole gap) — start there. In worksheet mode the response returns matched_count and ignored_ids — any id that isn't a posted, not-yet-cleared row dated on or before statement_date on THIS account is ignored, so check ignored_ids first if the difference is unexpected. Re-do is just re-running: reconciling the SAME account + statement_date again SUPERSEDES the prior tie-out (un-stamps its cleared rows and replaces it — one proof object per statement date, latest wins; the response echoes superseded_reconciliation_id). Different dates each keep their own proof.
business_idrequired | Which business (books) to operate on — see list_businesses |
bank_account_idrequired | — |
statement_daterequired | the statement's ending date |
statement_balance_centsrequired | the statement's ending balance in integer cents, NATURAL report sign: an asset account's cash balance is positive; a credit card or loan statement showing $500 owed is +50000 (positive amount owed), NOT the feed's money-out sign |
cleared_transaction_ids | optional: posted feed-transaction ids to mark cleared (worksheet mode); omit to reconcile the whole balance |
statement_url | optional: a public HTTPS URL of the statement file — it is fetched and PINNED to this reconciliation as the evidence the tie-out was performed against |
statement_base64 | optional: the statement file as base64, alternative to statement_url |
statement_filename | the statement's filename, e.g. 'chase-2026-06.pdf' |
set_budgetwrite
Set (or replace) a simple monthly target on an expense category — the personal-books budget model: a handful of targets plus one safe-to-spend number, NOT envelopes. rollover carries ONE prior month’s unspent remainder forward (opt-in, for lumpy expenses like car repair).
business_idrequired | Which business (books) to operate on — see list_businesses |
category_account_idrequired | an expense account from list_accounts |
monthly_target_centsrequired | — |
rollover | true = last month’s unspent remainder adds to this month |
resolve_recurringwrite
Confirm a detected recurring item (it's real), dismiss it (not recurring — never flagged again for that merchant+cadence), and/or PIN its real due day: due_day (1–31) fixes a monthly/yearly item's exact day-of-month (rent due the 1st, not '~Aug 2') — the calendar and projections use it; pinning implies confirmed; null unpins.
business_idrequired | Which business (books) to operate on — see list_businesses |
recurring_idrequired | — |
status | — |
due_day | exact day-of-month for monthly/yearly items; null to unpin |
mark_recurringwrite
Mark a transaction's merchant as recurring by hand (detection needs 3+ steady occurrences; this doesn't): creates a CONFIRMED recurring item from the row with the given cadence, so it appears in list_recurring, the calendar, and projections immediately. Optional due_day pins the exact day-of-month.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | — |
cadence | default monthly; semimonthly = twice a month, e.g. the 1st & 15th (24/yr, distinct from biweekly's 26/yr) |
due_day | — |
add_conventionwrite
Write one durable rule into the book's operating manual (list_conventions) so EVERY future session — any agent, any model, any device — keeps the books the same way. Add one whenever the user rules on how something should be booked ('Zelle to Irina is rent', 'tech subscriptions get the Business tag') or you fix a mistake worth never repeating. One rule per call, ≤500 chars, written for a bookkeeper who has never seen this book. Adding an identical rule replays as a no-op. Conventions are guidance you follow, not automation the server runs.
business_idrequired | Which business (books) to operate on — see list_businesses |
rulerequired | one durable, self-contained rule |
ask_userwrite
Park a question only the human can answer — held by the LEDGER, not your session memory, so it survives until someone answers it. Use it the moment you hit something you can't resolve ('where did this $3,000 wire go?'), optionally pinned to the transaction, then move on. Never carry an ask-the-user list in your head: a question you didn't park is a question the next session loses. Re-asking the same open question replays as a no-op.
business_idrequired | Which business (books) to operate on — see list_businesses |
questionrequired | one self-contained question the owner can answer without context from this chat |
transaction_id | the row the question is about, when there is one |
resolve_questionwrite
Record the human's answer to an open question (or dismiss:true when it no longer matters). The answer text is the durable artifact — write what the user actually said, then ACT on it (categorize, transfer, add_convention if it's a rule). An answered question is how the next session knows not to re-ask. Re-resolving an already-answered question AMENDS it (correct a wrong figure) rather than erroring.
business_idrequired | Which business (books) to operate on — see list_businesses |
question_idrequired | — |
answer | the user's answer, verbatim or faithfully summarized |
dismiss | true = drop the question without an answer |
retire_conventionwrite
Retire a convention that no longer holds (rule changed, counterparty gone). Retire-then-add is how a rule is amended — conventions are never edited in place, so the trail stays honest.
business_idrequired | Which business (books) to operate on — see list_businesses |
convention_idrequired | — |
set_lock_datewrite
Lock the books through a date after closing a period: posting anything dated on or before lock_date is rejected (idempotent replays of already-posted entries still return the original). Pass null to clear the lock.
business_idrequired | Which business (books) to operate on — see list_businesses |
lock_daterequired | YYYY-MM-DD, or null to clear the lock |
archive_businesswrite
Archive a business/book (or restore it with restore:true): it disappears from list_businesses and the switcher, all data is kept, and explicit business_id calls still reach it. Use this to clean up scratch books you created for testing. Refuses to archive the account's last active business. Nothing is ever deleted.
business_idrequired | Which business (books) to operate on — see list_businesses |
restore | true = bring an archived business back |
create_businesswrite
Create a new set of books. entity_type "personal" seeds a PERSONAL chart (everyday categories — Groceries, Rent & Housing, Dining, Subscriptions, Salary — plus "Owed to me"/"I owe" IOU accounts, and none of the business furniture: no AR/AP/COGS); omit it (or anything else) for a standard BUSINESS chart. The response echoes {id, name, entity_type, accounts:[{code,name,type}]} — the freshly seeded chart, so you can categorize right away without a follow-up list_accounts. Your account key already covers it — no separate key needed.
namerequired | — |
entity_type | "personal" for a personal-finance book (personal chart + IOU accounts); omit for a business book |
register_assetwrite
Register a purchase transaction as a FIXED ASSET: posts the row to the "Fixed assets" account (balance sheet, not expense) and records name + useful life. life_months null = track only, no depreciation. Straight-line only — depreciation METHOD is a tax election for the owner's CPA.
business_idrequired | Which business (books) to operate on — see list_businesses |
transaction_idrequired | The money-out purchase row |
namerequired | What the asset is, e.g. "MacBook Pro 16" |
life_months | Useful life in months (computers 60, furniture/equipment 84, vehicles 60, buildings 468); null = just track it |
idempotency_keyrequired | Fresh key for this action |
book_depreciationwrite
Post all straight-line depreciation due through today (debit Depreciation expense / credit Accumulated depreciation), one entry per asset per month, idempotency-keyed — safe to run every month-end close. Optionally scope to one asset_id.
business_idrequired | Which business (books) to operate on — see list_businesses |
asset_id | Only this asset (default: every asset) |