Money
Amounts are decimal strings, never floats, and never minor units:currency, an ISO 4217 code.
Transaction amounts are signed: negative is money leaving the workspace,
positive is money coming in. The direction field, "inflow" or
"outflow", is derived from the sign, so the two never disagree and you can
group by direction without parsing. Summing amount over any set of
transactions gives their net movement.
Dates
Transactions and forecasts have adate, an ISO 8601 calendar date such as
"2026-09-03". There is no time component. Date filters are inclusive on
both ends.
Audit fields created_at and updated_at are UTC timestamps in RFC 3339
format.
Objects and references
Every object has anobject field naming its type and an id, a UUID
string. Related objects come back as ids:
expand, naming the relations you want,
comma-separated. Each id is then replaced by the object it points at:
account, category, counterparty and
labels. On a forecast, category, counterparty, labels and
linked_transactions. On an account, bank. Expansion goes one level deep,
and an unsupported name is rejected with 400 invalid_expand.
An account’s bank is the id of the connection feeding it, null for
accounts kept by hand:
/accounts?bank_id=<id>.
Nullable references are null, never omitted. Arrays are [] when empty.
Forecasts
A forecast is money you plan to receive or pay on adate. Its amount is
what you planned, signed like a transaction amount. Its remaining_amount is
the part no transaction has matched yet, so the two are equal until the first
transaction is linked, and remaining_amount is 0.00 once the forecast is
realised.
A forecast has a category, optionally a counterparty and labels, and a
recurrence when a recurring plan generated it. Like the category and the
counterparty, recurrence is an id unless you name it in expand, which
returns the plan: its name, how often it repeats, and its first and last date.
linked_transactions holds the transactions matched to it, as ids, or as
objects with expand=linked_transactions.
Forecasts have no account. An account_id or bank_id filter applies to
transactions and balances, never to forecasts.
/forecasts returns the forecasts that have a date.
Enumerations
All enumerations are lowercase strings.
New values may be added in a minor release. Treat unknown values as opaque
rather than failing.
Lists and summaries
/transactions is a bank statement: one row per movement, and every movement
is a row. A transfer between two of your own accounts is two rows, one on
each account, and a transaction someone excluded from cash flow inside
ThinkOut is still a row. Nothing marks either.
/transactions/summary computes totals with the same rules as the cash flow
screen:
- transfers between your own accounts are excluded
- movements the user excluded are left out
- split payments are counted once
- foreign currency transactions are converted at the rate on the movement date
Summing a list counts every transfer twice. For figures that match what
ThinkOut shows, use the summary endpoint.
totals block for the whole
window, so you never add rows yourself:
null whenever group_by or direction is given. On
/balances each account converts whole at one rate; the conversion object
on every report says which basis applied.
/forecasts/summary totals forecasts the same way, one row per period and a
totals block: amount is what was planned and remaining_amount what is
still expected. Every status counts, so a realised forecast adds to amount
and nothing to remaining_amount.
Cash flow summary
/cash-flow/summary returns what the cash flow screen shows, in two halves.
The actual half is the transactions in the period, counted as
/transactions/summary counts them. The forecast half is the
remaining_amount of the forecasts still open in the period. inflow,
outflow and net add the two halves and equal the screen’s cells:
final_balance is the projected balance, the balance
carried forward through the forecasts up to the period’s end.
Overdue forecasts, dated before today and still open, are not counted in the
rows, as on the screen. Pass include_overdue=true to count them in the
periods they are dated in. totals.overdue_inflow and
totals.overdue_outflow always report the overdue amounts the rows did not
count.
An
account_id or bank_id filter narrows the actual half and the
balances. Forecasts have no account, so the forecast half always covers the
whole workspace; forecast_scope in the response says so.