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_archivedtrue = include archived (deactivated) businesses

list_accounts

Chart of accounts for a business.

business_idrequiredWhich 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_idrequiredWhich business (books) to operate on — see list_businesses
status
account_idFilter to one bank account
date_from
date_to
search
tag_idonly rows carrying this tag — see list_tags
category_idonly rows posted to this category account
amount_min_centsabsolute amount lower bound, cents
amount_max_centsabsolute amount upper bound, cents
flagged_duplicate
flagged_transfer
holding_areatrue = personal-mode holding area only
excludedtrue = ONLY soft-excluded (duplicate-resolved) rows — they're hidden otherwise; they carry reviewed:true, so combine with status:'all'
fieldscompact = 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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_idrequired

get_balance

Computed balance of one ledger account (from postings, never stored).

business_idrequiredWhich 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_idrequiredWhich 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
chartalso 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_idrequiredWhich 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
yeartax year for 1099 / schedule-c
expires_dayslink expiry (default 30)

run_balance_sheet

Balance sheet as of a date.

business_idrequiredWhich 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_idrequiredWhich 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_idrequiredWhich business (books) to operate on — see list_businesses
account_idrequired
date_from
date_to
hide_reversedtrue = 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_idrequiredWhich business (books) to operate on — see list_businesses
yeartax 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_idrequiredWhich 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_idrequiredWhich 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_idrequiredWhich 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_idrequiredWhich 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_idrequiredWhich 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_idrequiredWhich 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_idrequiredWhich 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_idrequiredWhich business (books) to operate on — see list_businesses
statusdefault '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_idrequiredWhich 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_idrequiredWhich 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_idrequiredWhich 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_idrequiredWhich 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_idrequiredWhich business (books) to operate on — see list_businesses
include_dismissedalso 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_idrequiredWhich business (books) to operate on — see list_businesses
chartalso 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_idrequiredWhich 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
titlerequiredone-line summary
detailrequiredwhat happened / what is wanted — include the steps and the tool if it is a bug
contextoptional: 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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_idrequired
target_business_idrequiredthe book this spend belongs to (list_businesses)
target_category_account_idBUSINESS→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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_idrequiredthe SOURCE row (the single bank movement)
counterpart_bank_account_idthe other account (single-leg transfer; omit for a split transfer)
counterpart_transaction_idthe exact feed row that is the other leg — always pass when known (single-leg transfer)
counterpart_transaction_idsSPLIT 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_idrequiredWhich business (books) to operate on — see list_businesses
namerequired
kindchecking/savings = cash; credit_card/loan = debt; investment = brokerage/retirement; property = a home/car/other owned asset. Defaults to checking.
opening_balance_centscurrent 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_idrequiredWhich 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_idrequiredWhich business (books) to operate on — see list_businesses
bank_account_idrequired
balance_centsrequiredthe 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_ofdefaults to today
offset_account_idrequiredthe 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_idrequiredWhich business (books) to operate on — see list_businesses
bank_account_idrequired
daterequired
descriptionrequired
amount_centsrequiredpositive = 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_idrequiredWhich business (books) to operate on — see list_businesses
bank_account_idrequired
batch_idrequiredstable 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_idrequiredWhich business (books) to operate on — see list_businesses
batch_idrequiredthe 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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_idrequired
description
payee
memo
date
amount_centsmanual/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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_idrequired
linesexplicit split lines; omit when using loan_auto
loan_autoserver-computed loan payment split: the loan liability (with loan_rate_bps set) + the interest expense account
memo
idempotency_keyrequired
dry_runtrue = 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_idrequiredWhich 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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_idrequired
category_account_idrequired
memo
idempotency_keyrequired 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_runtrue = 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_idrequiredWhich business (books) to operate on — see list_businesses
itemsrequired
dry_runtrue = 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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_id
transaction_idsmark 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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_idrequired
filenamerequirede.g. receipt.pdf or square-payout.jpg
content_base64base64-encoded file bytes (no data: prefix)
urla 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_idrequiredWhich business (books) to operate on — see list_businesses
daterequired
memorequired
linesrequired
idempotency_keyrequired
dry_runtrue = 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_idrequiredWhich 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_idrequiredWhich business (books) to operate on — see list_businesses
namerequired
typerequired
subtypefree-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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_id_arequired
transaction_id_brequired
fee_account_idexpense account that absorbs the out/in difference when the legs are unequal (instant-payout/wire fees)
idempotency_keyfresh 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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_id
transaction_idsresolve 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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_id
transaction_idstag/untag many rows in one call (up to 200); use instead of transaction_id
tag_id
tag_name
kindclassification applied only when tag_name creates a new tag
removetrue = 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_idrequiredWhich business (books) to operate on — see list_businesses
tag_idrequired
name
kindnull 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_idrequiredWhich business (books) to operate on — see list_businesses
account_idrequired
name
archived
subtypeasset 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_bpsannual rate in basis points on a liability account (6.49% = 649); null clears
schedule_c_lineIRS 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_idrequiredWhich business (books) to operate on — see list_businesses
bank_account_idrequired
statement_daterequiredthe statement's ending date
statement_balance_centsrequiredthe 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_idsoptional: posted feed-transaction ids to mark cleared (worksheet mode); omit to reconcile the whole balance
statement_urloptional: 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_base64optional: the statement file as base64, alternative to statement_url
statement_filenamethe 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_idrequiredWhich business (books) to operate on — see list_businesses
category_account_idrequiredan expense account from list_accounts
monthly_target_centsrequired
rollovertrue = 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_idrequiredWhich business (books) to operate on — see list_businesses
recurring_idrequired
status
due_dayexact 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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_idrequired
cadencedefault 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_idrequiredWhich business (books) to operate on — see list_businesses
rulerequiredone 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_idrequiredWhich business (books) to operate on — see list_businesses
questionrequiredone self-contained question the owner can answer without context from this chat
transaction_idthe 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_idrequiredWhich business (books) to operate on — see list_businesses
question_idrequired
answerthe user's answer, verbatim or faithfully summarized
dismisstrue = 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_idrequiredWhich 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_idrequiredWhich business (books) to operate on — see list_businesses
lock_daterequiredYYYY-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_idrequiredWhich business (books) to operate on — see list_businesses
restoretrue = 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_idrequiredWhich business (books) to operate on — see list_businesses
transaction_idrequiredThe money-out purchase row
namerequiredWhat the asset is, e.g. "MacBook Pro 16"
life_monthsUseful life in months (computers 60, furniture/equipment 84, vehicles 60, buildings 468); null = just track it
idempotency_keyrequiredFresh 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_idrequiredWhich business (books) to operate on — see list_businesses
asset_idOnly this asset (default: every asset)