API reference
Every /v1 route, from the OpenAPI spec the server generates.
Base URL https://app.usetruenote.com/v1. Send Authorization: Bearer <token>: a personal access token from Connected agents, or an OAuth access token. The spec is at https://app.usetruenote.com/v1/openapi.json; this page is drawn from it at build time.
curl https://app.usetruenote.com/v1/overview -H "Authorization: Bearer tn_pat_…" - Money is integer cents; days are
YYYY-MM-DD; transaction amounts keep Plaid's sign (positive is money out). - Lists page by keyset: pass the
cursoran answer gives you back. Transactions takeupdatedSince(the last answer'ssyncedAt) for what changed, withdeletedids. - Writes are
POST /v1/tools/<name>, the MCP tool's own body in dollars. - Errors are
application/problem+json: the code, what went wrong and what fixes it (errors); 403 lists the scopes missing; 404 is also what someone else's row looks like. - 240 requests a minute per token. Changes are additive; deprecations are announced in the changelog.
New to it? Your first script goes from a token to a copy that keeps itself in step.
Reads
Plain JSON with amounts in integer cents. Lists page by keyset; /v1/transactions and /v1/investment-transactions take updatedSince for what changed since your last pull.
- GET /v1/overview
- GET /v1/accounts
- GET /v1/transactions
- GET /v1/spending
- GET /v1/recurring
- GET /v1/net-worth-history
- GET /v1/holdings
- GET /v1/investment-transactions
- GET /v1/liabilities
- GET /v1/context
- GET /v1/plan
- GET /v1/plan/actuals
- GET /v1/taxes/projection
- GET /v1/tasks
- GET /v1/buckets
- GET /v1/review-items
- GET /v1/bucket-rules
- GET /v1/documents
- GET /v1/documents/{id}
- GET /v1/documents/{id}/file
- GET /v1/export
- GET /v1/export.zip
GET /v1/overview
Net worth, this month, the plan, bills, goals and the next estimated tax in one call
Needs any read scope. Accounts and entities the token excludes are invisible here, totals included.
Responses: 200, 400, 401, 403, 404
GET /v1/accounts
Accounts with latest balances (debts negative), manual accounts and net worth totals
Needs accounts:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
type | query | depository | credit | loan | investment | other | Only accounts of this type |
includeHidden | query | string | Include closed accounts, and accounts the person hid if this token may see hidden accounts |
Responses: 200, 400, 401, 403, 404
GET /v1/transactions
Transactions newest first, filtered, keyset-paged (money in positive); updatedSince for what changed since your last pull, with the ids deleted
Needs transactions:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
from | query | string | First day (inclusive); default: all history |
to | query | string | Last day (inclusive) |
accountId | query | uuid | |
categoryId | query | uuid | none | A category id (its subcategories included), or `none` for uncategorized |
entityId | query | uuid | |
merchant | query | string | Merchant or description contains this (any case) |
q | query | string | Description, merchant or note contains this |
minAmountCents | query | integer | null | Size at least this, in cents (either direction) |
maxAmountCents | query | integer | null | |
direction | query | in | out | Money in or money out |
pending | query | string | |
ownerContribution | query | string | true: only a business's spending paid from a personal account |
limit | query | integer | Rows per page (default 50, max 200) |
cursor | query | string | `nextCursor` of the last page |
updatedSince | query | date-time | Only rows added or changed at or after this instant (the syncedAt of your last pull), and `deleted` lists the ids gone since. Keeps a copy in step without re-reading everything; apply by id (a row may repeat). |
Responses: 200, 400, 401, 403, 404
GET /v1/spending
Spending grouped by category, merchant, entity or month: the household's by default (scope=all adds the businesses; entityId picks one)
Needs transactions:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
from | query | string | Default: the first of this month |
to | query | string | Default: today |
groupBy | query | category | merchant | entity | month | |
scope | query | household | all | household (default): the personal entities, as the overview's month counts them; all: the household and every business together. Ignored with entityId |
entityId | query | uuid | Only this entity's spending (one business) |
categoryId | query | uuid | Only this category (and its subcategories) |
limit | query | integer | Groups to list (default 25); the rest are summed |
Responses: 200, 400, 401, 403, 404
GET /v1/recurring
Recurring bills, subscriptions and income with next dates and monthly totals
Needs transactions:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
direction | query | inflow | outflow | |
includeInactive | query | string |
Responses: 200, 400, 401, 403, 404
GET /v1/net-worth-history
Net worth over time by account type (day, week or month points)
Needs accounts:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
from | query | string | Default: a year ago |
to | query | string | Default: today |
interval | query | day | week | month |
Responses: 200, 400, 401, 403, 404
GET /v1/holdings
Investment positions by account, optionally the latest investment transactions
Needs investments:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
accountId | query | uuid | |
includeTransactions | query | string | Also the latest investment transactions (buys, sells, dividends) |
Responses: 200, 400, 401, 403, 404
GET /v1/investment-transactions
Investment transactions newest first, filtered by date, account, type, subtype or symbol, keyset-paged, with totals by kind (cash in positive); updatedSince for what changed since your last pull, with the ids deleted
Needs investments:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
from | query | string | First day (inclusive); default: all history |
to | query | string | Last day (inclusive) |
accountId | query | uuid | |
type | query | buy | sell | cash | fee | transfer | cancel | |
subtype | query | string | Plaid's subtype, any case: dividend, interest, buy, sell, deposit… |
symbol | query | string | Ticker, any case |
q | query | string | Description contains this |
limit | query | integer | Rows per page (default 50, max 200) |
cursor | query | string | `nextCursor` of the last page |
updatedSince | query | date-time | Only rows added or changed at or after this instant (the syncedAt of your last pull), and `deleted` lists the ids gone since. Keeps a copy in step without re-reading everything; apply by id (a row may repeat). |
Responses: 200, 400, 401, 403, 404
GET /v1/liabilities
Cards and loans: owed, APR, minimums, due dates
Needs liabilities:read. Accounts and entities the token excludes are invisible here, totals included.
Responses: 200, 400, 401, 403, 404
GET /v1/context
Profile, entities, income sources, goals, categories, rules, notes, plans
Needs context:read. Accounts and entities the token excludes are invisible here, totals included.
Responses: 200, 400, 401, 403, 404
GET /v1/plan
The spending plan: regular take-home split into groups and lines
Needs context:read. Accounts and entities the token excludes are invisible here, totals included.
Responses: 200, 400, 401, 403, 404
GET /v1/plan/actuals
Plan vs actual for a month: household spending per plan group and category line against the plan, the trailing 3 months' average, and spending no line names
Needs context:read, transactions:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
month | query | string | A month like 2026-10; default this month |
Responses: 200, 400, 401, 403, 404
GET /v1/taxes/projection
The year's federal tax projected from today: the statement line by line, the refund or amount owed, the extra withholding per paycheck that breaks even, the safe harbor
Needs context:read, transactions:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
year | query | integer |
Responses: 200, 400, 401, 403, 404
GET /v1/tasks
The checklist: open tasks by due date (status=done|all for the rest)
Needs context:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
status | query | open | done | all |
Responses: 200, 400, 401, 403, 404
GET /v1/buckets
Buckets: each account's virtual split, balances from Truenote's ledger, targets and what's Unassigned
Needs accounts:read, context:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
accountId | query | uuid | Only this account's buckets |
includeArchived | query | string | Also archived buckets |
recentEntries | query | integer | null | Latest ledger entries per bucket (default 3) |
Responses: 200, 400, 401, 403, 404
GET /v1/review-items
The Review queue: deposits to split, spending that looks like a bucket's, money taken out of a bucketed account, each with a proposal
Needs accounts:read, transactions:read, context:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
status | query | open | resolved | open (default): waiting for a decision; resolved: the latest decided |
Responses: 200, 400, 401, 403, 404
GET /v1/bucket-rules
Bucket rules: spending they charge to a bucket automatically, first match wins
Needs accounts:read, context:read. Accounts and entities the token excludes are invisible here, totals included.
Responses: 200, 400, 401, 403, 404
GET /v1/documents
Documents the person uploaded (paystubs, tax forms, statements), newest first
Needs documents:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
q | query | string | Words in the name, notes or text (Social Security and account numbers masked) |
kind | query | paystub | w2 | form_1099 | form_1098 | tax_return | benefits | statement | receipt | other | |
taxYear | query | integer |
Responses: 200, 400, 401, 403, 404
GET /v1/documents/{id}
One document's text (masked, paged by offset); an image's bytes are at /file
Needs documents:read. Accounts and entities the token excludes are invisible here, totals included.
| Parameter | In | Type | Meaning |
|---|---|---|---|
id * | path | uuid | Id |
offset | query | integer | null | Where to continue a long text (the last call's nextOffset) |
Responses: 200, 400, 401, 403, 404
GET /v1/documents/{id}/file
A document's file as uploaded (unmasked)
Needs documents:read.
| Parameter | In | Type | Meaning |
|---|---|---|---|
id * | path | uuid | Id |
Responses: 200, 401, 403, 404
GET /v1/export
Everything this token can see, every table, as JSON (truenote-export/1)
Needs accounts:read, transactions:read, investments:read, liabilities:read, context:read. Accounts and entities the token excludes are invisible here, totals included.
Responses: 200, 400, 401, 403, 404
GET /v1/export.zip
The same export as a zip of CSV files, one per table
Needs accounts:read, transactions:read, investments:read, liabilities:read, context:read.
Responses: 200, 401, 403
Writes
Every MCP write tool as POST /v1/tools/<name>, with the tool's own body: amounts in dollars (650 for $650.00), ids from the reads, the answer in cents. Truenote's own records only; nothing here moves money.
- POST /v1/documents
- POST /v1/tools/update_tax_settings
- POST /v1/tools/upsert_goal
- POST /v1/tools/upsert_manual_account
- POST /v1/tools/upsert_income_source
- POST /v1/tools/update_profile
- POST /v1/tools/upsert_entity
- POST /v1/tools/upsert_plan_line
- POST /v1/tools/upsert_plan_group
- POST /v1/tools/upsert_bucket
- POST /v1/tools/move_between_buckets
- POST /v1/tools/record_bucket_entry
- POST /v1/tools/resolve_review_item
- POST /v1/tools/create_bucket_rule
- POST /v1/tools/add_note
- POST /v1/tools/update_note
- POST /v1/tools/upsert_task
- POST /v1/tools/save_plan
- POST /v1/tools/categorize_transactions
- POST /v1/tools/create_rule
- POST /v1/tools/split_transaction
- POST /v1/tools/prepare_upload
- POST /v1/tools/update_document
- POST /v1/tools/delete_record
- POST /v1/tools/delete_document
- POST /v1/tools/upsert_category
- POST /v1/tools/update_rule
- POST /v1/tools/update_account
- POST /v1/tools/reset_transactions
- POST /v1/tools/refresh_bank
- POST /v1/tools/disconnect_bank
POST /v1/documents
Keep a document for the person (multipart: file + metadata, a SaveDocument as JSON)
Needs documents:write. A PDF, PNG, JPEG or WebP image, at most 10 MB; the same file twice is a 409. Its entity, income source and account must be ones this token can see. Example: curl -H 'Authorization: Bearer tn_pat_…' -F file=@paystub.pdf -F 'metadata={"name":"Paystub Oct 16","kind":"paystub","taxYear":2026}' <origin>/v1/documents
| Body field | Type | Meaning |
|---|---|---|
file * | binary | |
metadata * | string |
Responses: 201, 400, 401, 403, 409, 413
POST /v1/tools/update_tax_settings
Set the tax year's household figures
Needs context:write. The same operation as the MCP tool update_tax_settings; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Change the household's figures for a tax year that the projection (get_tax_projection) can't find in the accounts: priorYearTax (last year's total tax, Form 1040 line 24) and priorYearAgi (for the 100%/110% safe harbor), otherPayments (estimated payments made from accounts Truenote can't see, this year's), priorYearPayments (of the IRS payments get_tax_projection found this year, the dollars that paid last year's return, a balance due or an extension payment, so they don't count toward this year), otherIncome (this year's interest, ordinary dividends, short-term gains and other ordinary income; negative for a net capital loss, up to 3000), capitalGains (this year's long-term capital gains and qualified dividends, taxed at 0/15/20%; both count toward the 3.8% net investment income tax), itemizedDeductions (used only when larger than the standard deduction), qualifyingChildren (children under 17 at year end; null: counted from the profile's household, a baby due by December 31 included) and notes. Only the fields you pass change; null clears. Per-job and per-business figures belong on the income source (upsert_income_source). Changes the person's data: say what you set.
| Body field | Type | Meaning |
|---|---|---|
year | integer | Default: this one |
priorYearTax | number | null | |
priorYearAgi | number | null | |
otherPayments | number | |
priorYearPayments | number | |
otherIncome | number | |
capitalGains | number | |
itemizedDeductions | number | null | |
qualifyingChildren | integer | null | |
notes | string |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/upsert_goal
Set a goal
Needs context:write. The same operation as the MCP tool upsert_goal; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Create or change a savings or payoff goal: by id, else by name (case-insensitive), else new (then kind and targetAmount are required). links are what counts toward it, each with the percent of it that counts: an account (accountId from list_accounts), a manual account (manualAccountId from list_accounts' manualAccounts: the house, an HSA) or a bucket (bucket: a name or id from list_buckets, for money kept in a savings bucket like an emergency fund); exactly one per link; giving links replaces them. Changes the person's data: say what you set.
| Body field | Type | Meaning |
|---|---|---|
id | uuid | |
name * | string | |
kind | emergency_fund | savings | debt_payoff | purchase | tax_reserve | other | |
targetAmount | number | Dollars |
targetDate | string | null | |
priority | integer | 1 is the most important |
notes | string | |
archived | boolean | |
links | object[] |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/upsert_manual_account
Add or update a manual account
Needs context:write. The same operation as the MCP tool upsert_manual_account; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Something no bank connection reports, kept by hand: the house (asset, type real_estate) and its mortgage (liability, type loan), a car loan, an HSA, IRA or pension held elsewhere (type retirement), private company stock or a startup stake (type equity), cash. By id, else by name (any case), else new: a new one needs kind and balance. balance is dollars, positive: what it's worth, or what's owed on a liability; it's recorded on balanceOn (default today, never a day still to come) and replaces that day's entry, so call it again with a new balance to update the value. These count in net worth and appear in list_accounts' manualAccounts; a goal can link them (upsert_goal links with manualAccountId). archived: true hides one that's gone (a sold car). What you leave out stays. Truenote's records only. Tell the person what you set.
| Body field | Type | Meaning |
|---|---|---|
id | uuid | |
name * | string | |
kind | asset | liability | |
type | string | real_estate, vehicle, loan, retirement, equity, cash, crypto, other |
entityId | uuid | null | Household (null) or a business |
notes | string | |
archived | boolean | |
balance | number | |
balanceOn | string | The day the balance is for (default today) |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/upsert_income_source
Set an income source
Needs context:write. The same operation as the MCP tool upsert_income_source; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Create or change an income source (a W-2 job, a 1099 client, a business, a bonus): by id, else by name, else new (then type and cadence are required). expectedAmount is take-home per payment (a paycheck's net pay). regular sources (salaries; the default for w2) are the spending plan's income; anything else (a bonus, 1099 work) is a windfall allocated when it lands. earner is who in the household earns it. taxSetAsidePct is the share of each payment to set aside for taxes (30 for 30%); payerMatch is text its deposits carry ("NORTHSTAR") so Truenote can find them. For the yearly tax projection (get_tax_projection), a W-2 job takes grossPay (this year's salary before deductions, bonus apart), retirement (401(k)/403(b) for the year), otherPretax (health, HSA, FSA through payroll for the year), withheldYtd with withheldAsOf (federal income tax withheld so far and the date of the paystub it's from, both or neither), withholdingPerPaycheck (federal tax withheld from each regular paycheck), bonus, bonusOn (paid or expected) and bonusWithholdingPct (default 22); a 1099 client or business takes receivedElsewhere (paid this year into accounts Truenote can't see), expectedRestOfYear (still expected this year; null: from expectedAmount) and expensesEstimate (deductible expenses for the year; null: its entity's spending). Fields you leave out keep their value; null clears. Changes the person's data: say what you set.
| Body field | Type | Meaning |
|---|---|---|
id | uuid | |
name * | string | |
type | 1099 | w2 | business | other | |
cadence | weekly | biweekly | semimonthly | monthly | quarterly | annually | irregular | |
expectedAmount | number | null | Dollars per payment |
taxSetAsidePct | number | |
payerMatch | string | null | |
entityId | uuid | null | |
earner | string | null | A household member's name |
regular | boolean | Counts toward the spending plan's income (default: true for w2) |
active | boolean | |
notes | string | |
grossPay | number | null | W-2: this year's salary, bonus apart |
retirement | number | W-2: 401(k)/403(b) deferrals for the year |
otherPretax | number | W-2: health, HSA, FSA through payroll, per year |
withheldYtd | number | null | W-2: federal income tax withheld so far this year (from a paystub) |
withheldAsOf | string | null | The paystub's date (with withheldYtd) |
withholdingPerPaycheck | number | null | W-2: federal income tax withheld per regular paycheck |
bonus | number | W-2: this year's bonus |
bonusOn | string | null | When the bonus was or will be paid |
bonusWithholdingPct | number | Default 22 |
receivedElsewhere | number | 1099/business: paid this year into accounts Truenote can't see |
expectedRestOfYear | number | null | 1099/business: still expected this year (null: from expectedAmount) |
expensesEstimate | number | null | 1099/business: deductible expenses for the year (null: the entity's spending) |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/update_profile
Update the household profile
Needs context:write. The same operation as the MCP tool update_profile; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Change the profile every agent reads first: the person's name, household members (each with a relationship, and a birthDate or, for a baby on the way, a dueDate), state (postal code like TX), filing status, risk tolerance, the free-form "about us" and time zone. Only the fields you pass change. householdMembers replaces the whole list: read get_context first and send everyone, including the person themself (relationship self). Changes the person's data: say what you set.
| Body field | Type | Meaning |
|---|---|---|
name | string | What to call the person |
householdMembers | object[] | |
state | AL | AK | AZ | AR | CA | CO | CT | DE | DC | FL | GA | HI | ID | IL | IN | IA | KS | KY | LA | ME | MD | MA | MI | MN | MS | MO | MT | NE | NV | NH | NJ | NM | NY | NC | ND | OH | OK | OR | PA | RI | SC | SD | TN | TX | UT | VT | VA | WA | WV | WI | WY | null | |
filingStatus | single | married_joint | married_separate | head_of_household | qualifying_surviving_spouse | null | |
riskTolerance | conservative | moderate | aggressive | null | |
about | string | |
timeZone | string | IANA zone, e.g. America/Chicago |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/upsert_entity
Set an entity
Needs context:write. The same operation as the MCP tool upsert_entity; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Add or rename an entity money can belong to: the household (personal) or a business (an LLC, a side business). By id, else by name (case-insensitive), else new (then kind is required). Assign accounts and transactions to it with create_rule or categorize_transactions; each business's money is then reported on its own. Changes the person's data: say what you set.
| Body field | Type | Meaning |
|---|---|---|
id | uuid | An existing entity to rename |
name * | string | |
kind | personal | business | personal or business |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/upsert_plan_line
Set a spending plan line
Needs context:write. The same operation as the MCP tool upsert_plan_line; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Add or change a line of the spending plan (read get_plan first): by id, else by name (case-insensitive), else new (then group and amount are required). group is a group's name or id; amount is dollars per payment, cadence how often (default monthly; a $139 yearly Amazon Prime is amount 139, cadence annually). endsOn is the last day it's paid (a car loan's final month); startsOn delays a line that hasn't begun (daycare from April). match is text its payment's bank description or merchant contains ("Valon" for the mortgage, "CoServ" for electricity): a line with match and no destination is a bill, paid when a payment out contains it (find the text with query_transactions or get_recurring). accountId (from list_accounts) is where saved money goes (a brokerage: its own deposits count); bucketId (from list_buckets) is the bucket it fills ("Vacation / Travel" → the Vacations bucket), which also sets the account. categoryId (from get_context) only breaks everyday spending down (Groceries, Dining). Anything you leave out stays as it is. To cancel something, delete_record with kind plan_line. Changes the person's data: say what you set.
| Body field | Type | Meaning |
|---|---|---|
id | uuid | |
name * | string | |
group | string | A group's name or id |
amount | number | Dollars per payment |
cadence | weekly | biweekly | semimonthly | monthly | quarterly | annually | |
startsOn | string | null | |
endsOn | string | null | The last day it's paid |
categoryId | uuid | null | |
accountId | uuid | null | |
bucketId | uuid | null | A bucket it fills (from list_buckets); the line then goes to its account |
match | string | null | Text its payment's bank description or merchant contains; null clears it |
notes | string |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/upsert_plan_group
Set a spending plan group
Needs context:write. The same operation as the MCP tool upsert_plan_group; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Add, rename or retarget a group of the spending plan: by id, else by name (case-insensitive), else new (then kind is required: spending for expenses and wants, saving for savings and investing). targetPct is the share of take-home it should hold (45 for 45%); the groups' targets usually add up to 100. Changes the person's data: say what you set.
| Body field | Type | Meaning |
|---|---|---|
id | uuid | |
name * | string | |
kind | spending | saving | |
targetPct | number | |
position | integer | Order, 0 first |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/upsert_bucket
Set a bucket
Needs context:write, accounts:read. The same operation as the MCP tool upsert_bucket; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Create or change a bucket (a virtual part of a savings or checking account, see list_buckets): by id, else by name (case-insensitive; pass accountId when two accounts have a bucket with that name), else new (then accountId is required: a checking or savings account from list_accounts). target is the bucket's own goal in dollars and targetDate when it should be reached (both optional; null clears). startingBalance (new buckets only) is what it holds today; to correct an existing bucket's balance use record_bucket_entry with kind set_balance. overflow makes it the account's overflow bucket: what a deposit has left after filling what each bucket is due goes there (one per account; setting it moves it from another). archived hides a bucket once it's empty. This changes only Truenote's records, never the bank: no money moves. Say what you set.
| Body field | Type | Meaning |
|---|---|---|
id | uuid | |
name * | string | |
accountId | uuid | The account it splits (required for a new bucket) |
target | number | null | Dollars |
targetDate | string | null | |
notes | string | |
overflow | boolean | Extra from deposits goes here |
archived | boolean | |
startingBalance | number | Dollars it holds today (new buckets only) |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/move_between_buckets
Move money between buckets
Needs context:write, accounts:read. The same operation as the MCP tool move_between_buckets; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Move an amount from one bucket to another bucket of the same account (by name or id from list_buckets), e.g. $500 from Emergencies to Vacations. Both buckets change in one step; the account's total and Unassigned stay the same (unless it takes the from bucket below zero: what it's short comes out of Unassigned). This is Truenote's bookkeeping only: no money moves at the bank. date defaults to today (it can't be in the future). Say what you moved.
| Body field | Type | Meaning |
|---|---|---|
from * | string | A bucket's name or id |
to * | string | A bucket's name or id, in the same account |
amount * | number | Dollars |
date | string | |
memo | string |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/record_bucket_entry
Record a bucket entry
Needs context:write, accounts:read. The same operation as the MCP tool record_bucket_entry; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Record money going into or out of a bucket (by name or id from list_buckets) in Truenote's ledger. kind: contribution (money put into the account for this bucket, e.g. a transfer to savings), withdrawal (money taken out of the account from this bucket), charge (spending paid from another account, like a vet bill on checking, that this bucket covers: it needs the transactionId, and the bucket drops now while the account's total drops when the money is moved back), or set_balance (a correction: the bucket holds exactly amount as of date). transactionId (from query_transactions) ties the entry to the bank transaction it accounts for. date defaults to today. This never moves money at the bank. When unsure which bucket something belongs to, ask the person rather than guess. Say what you recorded.
| Body field | Type | Meaning |
|---|---|---|
bucket * | string | A bucket's name or id |
accountId | uuid | When two accounts have a bucket with that name |
kind * | contribution | withdrawal | charge | set_balance | |
amount * | number | Dollars (for set_balance: the balance it holds) |
date | string | |
memo | string | |
transactionId | uuid | The transaction it accounts for (needed for charge) |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/resolve_review_item
Decide a review item
Needs context:write, accounts:read, transactions:read. The same operation as the MCP tool resolve_review_item; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Decide an item from list_review_items for the person: decision approve does what its proposal says (or, with allocations, puts other amounts in other buckets: bucket names or ids, dollars; for a withdrawal, paysBack lists the charge entry ids it pays back instead of the proposal's), and createRule (a charge only, to one bucket) also saves the proposed bucket rule so the next match is automatic; decision dismiss leaves the transaction out of buckets for good (a deposit stays in Unassigned). Together the amounts can't be more than the transaction. Only decide what the person asked you to, or what's plainly right; otherwise ask. This is Truenote's bookkeeping: no money moves at the bank. Say what you decided.
| Body field | Type | Meaning |
|---|---|---|
id * | uuid | The item's id from list_review_items |
decision * | approve | dismiss | |
allocations | object[] | Instead of the proposal's buckets and amounts |
paysBack | uuid[] | A withdrawal: the charge entry ids it pays back, instead of the proposal's |
createRule | boolean | A charge: also save the proposed bucket rule |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/create_bucket_rule
Create a bucket rule
Needs context:write, accounts:read. The same operation as the MCP tool create_bucket_rule; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. A rule that charges matching spending to a bucket automatically ("Banfield" → Pets), for everything since the bucket's start and every sync after: textContains is matched against the merchant and the bank's description (any case); accountId (from list_accounts), categoryId (from get_context) and minAmount/maxAmount (dollars) narrow it. Money out only. The first matching rule wins; a transaction the person already decided (charged by hand, an entry deleted, a review item answered) is left alone, and nothing is ever charged twice. Returns how many it charged right away. Prefer a specific text (a merchant's name) over a broad one: a wrong automatic charge is worse than a review item. Truenote's bookkeeping only: no money moves.
| Body field | Type | Meaning |
|---|---|---|
bucket * | string | A bucket's name or id from list_buckets |
bucketAccountId | uuid | When two accounts have a bucket with that name |
textContains * | string | |
accountId | uuid | Only spending on this account |
categoryId | uuid | Only spending in this category |
minAmount | number | |
maxAmount | number |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/add_note
Remember a fact
Needs context:write. The same operation as the MCP tool add_note; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Save a dated fact or decision for every agent to know later ("Lease ends 2027-06-30", "Decided to pay the Visa in full monthly"). Short tags (lowercase words) help find it. Use for durable context, not for chat history.
| Body field | Type | Meaning |
|---|---|---|
body * | string | |
occurredOn | string | null | The day it's about, if any |
tags | string[] | |
pinned | boolean | Always shown first in get_context |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/update_note
Change a note
Needs context:write. The same operation as the MCP tool update_note; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Change a note (an id from get_context): fix or rewrite its body when a fact or decision changes, re-date it, retag it, or pin or unpin it (pinned notes always come first in get_context). What you leave out stays. Prefer this over adding a second note that contradicts the first. Changes the person's data: say what you changed.
| Body field | Type | Meaning |
|---|---|---|
id * | uuid | |
body | string | |
occurredOn | string | null | The day it's about; null: none |
tags | string[] | Replaces the note's tags |
pinned | boolean |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/upsert_task
Add or change a checklist task
Needs context:write. The same operation as the MCP tool upsert_task; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Add a to-do to the person's checklist, or change one: by id, else by title (any case), else new. Use it for the dated actions a plan leaves behind ("Exercise options before the 409A", due 2026-11-06), one task per action, with a due date when there's a deadline. kind event is a dated thing that happens rather than something to do ("Brooke's due date", "JJ back to work", "Closing on the house"): it needs dueOn and has no checkbox. amount (dollars) is what it costs or brings when it's about money (the $15,000 a payment needs; it reads back as sum). done: true ticks it off, false opens it again. goalId (from get_context) or bucket (a name or id from list_buckets) say what it serves; null unlinks. spotlight: true makes it the Overview's countdown, at the top of the person's home page; there is only one, so this replaces the current one (list_tasks shows which). Spotlight the one dated event that matters most to the household's money in the coming months (a birth, a move, a wedding, a job change, a big payment being saved for), not routine to-dos; the person can change it. What you leave out stays. Truenote's records only: it never acts on the task. Tell the person what you added or changed.
| Body field | Type | Meaning |
|---|---|---|
id | uuid | |
kind | task | event | task (default) or event |
title * | string | |
dueOn | string | null | The day it's due (an event's day); null: no date |
notes | string | |
done | boolean | |
amount | number | null | Dollars; null clears it |
spotlight | boolean | The Overview's countdown; replaces the current one |
goalId | uuid | null | |
bucket | string | null | A bucket's name or id |
bucketAccountId | uuid | When two accounts have a bucket with that name |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/save_plan
Save a plan
Needs context:write. The same operation as the MCP tool save_plan; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Save a named scenario for the person to review: a markdown body and structured assumptions (numbers, dates, lists) such as a parental-leave budget or a tax plan. By id, else by name, else new (status draft). Saving again with the same name updates it.
| Body field | Type | Meaning |
|---|---|---|
id | uuid | |
name * | string | |
summary | string | |
body | string | Markdown |
assumptions | object | |
status | draft | active | archived |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/categorize_transactions
Recategorize transactions
Needs context:write, transactions:read. The same operation as the MCP tool categorize_transactions; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Set the category, entity (household or a business) or excluded flag of up to 200 transactions at once (ids from query_transactions, category and entity ids from get_context), and/or a note. A category, entity or exclusion set here wins over rules for those transactions. All of them change or none do. To fix a whole merchant going forward, use create_rule instead.
| Body field | Type | Meaning |
|---|---|---|
transactionIds * | uuid[] | |
categoryId | uuid | null | null: back to the automatic category |
entityId | uuid | null | |
excluded | boolean | true: leave out of spending and income |
note | string | null |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/create_rule
Create a categorization rule
Needs context:write, transactions:read. The same operation as the MCP tool create_rule; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. A rule that sets the category, entity or excluded flag of every matching transaction, past and future (except ones edited by hand), and/or a business share: businessShare splits each match, percent of it going to a business entity as a part of its own (the rest stays the household's), for a bill partly used for business (internet 60% to the LLC). Conditions all must hold: merchant or description contains text, an account, an amount range in dollars (by size), a direction (outflow is money out). Returns how many transactions it now decides. Prefer this over categorize_transactions or split_transaction for anything recurring.
| Body field | Type | Meaning |
|---|---|---|
name * | string | |
conditions * | object | |
setCategoryId | uuid | null | |
setEntityId | uuid | null | |
setExcluded | boolean | null | |
businessShare | object | null | |
priority | integer | Lower runs first |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/split_transaction
Split a transaction
Needs context:write, transactions:read. The same operation as the MCP tool split_transaction; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Split one transaction (an id from query_transactions; a part's id means its whole) into parts that add up to it exactly, each with its own category, entity and note: the business part of a mixed purchase, a bill shared with the LLC. amount is each part's size in dollars (the whole's direction is kept). The parts count in place of the whole everywhere (spending, the plan, each business's taxes), and query_transactions lists them instead (with splitOf). Replaces any split it had; rules leave it alone afterwards. unsplit: true puts it back together. For a recurring share, use create_rule's businessShare instead. Changes the person's records only: say what you split.
| Body field | Type | Meaning |
|---|---|---|
transactionId * | uuid | |
parts | object[] | |
unsplit | boolean |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/prepare_upload
Prepare a document upload
Needs documents:write. The same operation as the MCP tool prepare_upload; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Get a one-time link to keep one file (a paystub, W-2, 1099, 1098, tax return, statement, receipt; PDF or image, 10 MB at most) in the person's documents. Returns curl, a command that sends a local file straight to Truenote (replace FILE with the path; the file never passes through you), and uploadUrl, a page where the person drops the file themselves (for when you can't reach their files). Say what it is: kind, taxYear, documentOn, and the incomeSourceId a paystub is for (from get_context); name, kind and taxYear left out are guessed from the file's name. One call per file; the link lasts 15 minutes. Then read_document can read it.
| Body field | Type | Meaning |
|---|---|---|
name | string | Default: the file's name |
kind | paystub | w2 | form_1099 | form_1098 | tax_return | benefits | statement | receipt | other | |
taxYear | integer | |
documentOn | string | The date on the document (a paystub's pay date) |
entityId | uuid | Whose it is (default: the household) |
incomeSourceId | uuid | The income source a paystub or W-2 is for |
accountId | uuid | The connected account a statement is of |
manualAccountId | uuid | The manual account (list_accounts) it's about: the mortgage, the HSA |
notes | string |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/update_document
Edit a document
Needs documents:write. The same operation as the MCP tool update_document; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Change what a kept document is and whose (an id from list_documents): name, kind, taxYear, documentOn (the date on it, e.g. a statement's date), entityId, incomeSourceId (the job a paystub or W-2 is for), accountId (a connected account a statement is of), manualAccountId (a manual account it's about, like the mortgage), notes. Fields you leave out keep their value; null clears. The file itself never changes, and documents can't be deleted here. Changes the person's records: say what you changed.
| Body field | Type | Meaning |
|---|---|---|
id * | uuid | |
name | string | |
kind | paystub | w2 | form_1099 | form_1098 | tax_return | benefits | statement | receipt | other | |
taxYear | integer | null | |
documentOn | string | null | |
entityId | uuid | null | |
incomeSourceId | uuid | null | |
accountId | uuid | null | |
manualAccountId | uuid | null | |
notes | string |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/delete_record
Delete a record
Needs context:write. The same operation as the MCP tool delete_record; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Delete one of the person's records by kind and id: goal, income_source, entity (its accounts and transactions go back to the household; rules that set it go too), category (transactions in it fall back to Plaid's mapping; rules that set it go too), rule (what it decided goes back to the other rules), manual_account (with its balance history), plan, plan_group (only once it has no lines), plan_line (something canceled or entered by mistake; for one that ends later, set its endsOn with upsert_plan_line so the plan shows what frees up), task (to finish one, upsert_task with done: true instead), note (when a decision changes, add the new note, then delete the old one so agents don't read both), bucket (its money goes back to Unassigned; no money moves), bucket_rule, bucket_entry (undoes one ledger entry: a move's both sides; an automatic one isn't redone; an archived bucket's entries stay; needs accounts:read). Ids come from get_context, get_plan, list_tasks, list_accounts, list_buckets and list_bucket_rules. Documents use delete_document. It can't be undone: confirm with the person first (a note or entry you made in this conversation excepted), then say what you deleted.
| Body field | Type | Meaning |
|---|---|---|
kind * | goal | income_source | entity | category | rule | manual_account | plan | plan_group | bucket | bucket_rule | plan_line | task | note | bucket_entry | |
id * | uuid |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/delete_document
Delete a document
Needs documents:write. The same operation as the MCP tool delete_document; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Permanently delete an uploaded document (an id from list_documents): the file and its text. Use when the person asks, or for a duplicate or wrong upload. It can't be undone: confirm with the person first.
| Body field | Type | Meaning |
|---|---|---|
id * | uuid |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/upsert_category
Add or change a category
Needs context:write. The same operation as the MCP tool upsert_category; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Add a spending category, or rename one, change its kind (expense, income, transfer: transfers don't count as spending) or nest it under a parent (parentId, null: top level): by id, else by name (any case), else new (then kind is required). Which of the bank's categories land in it stays the person's to set in Truenote. Then categorize_transactions or create_rule to fill it. Say what you changed.
| Body field | Type | Meaning |
|---|---|---|
id | uuid | |
name * | string | |
kind | expense | income | transfer | |
parentId | uuid | null |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/update_rule
Change a categorization rule
Needs context:write, transactions:read. The same operation as the MCP tool update_rule; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Change a rule (an id from get_context): its name, conditions (given conditions replace them all; amounts in dollars by size), what it sets (category, entity, excluded; null clears), its businessShare (null removes it), priority (lower runs first) or active (false pauses it). What you leave out stays. Saving re-decides every transaction nobody edited by hand. To remove a rule, delete_record with kind rule.
| Body field | Type | Meaning |
|---|---|---|
id * | uuid | |
name | string | |
conditions | object | |
setCategoryId | uuid | null | |
setEntityId | uuid | null | |
setExcluded | boolean | null | |
businessShare | object | null | |
priority | integer | |
active | boolean |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/update_account
Change a bank account's settings
Needs context:write, accounts:read. The same operation as the MCP tool update_account; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Change how Truenote treats a connected account (an id from list_accounts): nickname (null: the bank's name again), hidden (true drops it out of totals, net worth and this connection; only the person can show it again unless this connection sees hidden accounts) or entityId (the business it belongs to; null: the household). Never touches the bank. Say what you changed.
| Body field | Type | Meaning |
|---|---|---|
accountId * | uuid | |
nickname | string | null | |
hidden | boolean | |
entityId | uuid | null |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/reset_transactions
Undo transaction edits
Needs context:write, transactions:read. The same operation as the MCP tool reset_transactions; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Undo hand edits on transactions (ids from query_transactions): their category, entity, exclusion and any hand split go back to what the rules decide. Notes stay. Use when an earlier categorize_transactions or split_transaction was wrong.
| Body field | Type | Meaning |
|---|---|---|
transactionIds * | uuid[] |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/refresh_bank
Refresh a bank connection
Needs context:write, accounts:read. The same operation as the MCP tool refresh_bank; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Ask the bank for what's new on the connection holding an account (an id from list_accounts): balances and transactions, in the background (a minute or two). Use when the person says something posted that Truenote doesn't show yet. A connection that needs the person to sign in again can't be refreshed: tell them to reconnect it in Truenote.
| Body field | Type | Meaning |
|---|---|---|
accountId * | uuid |
Responses: 200, 400, 401, 403, 404, 409
POST /v1/tools/disconnect_bank
Disconnect a bank
Needs context:write, accounts:read. The same operation as the MCP tool disconnect_bank; amounts in the body are dollars (650 for $650.00), ids come from the GET routes, and the answer is the tool's data with amounts in integer cents. Remove the bank connection holding an account (an id from list_accounts): Truenote stops syncing it, and every account on that connection and its history are deleted from Truenote. Truenote is read-only to banks: nothing changes at the bank itself. Connecting it again means the person signs in to the bank in Truenote. It can't be undone: confirm with the person first, naming every account it removes.
| Body field | Type | Meaning |
|---|---|---|
accountId * | uuid |
Responses: 200, 400, 401, 403, 404, 409
* required