API · 49 operations

Reports

Spending reports, age of money, cash flow, net worth.

get /v1/budgets/{budget_id}/report

Spending and funding across a window of months

scope budget:read · getReport

Parameters

  • budget_id path · string · required

  • from query · string

  • to query · string

  • include_closed query · string

Returns · 200

{ from, to, total_income_cents, total_assigned_cents, total_activity_cents, months: [{ month, income_cents, assigned_cents, activity_cents }], envelopes: [{ envelope_id, name, status, total_assigned_cents, total_activity_cents, months: [{ month, assigned_cents, activity_cents }] }] }. Answers "where did it go" and "is this month unusual" in one call. Activity is signed like entries, so spending is NEGATIVE. As in YNAB, an account starting balance is not income and a card payment envelope is not spending (the purchases it paid for were counted when they were made); neither appears. Every month in the window appears even when nothing happened, so a gap means a quiet month rather than a missing one. Defaults to the last six months including the current one; from/to take YYYY-MM (or a full date, which is read as its month) and the window is capped at 24 months; past that, export the budget and work on the whole thing. Drawn entirely from the same cached monthly totals the month view reads: Mintro stores no history beyond them. Closed envelopes are left out, rows and totals alike, unless include_closed=true, because a retired pot that money was once filed INTO would otherwise net against what was spent.

Example

curl 'https://api.mintro.money/v1/budgets/default/report' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/budgets/{budget_id}/reports/spending

Where the money went, by category, envelope or payee

scope budget:read · getSpendingReport

Parameters

  • budget_id path · string · required

  • from query · string

  • to query · string

  • group query · string

Returns · 200

{ from, to, group, total_cents, average_cents, last_vs_prior_bp, months: [{ month, spent_cents }], groups: [{ key, name, category, count, total_cents, average_cents, share_bp, last_vs_prior_bp, months: [{ month, spent_cents }] }], chart: { series: [{ key, name }], ticks, months: [{ month, total_cents, parts: [{ key, cents, low_ratio, high_ratio }] }] } }, largest first. count is how many transactions were filed into the group in the window; category names an envelope's category (null when grouped by category or payee). chart stacks spending by month: the five biggest groups bottom to top, then other ("Everything else") for the rest; a month that netted a refund has no height. Spending is POSITIVE here: what left envelopes, net of refunds. group is category (default), envelope or payee (the person's payee, else the bank's name). share_bp is the group's share of all spending in basis points; last_vs_prior_bp the last month against the average of the earlier ones (null without history). Card payment envelopes stay out, as in YNAB: the purchases they paid were counted where they were made. Chart fields are 0..1 ratios on one scale with round ticks: [{ cents, ratio }], ready to draw as bars; the cents beside them are the figures. from/to take YYYY-MM (or a full date, read as its month); the last six months including this one by default, at most 24.

Example

curl 'https://api.mintro.money/v1/budgets/default/reports/spending' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/budgets/{budget_id}/reports/plan

Plan against what happened, an envelope at a time

scope budget:read · getPlanReport

Parameters

  • budget_id path · string · required

  • from query · string

  • to query · string

Returns · 200

{ from, to, months, total_assigned_cents, total_spent_cents, envelopes: [{ envelope_id, name, category, target_kind, total_assigned_cents, total_spent_cents, gap_cents, months_active, months_overspent, months_target_met, months: [{ month, assigned_cents, spent_cents, available_cents, target_cents, target_met }] }] }. available_cents is what was left at the month's end, walked from every earlier month as the month view walks it; negative is overspent, and months_overspent counts them out of months_active (from the envelope's first assignment or spending in the window). target_met is only claimed for a monthly target (assigned reached it); null otherwise. gap_cents is spent less assigned across the window. Open envelopes with any activity; card payment envelopes stay out. Also month_totals: [{ month, assigned_cents, spent_cents, target_cents }] (every envelope in the view; target_cents adds up the monthly targets, null when none), chart: { ticks, zero_ratio, months: [{ month, assigned, spent, target }] } drawing them, and categories: [{ name, envelopes, assigned_cents, spent_cents, target_cents, gap_cents, envelopes_ran_out, assigned_ratio, spent_ratio, target_ratio }], most spent first, with category_ticks. Chart fields are 0..1 ratios on one scale with round ticks: [{ cents, ratio }], ready to draw as bars; the cents beside them are the figures. from/to take YYYY-MM (or a full date, read as its month); the last six months including this one by default, at most 24.

Example

curl 'https://api.mintro.money/v1/budgets/default/reports/plan' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/budgets/{budget_id}/reports/income

Income against spending, savings rate, card debt and repeating bills

scope budget:read · getIncomeReport

Parameters

  • budget_id path · string · required

  • from query · string

  • to query · string

Returns · 200

{ from, to, total_income_cents, total_spent_cents, total_kept_cents, savings_rate_bp, bills_yearly_cents, months: [{ month, income_cents, spent_cents, kept_cents, savings_rate_bp, card_debt_cents, age_of_money_days }], bills: [{ recurring_id, name, cadence, amount_cents, yearly_cents }] }. Income leaves out starting balances and holding sales, as /report does. Kept is income less spending; the savings rate is kept over income in basis points, null in a month with no income. card_debt_cents is what every card owed at the month's end; age_of_money_days age of money then. bills are active repeating outflows at what they cost a year. chart: { ticks, zero_ratio, months: [{ month, income, spent, kept }] } draws in, out and kept on one scale (kept below zero when more went out); card_debt_chart: { ticks, zero_ratio, months: [{ month, debt }] } draws card debt on its own. Chart fields are 0..1 ratios on one scale with round ticks: [{ cents, ratio }], ready to draw as bars; the cents beside them are the figures. from/to take YYYY-MM (or a full date, read as its month); the last six months including this one by default, at most 24.

Example

curl 'https://api.mintro.money/v1/budgets/default/reports/income' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/budgets/{budget_id}/reports/insights

The few facts worth saying about a window

scope budget:read · getReportInsights

Parameters

  • budget_id path · string · required

  • from query · string

  • to query · string

Returns · 200

{ from, to, insights: [...] }, each with a kind and its figures, never prose: rising / falling (an envelope's last month against its average, at least 25% and $50 either way), overspent_often (ran out in at least half the months), savings_rate or spent_more_than_earned, top_payee (10% or more of spending), card_debt (start and end of the window), bills_yearly. Say them in the person's words. from/to take YYYY-MM (or a full date, read as its month); the last six months including this one by default, at most 24.

Example

curl 'https://api.mintro.money/v1/budgets/default/reports/insights' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/budgets/{budget_id}/picture

The entire picture: accounts, holdings, loans, net, aim projection

scope budget:read · plan feature assets · getPicture

Parameters

  • budget_id path · string · required

Returns · 200

{ home_currency, fx_stale, unconverted[], accounts_cents, holdings_cents, loans_cents, net_cents, holdings[], loans[], aims[], aim }. Every figure is in home_currency: a holding or loan kept in another currency carries home_cents and fx: { rate, as_of, source, stale } beside its own figure, counts as nothing when no rate is stored, and is then named in unconverted; fx_stale asks for POST …/fx/refresh. Holdings carry equity_cents and the loans against them, both in home. Net is accounts working balances + open holdings − open loans. Aim includes interpolation when a target year is set. Numbers come from caches and declared snapshots; nothing here scans entries. Each loan in loans[] carries payoff as on GET …/loans.

Example

curl 'https://api.mintro.money/v1/budgets/default/picture' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/budgets/{budget_id}/net-worth

Net worth over the last months, computed on read

scope budget:read · plan feature assets · readNetWorth

Parameters

  • budget_id path · string · required

  • months query · string

Returns · 200

{ months, points[], zero_ratio, ticks[], change_cents, stack, owned_change_cents, owed_change_cents }. Each point is a month's end: accounts from the transactions to that date, holdings at their latest valuation by then, loans at their latest balance by then, and net_ratio 0..1 for a chart. The last point is today and equals GET /picture. Each point also carries owned_cents (accounts plus holdings) and, on the stack scale (stack: { zero_ratio, ticks[] }), owned_ratio above zero, owed_ratio (the loans, drawn below zero) and stack_net_ratio, so what you own and what you owe can be drawn apart on one axis. owned_change_cents and owed_change_cents are each side's last minus first, beside change_cents for the net. ?months= 1..60, default 24. Stored nowhere.

Example

curl 'https://api.mintro.money/v1/budgets/default/net-worth' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/budgets/{budget_id}/found-money

Where money is sitting that could be moving toward the first goal

scope budget:read · plan feature goals · findMoney

Parameters

  • budget_id path · string · required

  • month query · string

Returns · 200

{ month, aim, candidates[], total_once_cents, total_monthly_cents, total_later_cents, later_by_month }. aim is the first open goal not yet reached (debts aside). Three kinds of candidate, in this order: now: ready_to_assign (unassigned income), overfunded (an envelope holding more than this month needs; goal and card envelopes excluded), once_inflow (expected money on the baseline left in cash or spend), with action assign, move or add_event and to_envelope_id; room: underspend (a monthly target at least $25 and a tenth above its three-month average spend, on an envelope that is spent from and not overspent) with target_cents, average_spent_cents, suggested_target_cents and action: trim_target; later: liability_ends (a payment that frees up on from_month) with action: none, since there is nothing to do today. Every candidate carries months_sooner the goal would be reached and suggested_monthly_cents for the goal. A read: nothing here moves money; the actions it names are the ordinary ones (assign, move, update the envelope's target, edit the baseline's events). ?month= reads another month.

Example

curl 'https://api.mintro.money/v1/budgets/default/found-money' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/cashflow/alerts

The budget's cashflow alerts and where each line stands

scope budget:read · listCashflowAlerts

Parameters

  • budget_id query · string

Returns · 200

{ alerts: [{ id, budget_id, scope, account_id, horizon_days, threshold, amount_cents, count, in_app, agent, email, email_amounts, enabled, created_by, email_reaches_creator, created_at, updated_at, crossings }] }, oldest first. Each of crossings is one line of the alert (All cash, or one account): { id, account_id, account_name, on, cause, threshold_cents, lowest_cents, short_cents, cover_account_id, cover_account_name, cover_headroom_cents, fired_on, fired_at, cleared_at, active, dismissed, no_line, muted }. on is the first projected day at or below the threshold within the horizon (null when the line stays clear); short_cents is how far under the threshold the lowest day goes; cover_* names another cash account whose own line stays clear through the horizon by at least that much (a suggestion only: nothing is moved). fired_on is the crossing date last reported. active is true while it crosses, the alert is on with in_app, and the banner for that date was not dismissed. no_line is true when the alert watches the Warning line and that line has none set: it cannot cross until one is (threshold_cents is null). muted is true while the person muted the line until it clears. Alerts are re-checked after anything that moves the projection: a sync landing rows, a transaction or bill written, the line's Alarm or Warning changed, an alert saved. When it speaks: the banner and the agent event update at once. The email waits 15 minutes and reads the line again, so a crossing fixed in that time sends nothing. A crossing is said again only when it moves at least 3 days earlier; one reminder goes when it is 2 days away or less (state: soon is email only); a line counts as cleared, and re-arms, only after it has stayed clear for a full day; never more than one email per alert and line a day; a line already below gets one email, then the banner carries it.

Example

curl 'https://api.mintro.money/v1/cashflow/alerts' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

post /v1/cashflow/alerts

Make a cashflow alert

scope budget:write · createCashflowAlert

Parameters

  • budget_id query · string

Body · JSON

  • budget_id string · ≤ 64 chars

  • scope "all_cash" | "each_account" | "account" · required

  • account_id string | null · ≤ 64 chars

  • horizon_days integer · ≥ 1 · ≤ 90

  • threshold "zero" | "warning_line" | "red_line" | "amount" · required

  • amount_cents integer | null · ≥ -9007199254740991

  • count "bills_only" | "with_spending"

  • in_app boolean

  • agent boolean

  • email boolean

  • email_amounts boolean

  • enabled boolean

Returns · 201

{ id, budget_id, scope, account_id, horizon_days, threshold, amount_cents, count, in_app, agent, email, email_amounts, enabled, created_by, email_reaches_creator, created_at, updated_at, crossings }. scope: all_cash (every cash account summed), each_account (each cash account on its own, reported per account), or account with account_id (one cash account; a credit card is refused, its line is its limit on GET /v1/cashflow). horizon_days 1 to 90 (default 14; the app offers 14, 30 and 90). threshold: zero (at or below $0), warning_line (at or below the line's Warning amount, the ok_cents GET /v1/cashflow reports in bands, where the line turns yellow; a line with no Warning set never crosses), red_line (at or below the line's Alarm amount, the low_cents in bands, where the line turns red), or amount with amount_cents. count is which view of the line the alert reads: with_spending (the default: declared bills and income plus the projected envelope spending, the same as points on GET /v1/cashflow) or bills_only (declared bills and income only, points_bills_only). With nothing projected the two are the same. Channels: in_app (default true: a banner on Budget, Accounts and Cashflow), agent (default false: a cashflow.alert event in GET /v1/events and to webhooks, with ids, the day, the cause and amounts), email (default false: one email per crossing to the person who made the alert, when they have a verified address). email_amounts (default false) puts amounts in that email; without it the email names the line, the day and the cause only. Nothing is made for anyone: an alert exists because someone asked for it. It reads the projection and never changes the budget.

Example

curl -X POST 'https://api.mintro.money/v1/cashflow/alerts' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "scope": "each_account", "threshold": "zero", "horizon_days": 14, "email": true }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/cashflow/alerts/{id}

One cashflow alert

scope budget:read · getCashflowAlert

Parameters

  • id path · string · required

  • budget_id query · string

Returns · 200

{ id, budget_id, scope, account_id, horizon_days, threshold, amount_cents, count, in_app, agent, email, email_amounts, enabled, created_by, email_reaches_creator, created_at, updated_at, crossings }. Each of crossings is one line of the alert (All cash, or one account): { id, account_id, account_name, on, cause, threshold_cents, lowest_cents, short_cents, cover_account_id, cover_account_name, cover_headroom_cents, fired_on, fired_at, cleared_at, active, dismissed, no_line, muted }. on is the first projected day at or below the threshold within the horizon (null when the line stays clear); short_cents is how far under the threshold the lowest day goes; cover_* names another cash account whose own line stays clear through the horizon by at least that much (a suggestion only: nothing is moved). fired_on is the crossing date last reported. active is true while it crosses, the alert is on with in_app, and the banner for that date was not dismissed. no_line is true when the alert watches the Warning line and that line has none set: it cannot cross until one is (threshold_cents is null). muted is true while the person muted the line until it clears.

Example

curl 'https://api.mintro.money/v1/cashflow/alerts/<id>' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

patch /v1/cashflow/alerts/{id}

Change a cashflow alert

scope budget:write · updateCashflowAlert

Parameters

  • id path · string · required

  • budget_id query · string

Body · JSON

  • budget_id string · ≤ 64 chars

  • scope "all_cash" | "each_account" | "account"

  • account_id string | null · ≤ 64 chars

  • horizon_days integer · ≥ 1 · ≤ 90

  • threshold "zero" | "warning_line" | "red_line" | "amount"

  • amount_cents integer | null · ≥ -9007199254740991

  • count "bills_only" | "with_spending"

  • in_app boolean

  • agent boolean

  • email boolean

  • email_amounts boolean

  • enabled boolean

Returns · 200

{ id, budget_id, scope, account_id, horizon_days, threshold, amount_cents, count, in_app, agent, email, email_amounts, enabled, created_by, email_reaches_creator, created_at, updated_at, crossings }. Any field of POST. Changing the line, the horizon, the threshold or count asks a new question, so what was reported before is forgotten; changing only channels or enabled keeps it.

Example

curl -X PATCH 'https://api.mintro.money/v1/cashflow/alerts/<id>' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "budget_id": "<budget_id>", "scope": "all_cash" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

delete /v1/cashflow/alerts/{id}

Remove a cashflow alert

scope budget:write · deleteCashflowAlert

Parameters

  • id path · string · required

  • budget_id query · string

Returns · 200

{ deleted: true }.

Example

curl -X DELETE 'https://api.mintro.money/v1/cashflow/alerts/<id>' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

post /v1/cashflow/alerts/{id}/dismiss

Hide one crossing's banner

scope budget:write · dismissCashflowAlert

Parameters

  • id path · string · required

  • budget_id query · string

Body · JSON

  • budget_id string · ≤ 64 chars

  • crossing_id string · required · ≤ 64 chars

Returns · 200

{ id, budget_id, scope, account_id, horizon_days, threshold, amount_cents, count, in_app, agent, email, email_amounts, enabled, created_by, email_reaches_creator, created_at, updated_at, crossings }. The crossing named by crossing_id stops being active for its current date. A crossing on a different date shows again.

Example

curl -X POST 'https://api.mintro.money/v1/cashflow/alerts/<id>/dismiss' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "crossing_id": "<crossing_id>" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

post /v1/cashflow/alerts/{id}/mute

Mute one line of an alert until it clears

scope budget:write · muteCashflowAlert

Parameters

  • id path · string · required

  • budget_id query · string

Body · JSON

  • budget_id string · ≤ 64 chars

  • crossing_id string · required · ≤ 64 chars

Returns · 200

{ id, budget_id, scope, account_id, horizon_days, threshold, amount_cents, count, in_app, agent, email, email_amounts, enabled, created_by, email_reaches_creator, created_at, updated_at, crossings }. The crossing named by crossing_id gets no banner and no email until its line has stayed clear for a full day; then it re-arms by itself. cashflow.alert events still go to agents, with muted: true.

Example

curl -X POST 'https://api.mintro.money/v1/cashflow/alerts/<id>/mute' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "crossing_id": "<crossing_id>" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/cashflow/alert-kinds

The two alert kinds: payday missed and bill bigger than usual

scope budget:read · listCashflowAlertKinds

Parameters

  • budget_id query · string

Returns · 200

{ kinds: [{ kind, budget_id, enabled, in_app, agent, email, email_amounts, email_reaches_creator, rule, updated_at, notices }] }, always both, payday_missed first. A kind nobody saved reads enabled: false with the defaults (in_app true, the rest false). kind is payday_missed or bill_bigger. payday_missed: an active scheduled income with an account (a recurring row with a positive amount) whose date passed 2 business days ago (rule.grace_business_days, Saturday and Sunday do not count) with nothing like it landed on that account: no deposit from 3 days before the date on (not a transfer, not another bill's row) that either shares a word with its name or is at least half its amount. Entering it, or moving its date, clears it too. A date more than 30 days back is past watching (rule.lookback_days). bill_bigger: a posted row of an active scheduled bill (entered from it, adopted onto it, or a row whose payee is the bill's name on its account; pending rows never count) from the last 14 days (rule.recent_days) that is at least 20% (rule.over_percent) and at least $10 (rule.over_min_cents) over the bill's usual amount, the larger of its scheduled amount and the average of its last three earlier rows (when it has two or more). Each of notices is one occurrence: { id, kind, account_id, account_name, recurring_id, transaction_id, name, on, expected_cents, actual_cents, over_cents, over_percent, usual_basis, scheduled_cents, lowest_cents, lowest_on, below_on, with_it_lowest_cents, fired_at, cleared_at, active, dismissed, muted, emailed }. For a payday, on is the date it was due, expected_cents what was scheduled, and the line without it from today through the next month: lowest_cents on lowest_on, below_on the first day at or below the account's Alarm line (null when it stays above), with_it_lowest_cents what that lowest would be had it landed. The cashflow line already leaves a late pay out. For a bill, on is the day it posted, actual_cents what posted, expected_cents its usual amount (usual_basis scheduled or average), over_cents and over_percent by how much, scheduled_cents what the line still counts next time. active is true while it stands, the kind is on with in_app, and nobody muted or dismissed it. cleared_at is set once the pay landed or the bill stopped being bigger (fixed, removed, or older than the window); cleared notices stay listed a while. Checked after a change (a sync landing rows, a transaction or bill written, the kind saved), and as days pass by the six-hourly bank sync, never on a clock of its own. Each occurrence is said once: the banner and a cashflow.notice event (with agent) at once; the email (with email, to the person who turned the kind on) waits 15 minutes and reads again, names each notice once, and goes at most once a day per kind, carrying every notice not yet emailed. Amounts go in the email only with email_amounts; the percentage always does.

Example

curl 'https://api.mintro.money/v1/cashflow/alert-kinds' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/cashflow/alert-kinds/{kind}

One alert kind

scope budget:read · getCashflowAlertKind

Parameters

  • kind path · string · required

  • budget_id query · string

Returns · 200

{ kind, budget_id, enabled, in_app, agent, email, email_amounts, email_reaches_creator, rule, updated_at, notices }. Each of notices is one occurrence: { id, kind, account_id, account_name, recurring_id, transaction_id, name, on, expected_cents, actual_cents, over_cents, over_percent, usual_basis, scheduled_cents, lowest_cents, lowest_on, below_on, with_it_lowest_cents, fired_at, cleared_at, active, dismissed, muted, emailed }. For a payday, on is the date it was due, expected_cents what was scheduled, and the line without it from today through the next month: lowest_cents on lowest_on, below_on the first day at or below the account's Alarm line (null when it stays above), with_it_lowest_cents what that lowest would be had it landed. The cashflow line already leaves a late pay out. For a bill, on is the day it posted, actual_cents what posted, expected_cents its usual amount (usual_basis scheduled or average), over_cents and over_percent by how much, scheduled_cents what the line still counts next time. active is true while it stands, the kind is on with in_app, and nobody muted or dismissed it. cleared_at is set once the pay landed or the bill stopped being bigger (fixed, removed, or older than the window); cleared notices stay listed a while.

Example

curl 'https://api.mintro.money/v1/cashflow/alert-kinds/<kind>' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

patch /v1/cashflow/alert-kinds/{kind}

Turn an alert kind on or off, or change where it is heard

scope budget:write · setCashflowAlertKind

Parameters

  • kind path · string · required

  • budget_id query · string

Body · JSON

  • budget_id string · ≤ 64 chars

  • enabled boolean

  • in_app boolean

  • agent boolean

  • email boolean

  • email_amounts boolean

Returns · 200

{ kind, budget_id, enabled, in_app, agent, email, email_amounts, email_reaches_creator, rule, updated_at, notices }. Fields left out keep their value. enabled turns it on; in_app (a banner on Budget, Accounts and Cashflow), agent (a cashflow.notice event in GET /v1/events and to webhooks), email and email_amounts say where. Turn one on only when the human asks. kind is payday_missed or bill_bigger. payday_missed: an active scheduled income with an account (a recurring row with a positive amount) whose date passed 2 business days ago (rule.grace_business_days, Saturday and Sunday do not count) with nothing like it landed on that account: no deposit from 3 days before the date on (not a transfer, not another bill's row) that either shares a word with its name or is at least half its amount. Entering it, or moving its date, clears it too. A date more than 30 days back is past watching (rule.lookback_days). bill_bigger: a posted row of an active scheduled bill (entered from it, adopted onto it, or a row whose payee is the bill's name on its account; pending rows never count) from the last 14 days (rule.recent_days) that is at least 20% (rule.over_percent) and at least $10 (rule.over_min_cents) over the bill's usual amount, the larger of its scheduled amount and the average of its last three earlier rows (when it has two or more). Checked after a change (a sync landing rows, a transaction or bill written, the kind saved), and as days pass by the six-hourly bank sync, never on a clock of its own. Each occurrence is said once: the banner and a cashflow.notice event (with agent) at once; the email (with email, to the person who turned the kind on) waits 15 minutes and reads again, names each notice once, and goes at most once a day per kind, carrying every notice not yet emailed. Amounts go in the email only with email_amounts; the percentage always does.

Example

curl -X PATCH 'https://api.mintro.money/v1/cashflow/alert-kinds/<kind>' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "enabled": true, "in_app": true, "email": true }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

post /v1/cashflow/alert-kinds/{kind}/dismiss

Hide one notice's banner

scope budget:write · dismissCashflowNotice

Parameters

  • kind path · string · required

  • budget_id query · string

Body · JSON

  • budget_id string · ≤ 64 chars

  • notice_id string · required · ≤ 64 chars

Returns · 200

{ kind, budget_id, enabled, in_app, agent, email, email_amounts, email_reaches_creator, rule, updated_at, notices }. The notice named by notice_id stops being active. Email is not affected.

Example

curl -X POST 'https://api.mintro.money/v1/cashflow/alert-kinds/<kind>/dismiss' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "notice_id": "<notice_id>" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

post /v1/cashflow/alert-kinds/{kind}/mute

Mute one notice until it clears

scope budget:write · muteCashflowNotice

Parameters

  • kind path · string · required

  • budget_id query · string

Body · JSON

  • budget_id string · ≤ 64 chars

  • notice_id string · required · ≤ 64 chars

Returns · 200

{ kind, budget_id, enabled, in_app, agent, email, email_amounts, email_reaches_creator, rule, updated_at, notices }. The notice named by notice_id gets no banner and no email until it clears (the pay lands, the bill is fixed). cashflow.notice events still go to agents, with muted: true.

Example

curl -X POST 'https://api.mintro.money/v1/cashflow/alert-kinds/<kind>/mute' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "notice_id": "<notice_id>" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/cashflow/digest

The person's weekly cashflow digest

scope budget:read · getCashflowDigest

Parameters

  • budget_id query · string

Returns · 200

{ person, enabled, day, time_zone, count, email_amounts, budget_id, budget_name, reads_this_budget, next_on, last_sent_on }. Off until the person turns it on. One per person per household: budget_id is the budget it reads (null before it was ever saved) and reads_this_budget says whether that is the budget asked about. next_on is the person's local date of the next one (null while off). person is false for a key nobody in the app made, which cannot have a digest. One email a week, on day (monday to sunday) from seven in the morning in time_zone (an IANA zone such as America/Toronto; default UTC), about one budget: the cashflow line's lowest point in the month ahead and the day it lands, whether it goes below Alarm, safe to spend through the next income, the alert lines below right now (up to three named, the rest counted), and how many bills are due in the next seven days with their total, ending with a link to Cashflow. count is the view it reads: with_spending (the default: bills, income and projected envelope spending) or bills_only. email_amounts (default false) puts figures in it; without it the email says the same things in words. It goes to the person who turned it on and nobody else, never more than once in seven days (moving the day mid-week waits for the next week), and it never changes anything. Every email carries a link that turns it off without signing in.

Example

curl 'https://api.mintro.money/v1/cashflow/digest' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

patch /v1/cashflow/digest

Turn the weekly cashflow digest on or off, pick its day

scope budget:write · setCashflowDigest

Parameters

  • budget_id query · string

Body · JSON

  • budget_id string · ≤ 64 chars

  • enabled boolean

  • day "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday"

  • time_zone string · ≤ 64 chars

  • count "bills_only" | "with_spending"

  • email_amounts boolean

Returns · 200

{ person, enabled, day, time_zone, count, email_amounts, budget_id, budget_name, reads_this_budget, next_on, last_sent_on }. Fields left out keep their value. Saving points the digest at this budget. Turn it on only when the human asks, and ask which day. Saving sends nothing: the first goes on the next chosen day. One email a week, on day (monday to sunday) from seven in the morning in time_zone (an IANA zone such as America/Toronto; default UTC), about one budget: the cashflow line's lowest point in the month ahead and the day it lands, whether it goes below Alarm, safe to spend through the next income, the alert lines below right now (up to three named, the rest counted), and how many bills are due in the next seven days with their total, ending with a link to Cashflow. count is the view it reads: with_spending (the default: bills, income and projected envelope spending) or bills_only. email_amounts (default false) puts figures in it; without it the email says the same things in words. It goes to the person who turned it on and nobody else, never more than once in seven days (moving the day mid-week waits for the next week), and it never changes anything. Every email carries a link that turns it off without signing in.

Example

curl -X PATCH 'https://api.mintro.money/v1/cashflow/digest' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "enabled": true, "day": "monday", "time_zone": "America/Toronto" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

post /v1/unsubscribe/{token}

Turn off a weekly cashflow digest from its email

no key needed · turnOffCashflowDigest

Parameters

  • token path · string · required

Returns · 200

{ off: true }, whatever the token: an unknown one changes nothing and says nothing about who exists. No key: token is the one in the digest email's turn-off link, and it can only switch that one digest off. Mail clients that offer one-click unsubscribe post here. To turn a digest off as its person, use PATCH /v1/cashflow/digest with enabled: false.

Example

curl -X POST 'https://api.mintro.money/v1/unsubscribe/<token>'

post /v1/cashflow/preview

What if: the cashflow line with a purchase, an income or a moved bill. Writes nothing

scope budget:read · previewCashflow

Parameters

  • budget_id query · string

Body · JSON

  • budget_id string · ≤ 64 chars

  • account_id string · ≤ 64 chars

  • from string · YYYY-MM-DD

  • until string · YYYY-MM-DD

  • back string · matches ^\d{1,4}[dwmDWM]?$

  • ahead string · matches ^\d{1,4}[dwmDWM]?$

  • events object | object | object | object[] · required · ≤ 20 items

  • lump_on string · YYYY-MM-DD

Returns · 200

Everything GET /v1/cashflow returns, for the line WITH the what-ifs, plus what_if { events, real, ghost, check }. This is the answer to 'can I afford it?' Pass the read's own account_id, from, until, back and ahead to try it on the same line. Send what the person is thinking about in events (1 to 20): { kind: spend | income, account_id, on, amount_cents, name? } is a one-off amount on a day, today or later (amount_cents positive; the kind gives the sign); { kind: move_bill, recurring_id, date } tries a bill's next date on another day, exactly as POST /v1/budgets/{budget_id}/recurring/{id}/move with scope: occurrence would move it (the schedule after it is kept, and the date must be before the bill's following date). { kind: spend_less, envelope_id, amount_cents, until } tries spending less from one envelope the line projects: that much off its projected spending, spread evenly per day from tomorrow through until (after today), never more than it projects over those days. Instead of amount_cents it can give keep_to_cents, what to keep the envelope to over those days, and the amount is what it usually spends then less that (refused when that is not less). Its what_if.events entry then says the change both ways: amount_cents, keep_to_cents, usual_cents (what the chart expects it to spend over those days), per_day_cents, from, title ('Keep Groceries to $301.28 from Oct 31 to Nov 5'), per_day_words ('About $50.21 a day.') usual_words ('You usually spend $858.40 in that time, so this saves $557.12.') and compare { days, usual_cents, usual_per_day_cents, keep_cents, keep_per_day_cents, saves_cents, saves_per_day_cents }, before and after a day and in all, to set side by side. It is on the line with spending only; bills only counts no spending. { kind: lump, account_id, on, amount_cents, to? } tries a lump sum out of an account on a day (to, where it goes, is only a label): it shows in the day's causes as Plan: lump sum to <to>, and its echo in what_if.events carries most { with_spending, bills_only }, the most that lump could be on its day and keep the balance at or above $0 (or the buffer safe to spend holds back), the rest of the what-ifs kept. most on the answer is the same question for lump_on (default the next pay) on the what-if line, and what_if.real.most on the line as it is. GET /v1/cashflow's cut.events are ready to send, and a cut (a spend-less) is folded into that envelope's projected cause each day, with usual_cents and plan_cents beside the amount_cents that already counts both, and shows as its own Plan: less on <envelope> cause only on a day the envelope projects nothing. The line reaches the latest what-if when it lands after until (at most twelve months ahead). Each what-if shows in the day's causes as Plan: <name> (a moved bill as <name> (moved)). lowest_*, next_below_on, safe_to_spend_cents and the rest are the line with them, worked out by the server; what_if.real { with_spending, bills_only, cards } is the same figures for the line as it is, so say both: 'lowest $X, was $Y'. A one-off income lifts the line but is not a payday: safe_until still runs to the next declared income. what_if.check { with_spending, bills_only } answers per view: { stays_above_alarm, stays_above_warning, stays_under_limits, line, accounts, cards }. stays_above_alarm is true when the line and every account in it stay above their Alarm from today to until; stays_above_warning likewise for Warning, null when no Warning is set. line and each of accounts { account_id, name, alarm_cents, warning_cents, lowest_cents, lowest_on, below_alarm_on, below_warning_on, was } give the first day at or below each, and was the same without the what-ifs, so 'this takes Chequing below Alarm on Nov 3' and 'Chequing was going below Alarm anyway' read apart. cards { account_id, name, peak_owed_cents, over_limit_on, was_over_limit_on } for a spend on a card. what_if.ghost { with_spending, bills_only }, each { ratios, real_ratios, zero_ratio }, puts the what-if line and the real line on one 0..1 scale, aligned with points, for drawing the real line faint behind. events echoes each what-if resolved: names, the account, and for a moved bill its from date and amount. dip (and summary_bills_only.dip) is the first stretch ahead at or below Warning or Alarm on the what-if line, as on the read. Nothing is entered, stored or moved: to make a move real, call the move endpoint; to see the line as it is, call GET /v1/cashflow.

Example

curl -X POST 'https://api.mintro.money/v1/cashflow/preview' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "events": [ { "kind": "spend", "account_id": "<account_id>", "on": "2026-09-15", "amount_cents": 45000 } ] }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/budgets/{budget_id}/cashflow-plans/chart

The plan every cashflow chart shows for this person

scope budget:read · getCashflowChartPlan

Parameters

  • budget_id path · string · required

  • line query · string

  • account_id query · string

  • back query · string

  • ahead query · string

Returns · 200

{ today, show, plan_id, fell_back, shows, plan, summary, status, attention, line, line_note, options[] }. show is what the person chose: current (the default: the current plan when there is one), none, or plan with plan_id. fell_back is true when the chosen plan has been deleted or has ended, so the default shows. shows is what the charts draw: current, plan (a saved plan that is not applied) or none. plan is that plan (null with none); summary says it in a sentence for above the chart; status and attention are the current plan's (null and 0 for any other plan). With line=true, line is the plan drawn on the chart account_id names (left out: All cash) over back and ahead, exactly as on GET .../cashflow-plans/current: on another account's chart only the changes in that account are drawn, and line_note says so; null on a card's chart or with no plan. options lists every saved plan still going in the budget { id, name, account_id, applied_at, current }, oldest first, for a picker; a finished plan is not offered, and a choice of one reads as the default with fell_back true. This answers 'which plan is my chart showing?' The choice is kept per person per budget, so every device and every cashflow chart (the Cashflow page, Accounts, Budget) shows the same plan. A READ.

Example

curl 'https://api.mintro.money/v1/budgets/default/cashflow-plans/chart' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

put /v1/budgets/{budget_id}/cashflow-plans/chart

Choose the plan every cashflow chart shows

scope budget:write · setCashflowChartPlan

Parameters

  • budget_id path · string · required

  • line query · string

  • account_id query · string

  • back query · string

  • ahead query · string

Body · JSON

  • show "current" | "none" | "plan" · required

  • plan_id string · ≤ 64 chars

Returns · 200

The read after the change, drawn on one chart with the same query as GET. { today, show, plan_id, fell_back, shows, plan, summary, status, attention, line, line_note, options[] }. show is what the person chose: current (the default: the current plan when there is one), none, or plan with plan_id. fell_back is true when the chosen plan has been deleted or has ended, so the default shows. shows is what the charts draw: current, plan (a saved plan that is not applied) or none. plan is that plan (null with none); summary says it in a sentence for above the chart; status and attention are the current plan's (null and 0 for any other plan). With line=true, line is the plan drawn on the chart account_id names (left out: All cash) over back and ahead, exactly as on GET .../cashflow-plans/current: on another account's chart only the changes in that account are drawn, and line_note says so; null on a card's chart or with no plan. options lists every saved plan still going in the budget { id, name, account_id, applied_at, current }, oldest first, for a picker; a finished plan is not offered, and a choice of one reads as the default with fell_back true. { show: current } goes back to the default, { show: none } draws no plan, { show: plan, plan_id } draws that saved plan (404 when it is not in this budget). Only what the charts show changes: nothing in the budget, and the plan is not applied.

Example

curl -X PUT 'https://api.mintro.money/v1/budgets/default/cashflow-plans/chart' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "show": "plan", "plan_id": "cfp_x" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/budgets/{budget_id}/cashflow-plans/current

The current cashflow plan: what you agreed to do and how it is going

scope budget:read · getCurrentCashflowPlan

Parameters

  • budget_id path · string · required

  • line query · string

  • check query · string

  • account_id query · string

  • back query · string

  • ahead query · string

Returns · 200

{ today, plan, summary, status, attention, todos, findings, conflicts, line, line_note }. This answers 'am I keeping to my plan?' A budget has one current plan at a time: the plan applied most recently that has not ended. plan is null when none is applied, or the applied plan has ended (every other field is then empty). summary names it and when it was applied; status says whether everything is on track or what needs attention; attention counts the todos that do. Each of todos is one change in the plan: { move_id, kind, title, name, status, sentence, also[], attention, envelope_id, recurring_id, account_id, on, amount_cents, progress, findings[] }. title says what was agreed ("Spend $400 less on Groceries by Nov 5") and sentence how it is going, in words to show as they are. status: for spend less starts, on_track, ahead, over, kept, missed or not_tracked; a moved bill done or skipped; a lump sum upcoming, due or done; a one-off noted or past. progress (spend less) { status, spent_cents, allowed_so_far_cents, allowed_total_cents, over_cents, days_total, days_gone, from, until, ratio, per_day_cents, compare, sentence, short }: ratio is spent over the whole allowance, for a bar (past 1 when over), per_day_cents the allowance per day from from to until, and compare { days, usual_cents, usual_per_day_cents, keep_cents, keep_per_day_cents, saves_cents, saves_per_day_cents } the change before and after, a day and in all. It compares the envelope's spending since the day after the plan was applied (scheduled bills left out, as the chart leaves them out) against what the plan allows by today, spread evenly over its days. also says what apply did beside it (the target it set, the money it took back). attention is true when the person should act or look: over, missed, a reminder due, or a conflict from the plan check. With check=true, the plan check runs too: findings are its findings about the plan as a whole (each change's own sit on its todo) and conflicts counts the changes that cannot happen as written. With line=true, line is the plan drawn on the chart account_id names (left out: All cash) over back and ahead: exactly what POST /v1/cashflow/preview returns for the plan's changes still to come, so the balance with the plan and without it are on one scale. On another account's chart only the changes in that account are drawn, and line_note says so; on a card's chart line is null. A READ.

Example

curl 'https://api.mintro.money/v1/budgets/default/cashflow-plans/current' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/budgets/{budget_id}/cashflow-plans

The cashflow plans in a budget

scope budget:read · listCashflowPlans

Parameters

  • budget_id path · string · required

  • state query · string

Returns · 200

{ plans[] }. state picks which: open (the default) the plans still going, oldest first; finished the plans that have ended, the latest first, each with its result (kept 90 days after it ended, then deleted); all both. A cashflow plan: { id, budget_id, name, account_id, moves, applied_at, applied, reminders, created_at, updated_at }. account_id is the line it is on (null: All cash). Each of moves is what you sent plus its id (made for you when left out: m1, m2, ...), label (the bill's, envelope's or account's name; null when it is gone) and past (its day is before today, so it is no longer tried on the line). Kinds: { kind: spend | income, account_id, on, amount_cents, name? } a one-off on a day; { kind: move_bill, recurring_id, date } a bill's next date moved (just that date, before its following date); { kind: spend_less, envelope_id, amount_cents, until } spending that much less from an envelope the line projects, spread per day from tomorrow through until (or keep_to_cents in place of amount_cents: what to keep the envelope to over those days; the server works the amount out from what it usually spends when the plan is saved, keeps both, and the current plan's to-do says 'Keep Groceries to $301.28 from Oct 31 to Nov 5'); { kind: lump, account_id, on, amount_cents, to? } a lump sum out of an account on a day, to naming where it goes (a goal, a holding, an envelope: a label only). amount_cents is always positive. applied_at is null until it is applied; applied { on, steps } then says what apply did, step by step (bill_moved { recurring_id, name, from, to }, target_set { envelope_id, name, target_cents, was_cents, was_kind, was_date }, money_moved { envelope_id, name, amount_cents, month, assignment_id, settled? } (taken back to Ready to assign by an older apply; Mintro no longer does this), tracked { envelope_id, name, amount_cents, from, until, planned_cents } (the spend-less promise as applied; a spend-less move can have several steps), reminder { account_id, on, amount_cents, to, reminded_at, done_at }, kept { what }, skipped { reason }). reminders are the lump sums of an applied plan whose day has come and that are not marked done. A spend-less move also carries keep_to_shown_cents: what to keep the envelope to, as typed or, on an applied plan, the allowance as applied (null when neither is known), for an edit to start from. Each move also carries state (coming_up, happening_now or finished) and state_words (the same as a person reads it: "Coming up"). A plan ends. ends_on is the first day it is finished: the day after its last change finishes (ends_from_changes), or the day the person chose (ends_on_chosen true). It is null while it has no end: no changes yet, or an applied plan's lump sum not marked done (that keeps it going). ended is true once ends_on has come: the plan is then finished, it leaves the plan list, the chart picker and every chart, it is no longer the current plan, and its reminders stop; it can be read with state=finished and reopened by PATCHing a later ends_on. end_sentence says all this in words to show. result (finished plans only, else null) says how it went: "You kept to 1 of 2 changes in this plan. Groceries went $100 over what the plan allowed." kept is true once the person named it or applied it; a plan with neither is deleted after 14 days without a change, keep_sentence says so on screen and deletes_on gives the day (for a finished plan: 90 days after it ended). moved_money is null, or { amount_cents, envelopes[] { envelope_id, name, amount_cents }, head, sentence, put_back_words } when an older apply took money out of envelopes into Ready to assign and the person has not answered: show head and sentence, then POST .../moved-money to put it back or leave it. A plan changes nothing until it is applied: to see its line, GET /v1/budgets/{budget_id}/cashflow-plans/{id}/line.

Example

curl 'https://api.mintro.money/v1/budgets/default/cashflow-plans' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

post /v1/budgets/{budget_id}/cashflow-plans

Save a cashflow plan

scope budget:write · createCashflowPlan

Parameters

  • budget_id path · string · required

Body · JSON

  • name string · ≤ 80 chars

  • account_id string | null · ≤ 64 chars

  • moves object | object | object | object[] · required · ≤ 20 items · default []

  • from_plan_id string · ≤ 64 chars

Returns · 201

The new plan. name may be left out: the plan is then named for the day it is made, "Plan from Oct 28" (with a number when that name is taken), or with from_plan_id "Changes to <that plan>", and can be renamed with PATCH. A name you send keeps the plan; a name Mintro gives does not, so a plan never named or applied is deleted after 14 days without a change. A cashflow plan: { id, budget_id, name, account_id, moves, applied_at, applied, reminders, created_at, updated_at }. account_id is the line it is on (null: All cash). Each of moves is what you sent plus its id (made for you when left out: m1, m2, ...), label (the bill's, envelope's or account's name; null when it is gone) and past (its day is before today, so it is no longer tried on the line). Kinds: { kind: spend | income, account_id, on, amount_cents, name? } a one-off on a day; { kind: move_bill, recurring_id, date } a bill's next date moved (just that date, before its following date); { kind: spend_less, envelope_id, amount_cents, until } spending that much less from an envelope the line projects, spread per day from tomorrow through until (or keep_to_cents in place of amount_cents: what to keep the envelope to over those days; the server works the amount out from what it usually spends when the plan is saved, keeps both, and the current plan's to-do says 'Keep Groceries to $301.28 from Oct 31 to Nov 5'); { kind: lump, account_id, on, amount_cents, to? } a lump sum out of an account on a day, to naming where it goes (a goal, a holding, an envelope: a label only). amount_cents is always positive. applied_at is null until it is applied; applied { on, steps } then says what apply did, step by step (bill_moved { recurring_id, name, from, to }, target_set { envelope_id, name, target_cents, was_cents, was_kind, was_date }, money_moved { envelope_id, name, amount_cents, month, assignment_id, settled? } (taken back to Ready to assign by an older apply; Mintro no longer does this), tracked { envelope_id, name, amount_cents, from, until, planned_cents } (the spend-less promise as applied; a spend-less move can have several steps), reminder { account_id, on, amount_cents, to, reminded_at, done_at }, kept { what }, skipped { reason }). reminders are the lump sums of an applied plan whose day has come and that are not marked done. A spend-less move also carries keep_to_shown_cents: what to keep the envelope to, as typed or, on an applied plan, the allowance as applied (null when neither is known), for an edit to start from. Each move also carries state (coming_up, happening_now or finished) and state_words (the same as a person reads it: "Coming up"). A plan ends. ends_on is the first day it is finished: the day after its last change finishes (ends_from_changes), or the day the person chose (ends_on_chosen true). It is null while it has no end: no changes yet, or an applied plan's lump sum not marked done (that keeps it going). ended is true once ends_on has come: the plan is then finished, it leaves the plan list, the chart picker and every chart, it is no longer the current plan, and its reminders stop; it can be read with state=finished and reopened by PATCHing a later ends_on. end_sentence says all this in words to show. result (finished plans only, else null) says how it went: "You kept to 1 of 2 changes in this plan. Groceries went $100 over what the plan allowed." kept is true once the person named it or applied it; a plan with neither is deleted after 14 days without a change, keep_sentence says so on screen and deletes_on gives the day (for a finished plan: 90 days after it ended). moved_money is null, or { amount_cents, envelopes[] { envelope_id, name, amount_cents }, head, sentence, put_back_words } when an older apply took money out of envelopes into Ready to assign and the person has not answered: show head and sentence, then POST .../moved-money to put it back or leave it. Every move is checked against the budget: an unknown bill, envelope or account is 404; a bill moved twice, or an envelope spending less twice, is refused; on one account's line every one-off and lump sum is on that account; a card's line is refused (a plan is on a cash line). Days are today or later. Up to 20 moves, and 20 plans still going in a budget (finished plans do not count). Saving changes nothing else: no bill moves and no target changes.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/cashflow-plans' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "name": "RRSP before Christmas", "moves": [ { "kind": "lump", "account_id": "acc_x", "on": "2026-11-20", "amount_cents": 200000, "to": "RRSP" } ] }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/budgets/{budget_id}/cashflow-plans/{id}

One cashflow plan

scope budget:read · getCashflowPlan

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

A cashflow plan: { id, budget_id, name, account_id, moves, applied_at, applied, reminders, created_at, updated_at }. account_id is the line it is on (null: All cash). Each of moves is what you sent plus its id (made for you when left out: m1, m2, ...), label (the bill's, envelope's or account's name; null when it is gone) and past (its day is before today, so it is no longer tried on the line). Kinds: { kind: spend | income, account_id, on, amount_cents, name? } a one-off on a day; { kind: move_bill, recurring_id, date } a bill's next date moved (just that date, before its following date); { kind: spend_less, envelope_id, amount_cents, until } spending that much less from an envelope the line projects, spread per day from tomorrow through until (or keep_to_cents in place of amount_cents: what to keep the envelope to over those days; the server works the amount out from what it usually spends when the plan is saved, keeps both, and the current plan's to-do says 'Keep Groceries to $301.28 from Oct 31 to Nov 5'); { kind: lump, account_id, on, amount_cents, to? } a lump sum out of an account on a day, to naming where it goes (a goal, a holding, an envelope: a label only). amount_cents is always positive. applied_at is null until it is applied; applied { on, steps } then says what apply did, step by step (bill_moved { recurring_id, name, from, to }, target_set { envelope_id, name, target_cents, was_cents, was_kind, was_date }, money_moved { envelope_id, name, amount_cents, month, assignment_id, settled? } (taken back to Ready to assign by an older apply; Mintro no longer does this), tracked { envelope_id, name, amount_cents, from, until, planned_cents } (the spend-less promise as applied; a spend-less move can have several steps), reminder { account_id, on, amount_cents, to, reminded_at, done_at }, kept { what }, skipped { reason }). reminders are the lump sums of an applied plan whose day has come and that are not marked done. A spend-less move also carries keep_to_shown_cents: what to keep the envelope to, as typed or, on an applied plan, the allowance as applied (null when neither is known), for an edit to start from. Each move also carries state (coming_up, happening_now or finished) and state_words (the same as a person reads it: "Coming up"). A plan ends. ends_on is the first day it is finished: the day after its last change finishes (ends_from_changes), or the day the person chose (ends_on_chosen true). It is null while it has no end: no changes yet, or an applied plan's lump sum not marked done (that keeps it going). ended is true once ends_on has come: the plan is then finished, it leaves the plan list, the chart picker and every chart, it is no longer the current plan, and its reminders stop; it can be read with state=finished and reopened by PATCHing a later ends_on. end_sentence says all this in words to show. result (finished plans only, else null) says how it went: "You kept to 1 of 2 changes in this plan. Groceries went $100 over what the plan allowed." kept is true once the person named it or applied it; a plan with neither is deleted after 14 days without a change, keep_sentence says so on screen and deletes_on gives the day (for a finished plan: 90 days after it ended). moved_money is null, or { amount_cents, envelopes[] { envelope_id, name, amount_cents }, head, sentence, put_back_words } when an older apply took money out of envelopes into Ready to assign and the person has not answered: show head and sentence, then POST .../moved-money to put it back or leave it. A finished plan carries result.

Example

curl 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

patch /v1/budgets/{budget_id}/cashflow-plans/{id}

Rename a cashflow plan, change its line or moves, or change when it ends

scope budget:write · updateCashflowPlan

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • name string · ≤ 80 chars

  • account_id string | null · ≤ 64 chars

  • moves object | object | object | object[] · ≤ 20 items

  • ends_on string | null · YYYY-MM-DD

Returns · 200

The plan after the change. moves replaces every move (send the ids you want kept). To change one move, send every move with its id and the changed one with its id and new values; a move sent without an id is new, and a move left out is removed. An applied plan is changed in place: each changed move takes effect in the same request (a bill moved to another day moves again from its own day, a spend-less is tracked at its new amount from tomorrow, a lump sum's reminder moves), a move left out is undone (its bill goes back, its target goes back), and a new move takes effect as apply would (no target is set). A move that cannot be changed (its bill was entered or moved since) refuses the whole request (409 not_editable) and nothing changes. A move sent back unchanged is kept as it is, even when its day has passed. An applied plan's line is refused (409 not_editable): start a new plan from it. ends_on chooses the day the plan ends, today or later: today ends it now, a later day reopens a finished plan, null goes back to the day after its last change finishes. A change that finishes after a chosen end takes the end with it. A finished plan's moves and line are refused (409 not_editable); reopening an applied plan while another plan is current is refused (409 current_plan_exists), because a budget has one current plan. Renaming keeps the plan. A cashflow plan: { id, budget_id, name, account_id, moves, applied_at, applied, reminders, created_at, updated_at }. account_id is the line it is on (null: All cash). Each of moves is what you sent plus its id (made for you when left out: m1, m2, ...), label (the bill's, envelope's or account's name; null when it is gone) and past (its day is before today, so it is no longer tried on the line). Kinds: { kind: spend | income, account_id, on, amount_cents, name? } a one-off on a day; { kind: move_bill, recurring_id, date } a bill's next date moved (just that date, before its following date); { kind: spend_less, envelope_id, amount_cents, until } spending that much less from an envelope the line projects, spread per day from tomorrow through until (or keep_to_cents in place of amount_cents: what to keep the envelope to over those days; the server works the amount out from what it usually spends when the plan is saved, keeps both, and the current plan's to-do says 'Keep Groceries to $301.28 from Oct 31 to Nov 5'); { kind: lump, account_id, on, amount_cents, to? } a lump sum out of an account on a day, to naming where it goes (a goal, a holding, an envelope: a label only). amount_cents is always positive. applied_at is null until it is applied; applied { on, steps } then says what apply did, step by step (bill_moved { recurring_id, name, from, to }, target_set { envelope_id, name, target_cents, was_cents, was_kind, was_date }, money_moved { envelope_id, name, amount_cents, month, assignment_id, settled? } (taken back to Ready to assign by an older apply; Mintro no longer does this), tracked { envelope_id, name, amount_cents, from, until, planned_cents } (the spend-less promise as applied; a spend-less move can have several steps), reminder { account_id, on, amount_cents, to, reminded_at, done_at }, kept { what }, skipped { reason }). reminders are the lump sums of an applied plan whose day has come and that are not marked done. A spend-less move also carries keep_to_shown_cents: what to keep the envelope to, as typed or, on an applied plan, the allowance as applied (null when neither is known), for an edit to start from. Each move also carries state (coming_up, happening_now or finished) and state_words (the same as a person reads it: "Coming up"). A plan ends. ends_on is the first day it is finished: the day after its last change finishes (ends_from_changes), or the day the person chose (ends_on_chosen true). It is null while it has no end: no changes yet, or an applied plan's lump sum not marked done (that keeps it going). ended is true once ends_on has come: the plan is then finished, it leaves the plan list, the chart picker and every chart, it is no longer the current plan, and its reminders stop; it can be read with state=finished and reopened by PATCHing a later ends_on. end_sentence says all this in words to show. result (finished plans only, else null) says how it went: "You kept to 1 of 2 changes in this plan. Groceries went $100 over what the plan allowed." kept is true once the person named it or applied it; a plan with neither is deleted after 14 days without a change, keep_sentence says so on screen and deletes_on gives the day (for a finished plan: 90 days after it ended). moved_money is null, or { amount_cents, envelopes[] { envelope_id, name, amount_cents }, head, sentence, put_back_words } when an older apply took money out of envelopes into Ready to assign and the person has not answered: show head and sentence, then POST .../moved-money to put it back or leave it.

Example

curl -X PATCH 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "ends_on": "2026-11-30" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

    not_editable: Only rows you entered can have their date or amount changed. A row from your bank's feed is a record of what happened at the bank, and the feed would re-sync it anyway. Unfile it if it is misfiled, or correct it at the bank.

    current_plan_exists: Another cashflow plan in this budget is the current plan, and a budget has one at a time. GET .../cashflow-plans/{id}/apply on the plan you want to apply: replaces names the current one and says what replacing it undoes. Tell the person, and send POST .../apply with replace: true only if they agree. The old plan is undone and stays saved.

delete /v1/budgets/{budget_id}/cashflow-plans/{id}

Delete a cashflow plan

scope budget:write · deleteCashflowPlan

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

{ deleted: true }. What an applied plan already did stays done (a moved bill stays moved); undo it first to put that back.

Example

curl -X DELETE 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

post /v1/budgets/{budget_id}/cashflow-plans/{id}/copy

Start a new cashflow plan from this one

scope budget:write · copyCashflowPlan

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • name string · ≤ 80 chars

Returns · 201

The new plan. This answers 'do the same again next month'. It holds this plan's changes with every day moved forward: each keeps its distance from the day this plan started (the day it was applied, else the day it was made, never after its first change), counted from today, and a day that would still be before today is today. Works on a current, saved or finished plan. The new plan is not applied and changes nothing; it is named name, else "<this plan> again" (or "Plan from <today>" when this plan was never named), and a name Mintro gives does not keep it. A cashflow plan: { id, budget_id, name, account_id, moves, applied_at, applied, reminders, created_at, updated_at }. account_id is the line it is on (null: All cash). Each of moves is what you sent plus its id (made for you when left out: m1, m2, ...), label (the bill's, envelope's or account's name; null when it is gone) and past (its day is before today, so it is no longer tried on the line). Kinds: { kind: spend | income, account_id, on, amount_cents, name? } a one-off on a day; { kind: move_bill, recurring_id, date } a bill's next date moved (just that date, before its following date); { kind: spend_less, envelope_id, amount_cents, until } spending that much less from an envelope the line projects, spread per day from tomorrow through until (or keep_to_cents in place of amount_cents: what to keep the envelope to over those days; the server works the amount out from what it usually spends when the plan is saved, keeps both, and the current plan's to-do says 'Keep Groceries to $301.28 from Oct 31 to Nov 5'); { kind: lump, account_id, on, amount_cents, to? } a lump sum out of an account on a day, to naming where it goes (a goal, a holding, an envelope: a label only). amount_cents is always positive. applied_at is null until it is applied; applied { on, steps } then says what apply did, step by step (bill_moved { recurring_id, name, from, to }, target_set { envelope_id, name, target_cents, was_cents, was_kind, was_date }, money_moved { envelope_id, name, amount_cents, month, assignment_id, settled? } (taken back to Ready to assign by an older apply; Mintro no longer does this), tracked { envelope_id, name, amount_cents, from, until, planned_cents } (the spend-less promise as applied; a spend-less move can have several steps), reminder { account_id, on, amount_cents, to, reminded_at, done_at }, kept { what }, skipped { reason }). reminders are the lump sums of an applied plan whose day has come and that are not marked done. A spend-less move also carries keep_to_shown_cents: what to keep the envelope to, as typed or, on an applied plan, the allowance as applied (null when neither is known), for an edit to start from. Each move also carries state (coming_up, happening_now or finished) and state_words (the same as a person reads it: "Coming up"). A plan ends. ends_on is the first day it is finished: the day after its last change finishes (ends_from_changes), or the day the person chose (ends_on_chosen true). It is null while it has no end: no changes yet, or an applied plan's lump sum not marked done (that keeps it going). ended is true once ends_on has come: the plan is then finished, it leaves the plan list, the chart picker and every chart, it is no longer the current plan, and its reminders stop; it can be read with state=finished and reopened by PATCHing a later ends_on. end_sentence says all this in words to show. result (finished plans only, else null) says how it went: "You kept to 1 of 2 changes in this plan. Groceries went $100 over what the plan allowed." kept is true once the person named it or applied it; a plan with neither is deleted after 14 days without a change, keep_sentence says so on screen and deletes_on gives the day (for a finished plan: 90 days after it ended). moved_money is null, or { amount_cents, envelopes[] { envelope_id, name, amount_cents }, head, sentence, put_back_words } when an older apply took money out of envelopes into Ready to assign and the person has not answered: show head and sentence, then POST .../moved-money to put it back or leave it.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>/copy' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "name": "Tighter December" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/budgets/{budget_id}/cashflow-plans/{id}/line

A cashflow plan on its line: the what-if preview of its moves

scope budget:read · getCashflowPlanLine

Parameters

  • budget_id path · string · required

  • id path · string · required

  • back query · string

  • ahead query · string

  • lump_on query · string

Returns · 200

Exactly what POST /v1/cashflow/preview returns for the plan's moves on the plan's line, plus plan. Moves whose day has passed are left out, and so is a bill move an applied plan already made real. what_if.events echoes each; a lump sum's carries most { with_spending, bills_only }: the most that lump could be on its day and keep the balance at or above $0 (or the buffer safe to spend holds back), the rest of the plan kept. The top-level most is the same question on the plan's line for lump_on (default: the next pay): how much MORE could go out that day. cut is what keeps the plan's balance at or above $0 (or the held buffer): spend this much less before the next pay, or move this bill. back and ahead pick the window as on GET /v1/cashflow. A READ: nothing is stored or moved.

Example

curl 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>/line' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

get /v1/budgets/{budget_id}/cashflow-plans/{id}/apply

What applying a cashflow plan would do

scope budget:read · previewApplyCashflowPlan

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

{ plan_id, applied_at, steps, replaces }, one step per move, for the person to confirm before anything changes: move_bill { recurring_id, name, from, to } moves that bill's next date (just that date); set_target { envelope_id, name, target_cents, per_month_cents, was_cents, was_kind, reason } is offered for a spend-less move, and only happens when you send its move id in set_targets: target_cents is the monthly target it would set (the envelope's monthly target, or with none its usual month of spending, less per_month_cents, the move's daily pace over a 30-day month), null when none is offered and reason says why (a target of another kind is left alone); it also carries planned_cents, what the plan leaves the envelope to spend from from through until (null when the chart does not project it); remind { account_id, account_name, on, amount_cents, to } is a lump sum, which becomes a reminder on its day and never moves money; keep { what } is a one-off, kept in the plan; skip { reason } will not happen (its day passed, the bill is gone or paused). replaces is null, or { plan_id, name, applied_on, sentence } when another plan is the current one: a budget has one current plan at a time, and applying this one undoes what that one changed (it stays saved); sentence says exactly what goes back. Show the steps and that sentence, ask which targets to set (none by default), then POST. A READ.

Example

curl 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>/apply' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

post /v1/budgets/{budget_id}/cashflow-plans/{id}/apply

Apply a cashflow plan: move its bills, set the targets asked for, keep its reminders

scope budget:write · applyCashflowPlan

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • set_targets string[] · required · ≤ 20 items · default []

  • replace boolean · required · default false

Returns · 200

{ plan, already, replaced }. Moves each bill's next date as previewed, sets each envelope target named in set_targets (move ids of spend-less moves; leave it out to edit no targets), and turns each lump sum into a reminder on its day. It never moves money between accounts or envelopes, and never enters or files anything. The plan becomes the budget's current plan. When another plan is current, 409 current_plan_exists unless replace: true: then that plan is undone first, in the same request, and replaced { plan_id, name, undone, left } says what went back. plan.applied lists what it did; a step that could not happen is skipped with its reason. Applying an applied plan changes nothing and returns already: true. On a lump sum's day its reminder shows in reminders on the plan and on GET /v1/cashflow, a cashflow.reminder event goes to agents, and an email goes to the person who turned on email for a cashflow alert in this budget, if anyone has. Undo with POST .../undo.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>/apply' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "set_targets": [ "m2" ], "replace": false }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

    current_plan_exists: Another cashflow plan in this budget is the current plan, and a budget has one at a time. GET .../cashflow-plans/{id}/apply on the plan you want to apply: replaces names the current one and says what replacing it undoes. Tell the person, and send POST .../apply with replace: true only if they agree. The old plan is undone and stays saved.

post /v1/budgets/{budget_id}/cashflow-plans/{id}/undo

Undo an applied cashflow plan

scope budget:write · undoCashflowPlan

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

{ plan, undone, left }. Puts each moved bill back on the date it had, while it is still on the moved date (not entered or moved since), each target back, while it is still the one apply set, and any money an older apply took out of an envelope back into it (unless the person already put it back or chose to leave it). undone { move_id, kind, name } lists what went back and left { move_id, name, reason } what stayed and why. The plan is a plan again: its reminders stop. 409 not_editable when it is not applied.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>/undo' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

    not_editable: Only rows you entered can have their date or amount changed. A row from your bank's feed is a record of what happened at the bank, and the feed would re-sync it anyway. Unfile it if it is misfiled, or correct it at the bank.

post /v1/budgets/{budget_id}/cashflow-plans/{id}/moved-money

Put back, or leave, money an older apply moved to Ready to assign

scope budget:write · settleCashflowPlanMovedMoney

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • action "put_back" | "leave" · required

Returns · 200

The plan, with moved_money null. action: put_back assigns each amount in the plan's moved_money back to its envelope this month (an assignment); action: leave keeps it in Ready to assign. Either way the plan stops asking, and undoing or stopping the plan leaves that money as the person said. 400 validation_failed when nothing from the plan is waiting. Ask the person which they want first.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>/moved-money' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "action": "put_back" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

    not_editable: Only rows you entered can have their date or amount changed. A row from your bank's feed is a record of what happened at the bank, and the feed would re-sync it anyway. Unfile it if it is misfiled, or correct it at the bank.

post /v1/budgets/{budget_id}/cashflow-plans/{id}/stop

Stop an applied cashflow plan today

scope budget:write · stopCashflowPlan

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

{ plan, left }. Each bill the plan moved that is still waiting goes back to its old day, each target it set goes back, and the plan ends today, so it moves to Finished plans with how it went. A bill already paid on its new day stays; left { name, reason } says what stayed and why. Money in Ready to assign from an older apply is not touched (see .../moved-money). Ask the person first. 409 not_editable when it is not applied.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>/stop' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

    not_editable: Only rows you entered can have their date or amount changed. A row from your bank's feed is a record of what happened at the bank, and the feed would re-sync it anyway. Unfile it if it is misfiled, or correct it at the bank.

post /v1/budgets/{budget_id}/cashflow-plans/{id}/reminders/done

Mark a lump-sum reminder done

scope budget:write · markCashflowReminderDone

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • move_id string · required · ≤ 24 chars

Returns · 200

The plan. The reminder for move_id no longer shows. Marking it done records nothing about money: Mintro never moves it.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>/reminders/done' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "move_id": "<move_id>" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/cashflow/most

The most you could put in on a day and keep your balance above $0

scope budget:read · getCashflowMost

Parameters

  • budget_id query · string

  • on query · string

  • account_id query · string

  • plan_id query · string

  • back query · string

  • ahead query · string

Returns · 200

{ on, through, floor, floor_cents, with_spending, bills_only, account_id, plan_id, without_plan }. This answers 'how much can I put into my RRSP on Nov 20?' The most that could go out of the line on on (default: the next pay, else today) and keep the balance at or above $0 every day from then to through (the end of the line), or at or above the buffer safe to spend holds back when it holds one (floor zero or buffer, at floor_cents, measured from $0; Alarm and Warning never set it). Each view { most_cents, room_cents, lowest_on, lowest_cents }: most_cents is in whole dollars and never below 0; room_cents is how far the lowest day from on sits above the floor (negative: under it already); lowest_on is the day that holds it down. Answer from with_spending when the person projects spending, else bills_only. With plan_id it is asked on that plan's line (its moves tried), so it is what MORE could go out with the plan, and without_plan is the same on the line as it is. account_id asks one cash account's line; a card is refused. A READ.

Example

curl 'https://api.mintro.money/v1/cashflow/most' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

get /v1/cashflow/accuracy

Forecast against what happened: did the cashflow line come true?

scope budget:read · getCashflowAccuracy

Parameters

  • budget_id query · string

  • account_id query · string

  • back query · string

  • from query · string

Returns · 200

{ account_id, today, line, started_on, from, until, forecasts, with_spending, bills_only }. This answers 'has the forecast been right?' and 'why was I off?' The first cashflow read of a day, or rows landing from a bank sync, keeps the line's next thirty days for All cash and each cash account, in both views (bills only, and with the projected envelope spending), for thirteen months. This compares the past with them. back (a span: 7d, 30d, 3m, 12m; default thirty days) or from picks how far back; account_id picks one cash account's line (omit for All cash; a card is refused). forecasts { taken_on, from, until }[] are the forecasts compared, each over the thirty days after it was taken, then the next one, so every stretch starts from the real balance on the day it was taken. started_on is the first day one was kept for this line, or null when none has been yet (nothing can be compared until a day has passed). Each view is { days, within_cents, days_within, missed_days, by_cause, biggest, compared }: each day is judged by its own move against what was forecast for it, never the running gap (one big miss is one day off, not a month of them), and each forecast's first day (a part day) is drawn but not judged. days_within days came within within_cents (a round amount, at least $25) and missed_days were off by more; they always add up to days. The off days are counted in by_cause by their largest cause: unscheduled (money out with nothing scheduled), unscheduled_income, transfer (moved to or from another of your accounts, nothing scheduled), bill_bigger, bill_smaller, bill_late (a bill that was due and had not landed, or landed after its day), bill_early, income_late, income_early, income_different, spending_under (everyday spending under the projection), other. biggest { on, miss_cents, kind, causes } is the day that moved it most: miss_cents is what happened that day less what was projected for it, and causes { kind, name, amount_cents, scheduled_cents, scheduled_on, recurring_id } (largest first, at most three) name it from the register: 'Costco $412, not scheduled', 'Hydro $180, scheduled $120', 'Payroll had not landed'. compared { on, actual_cents, forecast_cents, miss_cents }[] is every day compared. Say it as counts, not lists: 'On 26 of 30 days the line moved as forecast, within $25. Off on 4 days, 3 of them spending that was not scheduled; the biggest was Oct 12, Costco $412'. Never work an amount out yourself. A READ: nothing is entered.

Example

curl 'https://api.mintro.money/v1/cashflow/accuracy' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

get /v1/cashflow/envelopes-vs-cash

Is the money in the envelopes in the bank accounts before the next pay

scope budget:read · getEnvelopesVsCash

Parameters

  • budget_id query · string

  • ahead query · string

  • plan_id query · string

Returns · 200

{ today, plan, month, envelopes, ready_to_assign_cents, cash_today_cents, today_check, payday, through, has_projection, with_spending, bills_only, cards, cards_uncovered_cents }. This answers 'my envelopes say I have money, but do I actually have it?' It takes what the open envelopes hold this month and walks the cashflow line (the same one the charts draw, All cash, with the plan they show) from today to the day before the next pay (through; with no pay ahead, the end of the line). A bill or projected spending paid from an envelope that holds the money comes out of the envelope and the bank together, so it is never counted as missing; money going out that no envelope holds comes out of the bank only, and that is what makes envelope money not be there. envelopes { count, held_cents, spending, card_payment, saving, overspent } groups the envelopes with money in them: card_payment holds money to pay a credit card; saving is an envelope a goal saves into, or one whose target is a balance to hold or an amount by a date; spending is the rest. overspent counts envelopes spent past what they held: that money is already gone, so they count as 0. cash_today_cents is what the cash accounts hold today. today_check { envelopes_cents, cash_cents, short_cents, spare_cents, to_file, overspent_cents, sentence } is today alone: what the envelopes hold against what the bank accounts hold, and why they differ where it can be told: to_file { count, out_cents, on_cards } is spending in To file on cash accounts, which left the bank but no envelope yet, and on_cards counts the purchases in To file on credit cards, which have not left the bank and are not in the check (a count of everything to file includes them); overspent_cents was spent past what envelopes held. payday { on, amount_cents, names } is the next pay, or null. Each view { status, cash_lowest_cents, cash_lowest_on, below_zero_on, worst_on, cash_on_worst_cents, envelopes_on_worst_cents, spent_from_envelopes_cents, not_from_envelopes_cents, short_cents, spare_cents, short_from, pay_covers, before_pay, holding, holding_at_pay, line, parts, steps, sentences }. status is covered (every envelope dollar is in the bank on every day until the pay), short (some is not, but the balance stays at or above zero) or below_zero (the balance goes below zero before the pay; below_zero_on is the first such day). before_pay is why: money going out from today until the pay that no envelope holds, { total_cents, next_month_bills, this_month_bills, spending, plan_cents, other_cents }. next_month_bills are next month's bills due before the pay, whose envelopes do not hold next month's money yet; this_month_bills are this month's bills still to come whose envelope holds less than the bill (or that no envelope pays); each is { count, without_envelope, cents, month, from, to, top }, top the three needing most { bill_id, name, on, cents, envelope_id, envelope_name }. spending { count, cents, top } is projected everyday spending past what its envelope has left, top { envelope_id, name, cents, expected_cents, left_cents, less_cents, basis }: expected_cents is all the spending the chart expects from it from tomorrow through through, after any spend-less in the plan (less_cents), left_cents what it holds today, and basis how its spending is projected (last_month, average_3, target). plan_cents is the plan's one-off purchases and lump sums, which no envelope pays. worst_on is the day the envelopes hold the most more than the bank accounts (when nothing is short, the day they are closest): short_cents that day is today's gap plus before_pay up to then, less any money coming in; spare_cents is how much more the bank holds on the closest day when nothing is short. pay_covers is true only when the balance stays at or above zero until the next pay and that pay is at least short_cents; it is false when the balance goes below zero first, because the pay arrives after the money is needed. holding and holding_at_pay { count, held_cents, top } are the envelopes still holding money on worst_on and on through: money is not labelled in a bank account, so this says which envelopes hold money, never whose money is missing. line is one sentence for under Ready to assign (it starts with the plan's name when a plan is followed, and says so plainly when the plan covers what would otherwise be short); parts { plan, today, before_pay, result } are the parts in words: plan says which plan is followed and what the check says without it (empty with no plan), before_pay a list of { kind, cents, sentence } (kind none, total, next_month_bills, this_month_bills, spending, plan or other); without_plan { status, short_cents, before_pay_cents } is the same check on the line without the plan (null with no plan); plan_covers is true when the plan makes a short check covered; steps { kind, sentence, amount_cents, envelope_id, bill_id } are what the person can do, most useful first: file_to_file, move_bill, spend_less, top_up_bill_envelopes, set_aside_for_bills; sentences is every part in order; fix is the money moves that would back every envelope (the same shape GET /v1/cashflow/envelopes-vs-cash/fix returns, without its header fields). Show them as they are. with_spending follows the line with projected envelope spending, bills_only the line with bills and income only; they are equal when has_projection is false. cards { account_id, name, owed_cents, envelope_id, envelope_name, envelope_cents, uncovered_cents, extra_cents, sentence } is each credit card's balance against its payment envelope: uncovered_cents is owed with no envelope money set aside for it. Cards are beside the check, not in it: paying a card only takes cash when it is paid, and a card payment that is a scheduled bill is already in the walk. It follows a cashflow plan the way the chart does. Left out, plan_id is the plan the person's charts show (their Show choice on GET /v1/budgets/{budget_id}/cashflow-plans/chart; the current plan by default; a chosen plan that was deleted falls back to it); plan_id=none checks the budget as it is, and a plan id checks that plan (404 when it is not in this budget). The plan's line is the chart's own: a spend-less comes off that envelope's expected spending, a moved bill is on its new day, and a one-off purchase or lump sum goes out with no envelope. plan { id, name, current, followed } is the plan followed, null for none; followed is false when none of its changes are still ahead or the chart cannot draw it, and the check is then the line as it is. ahead is how far ahead to look for the next pay (a span like 5w or 2m; default five weeks). A READ: nothing changes.

Example

curl 'https://api.mintro.money/v1/cashflow/envelopes-vs-cash' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

get /v1/cashflow/envelopes-vs-cash/fix

Which money moves between envelopes would back every envelope before the next pay

scope budget:read · getEnvelopeFix

Parameters

  • budget_id query · string

  • ahead query · string

  • plan_id query · string

  • view query · string

Returns · 200

{ today, plan, view, status, through, payday, ready_to_assign_cents, month, covers_all, needed_cents, total_cents, moves, needs, uncovered, sentence, note }. This answers 'which envelopes should I move money between so every envelope holds what its bills and spending need before the next pay?' It is the same walk as GET /v1/cashflow/envelopes-vs-cash, on the same plan. needs { envelope_id, name, cents, covered_cents, first, why } is each envelope that is short before the pay, earliest first: this month's bills its envelope holds less than, next month's bills due before the pay (their month's money is not in the envelope yet), and projected spending past what the envelope has left. moves { from_envelope_id, from_name, from_kind, envelope_id, name, amount_cents, title, why } is the money to move, never more than each need: from_envelope_id null is Ready to assign. Sources are taken in this order (from_kind): ready_to_assign; unused (an envelope nothing is paid from before the pay); spare (only what an envelope has left after its own bills and spending before the pay); plan_less (what an APPLIED plan frees by spending less in an envelope; a plan that is only shown is not leaned on, and its spend-less is not counted when working out the need); saving (goal and balance or by-date target envelopes, last). Card payment envelopes are never a source. uncovered { cents, no_money_cents, no_envelope_cents, sentence }: no_money_cents is need no source could cover, no_envelope_cents money going out with no envelope at all (a bill with none chosen, a plan's one-off purchases), which moving money cannot cover. sentence, title, why, uncovered.sentence and note are for showing as they are. Apply the moves (all of them, or the ones the person ticks) with POST /v1/budgets/{budget_id}/assignments/batch, month set to month. Only after the person confirms: never apply a proposal on your own. A READ: nothing moves. view is with_spending or bills_only (default with_spending when spending is projected), plan_id and ahead as on GET /v1/cashflow/envelopes-vs-cash.

Example

curl 'https://api.mintro.money/v1/cashflow/envelopes-vs-cash/fix' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

get /v1/budgets/{budget_id}/cashflow-plans/{id}/check

What in a cashflow plan does not add up

scope budget:read · checkCashflowPlan

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

{ plan_id, today, count, conflicts, findings }. Each finding { kind, move_id, severity, sentence, name, amount_cents, left_cents, projected_cents, most_cents, on } is about one change in the plan (move_id), or the whole plan (null). severity is conflict when the change cannot happen as written, or worth_a_look when it can but may not mean what it seems. Kinds: spend_less_more_than_left (worth a look): the plan spends less from an envelope by more than the envelope has left this month (left_cents), so keeping to the envelope already makes most of that saving, and it is not extra money; spend_less_more_than_projected (conflict): more than the chart expects the envelope to spend in those days (projected_cents); spend_less_not_projected (conflict): the chart does not forecast that envelope's spending; envelope_gone (conflict): the envelope was deleted or closed; lump_more_than_most (conflict): a lump sum bigger than the most that could go out that day and keep the balance at or above $0, or the buffer safe to spend holds back, every day after (most_cents), with the rest of the plan kept; bill_past_next_date (conflict): a bill moved to or past its following date (on); bill_moved_earlier (worth a look): the bill's next date is now later than the day the plan moves it to (on), so it may already be paid; bill_not_on_line (conflict): the bill is paused or removed; still_below_alarm (worth a look): with the plan, the balance still drops to or below Alarm on on. Changes whose day has passed, and bill moves an applied plan already made, are not checked. sentence says it in full: show it beside the change. A READ.

Example

curl 'https://api.mintro.money/v1/budgets/default/cashflow-plans/<id>/check' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

get /v1/age-of-money

How long the money you are spending sat here first

scope budget:read · ageOfMoney

Parameters

  • budget_id query · string

Returns · 200

{ days, hours, estimated, sample_size, reason, as_of }. YNAB's signature metric, computed on read and stored nowhere: inflows are queued oldest-first and each outflow is paid from the oldest money still unspent, so every dollar spent carries the age it had when it left; days and hours average that over the last 10 outflows (rows are taken at midnight). estimated is true, and reason is estimated, when some spending in the 400-day window predates the income on record (an opening balance): the age comes from the part that could be paid and may read young. days is null only when reason is no_outflows (nothing spent yet) or not_enough_inflow_history (no spending had any income behind it). Transfers recorded through POST /v1/transfers are excluded. Two ordinary transactions that happen to be a transfer still look like spending.

Example

curl 'https://api.mintro.money/v1/age-of-money' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/cashflow

Projected cash: posted history plus declared recurrences

scope budget:read · getCashflow

Parameters

  • budget_id query · string

  • account_id query · string

  • from query · string

  • until query · string

  • back query · string

  • ahead query · string

  • lump_on query · string

Returns · 200

{ account_id, from, until, today, working_balance_cents, lowest_cents, lowest_on, days_below, next_below_on, cause, zero_ratio, mode, line_cents, credit_limit_cents, points, has_projection, points_bills_only, summary_bills_only, zero_ratio_bills_only, overlay, bands, cards, projected_envelopes, paydays, pay_periods, pay_periods_bills_only, safe_to_spend_cents, safe_until, safe_until_income, dip, safe_to_spend_held_cents, buffer, spending_band, cut, answer, fixes, forecast_check, most, reminders, accounts }. Two views of one line, in one read. The top-level fields are the line *with spending*: declared bills and income plus the projected envelope spending below. points_bills_only is the same walk *bills only* (declared bills and income, nothing projected), in the same point shape, with summary_bills_only { lowest_cents, lowest_on, days_below, next_below_on, cause, safe_to_spend_cents, safe_until, safe_until_income, dip, cut, answer, fixes } and zero_ratio_bills_only for it. Past days are the same in both; only days after today differ, by exactly the projected amounts. has_projection is true when projected spending moves this line on some day in the window; when false the two views are equal. Each view's ratio is across its own series; overlay { ratios, bills_only_ratios, zero_ratio } puts both on one shared 0..1 scale, aligned with points, for drawing both on one chart (null when has_projection is false). To answer 'with my usual spending, when do I go below?' read next_below_on; for 'bills only?' read summary_bills_only.next_below_on. cards follow the line with spending. What the line counts is the person's choice. By default it is declared bills and income only. An envelope joins it when its forecast_basis is set (PATCH /v1/budgets/{budget_id}/envelopes/{id}): last_month (that envelope's spending last calendar month), average_3 (its average over the last three full months, rounded up) or target (its monthly target; a weekly target counts as 52 weeks over 12 months; any other kind, or none, projects nothing). Each option's amount is shown before you choose: GET /v1/budgets/{id} gives every spending envelope's forecast_options { last_month_cents, average_3_cents, target_cents }, by these same rules. projected_envelopes lists each one in the line: { envelope_id, name, basis, monthly_cents, account_id, scheduled_bills, bill_rows_excluded, all_scheduled }. Scheduled bills are never counted twice. The line walks every active bill with an account on its own day, so a projected envelope adds only the spending that is not already a scheduled bill: a history basis leaves out every row that is an occurrence of an active bill (entered from it or matched to it) and every row whose payee is the name of an active bill filed to that envelope, the whole row whatever its amount, in every month it looks back over; a target is the target less that month's bills filed to the envelope (this month: the ones already paid and the ones still due), never below 0. monthly_cents is what the projection adds in a month after those bills (for target, next month's). scheduled_bills counts the active bills filed to the envelope, bill_rows_excluded the history rows left out as bills over the last three months and this one, and all_scheduled is true when bills cover the envelope and the projection adds nothing. A paused or removed bill is no longer in the line, so its past rows count as ordinary spending again. The month's amount is spread evenly per day from tomorrow: this month only what is not yet spent (the month's amount less this month's spending that is not a bill, never below 0), each later month in full. It lands on the account that carried most of that envelope's spending over the last three months and this one, so card spending moves that card's row in cards, not the cash line; with no spending to follow, on the cash account that carried the most of the chosen envelopes' spending, else the first cash account by name. Each projected day is one cause per envelope named <envelope> (projected). Every figure on this read is measured from $0: Alarm and Warning only set the tone and the bands. safe_to_spend_cents is the lowest running total from today until the next declared income lands: what can go out before then without the balance going below $0. Negative only when the balance goes below $0, and then it is how far below $0 it goes (the shortfall). safe_until is that income's day (safe_until_income true), or until when no income lands in the window (false). Both are null in card mode. Paydays and the low before each. paydays { on, amount_cents, names }[] is every day after today, through until, that a declared income (an upcoming row with a positive amount) lands on this line: amount_cents is all of that day's income summed and names are what lands, largest first. These are the same days safe_until stops at. pay_periods { from, until, payday_on, payday_cents, lowest_cents, lowest_on, tone }[] cuts the line from today to until at each payday: each stretch runs from today, or the payday that opened it, to the day before the next payday (payday_on, with its payday_cents), and the last one to until with payday_on null. A payday's income counts in the stretch it opens, never the one before. lowest_cents / lowest_on are the lowest running total in the stretch and its first day; tone is that day's tone. With no income ahead there is one stretch from today to until. The first stretch's lowest_cents is safe_to_spend_cents (less any buffer held back, below). pay_periods follows the line with spending; pay_periods_bills_only is the same cut of the bills-only line. All three are empty for one card (a payment is not pay) and when the window ends before today. To answer 'how low do I get before each payday?' read pay_periods; never work a low out from points yourself. The buffer goal. buffer is the open buffer goal on this line (an aim of kind buffer: All cash when account_id is omitted, else that account's), or null when none is set or for one card: { aim_id, label, unit, count, account_id, holds_safe_to_spend, amount_cents, yearly_bills_cents, monthly_bills_cents, bills, alarm_cents, line_cents, with_spending, bills_only, covers_days, through, band }. amount_cents is the buffer: count weeks (a fifty-second of a year of bills each) or months (a twelfth) of the repeating scheduled bills that leave this line, or the fixed amount when unit is amount; null with no repeating bills to size it from. yearly_bills_cents is a year of those bills (one-off bills and income are left out), monthly_bills_cents a twelfth of it, bills how many it counts. line_cents is the buffer measured from $0 (the same as amount_cents); alarm_cents is the line's Alarm, for reference only. with_spending and bills_only are { lowest_ahead_cents, lowest_ahead_on, cushion_cents, short_cents, reached } for each view: the lowest point from today to until, how far it stays above $0 (negative: below $0), what the cushion still needs to be the whole buffer, and whether it is. covers_days is how many days of those bills today's cash (above $0) pays for. band { with_spending, bills_only, overlay } is { low, high } on each view's 0..1 scale (and the shared overlay scale), clamped, for drawing the band between $0 and the buffer line; null when the band sits wholly off the chart (the line stays clear of the buffer, or below $0 the whole way). When holds_safe_to_spend is true, safe_to_spend_cents (and summary_bills_only.safe_to_spend_cents) hold the buffer back: what can go out and still leave the buffer, never below 0 while the balance itself stays at or above $0 (when the balance goes below $0 the figure is the shortfall, with nothing held on top), and safe_to_spend_held_cents is the amount held back (null when nothing is held). bands is { low_cents, ok_cents, source }: low_cents is the Alarm line (red at or below it) and ok_cents the Warning line (yellow, kraft, at or below it; null: no Warning). source is budget when All cash has its own lines (set with PATCH /v1/budgets/{budget_id} cash_low_cents / cash_ok_cents), accounts when it is the cash accounts' lines summed (the default), account for one account. mode is card when one credit account is asked for: the line is what is owed (purchases climb, payments fall), working_balance_cents and every running_cents are what is owed, line_cents is the limit (0 when none is set) and zero_ratio is where it sits, lowest_* is the highest owed, and tone is red past the limit and kraft within a tenth of it. The aggregate is always cash and sums cash accounts only: a card owed is not money that runs out, and summing it would turn $0 red while no account is overdrawn. Omit account_id for that line. The aggregate also returns cards, one per credit account, each against its own limit: { id, name, owed_cents, credit_limit_cents, used_ratio, tone, peak_owed_cents, peak_on, near_limit_on, over_limit_on, over_limit_cause }. owed_cents is today, used_ratio is owed / limit uncapped (null without a limit), peak_* is the most owed from today to until, near_limit_on is the first day at or within a tenth of the limit and over_limit_on the first day past it (null when never). cards is empty when account_id is given. Each of accounts carries kind: cash (in the line) or card. Past days walk posted transactions; future days walk upcoming bills and income. The window is the person's choice. By default it is thirty days before today and thirty after. back and ahead choose it as a span: a number of days, or with w or m weeks or calendar months (7d, 2w, 3m, 12m). back reaches from 0d (no history: the line starts today) to twelve months, ahead from seven days to twelve months; a span past either end is clamped, not refused. from and until are dates instead, each within twelve months of today (clamped), and each wins over the span on its side. Without a from, the line starts no earlier than the accounts' first row, so from in the answer may be later than asked. lowest_*, days_below and next_below_on look from today on, never at a past day: days_below counts the days ahead at or below Alarm (for one card, past the limit), so it is 0 exactly when next_below_on is null. Lowest, days below, next below, safe to spend and pay_periods follow the window: with no declared income ahead, safe_until is the window's end. Envelope projection runs every month the window touches; recurring bills walk every occurrence through until. Each point has ratio (0..1), tone (overspent|underfunded|funded), and causes ({ name, amount_cents, kind, envelope?, usual_cents?, plan_cents? }[], every posted or upcoming hit that day, largest in first). kind is bill (a scheduled bill or income), spending (an envelope's projected spending; envelope is its plain name), plan (a what-if) or other (posted history, transfers). A plan's spend-less on an envelope is folded into that envelope's projected cause the same day: usual_cents is what it projects without the plan, plan_cents the plan's part (positive), and amount_cents already both; never add them yourself. cause is the last hit's name, used when naming the next day below warn. zero_ratio is where remaining is $0 on that same 0..1 scale, or null when the series does not cross it. Tone is the plotted remaining against that line's Alarm and Warning amounts (one account: its low_cents / ok_cents; all accounts: the sums). dip is the first stretch from today at or below Warning or Alarm, or null when the line stays above both: { from, until, line: alarm | warning, lowest_cents, lowest_on, bills }. bills (largest first, at most three) are the scheduled bills that land in it on their next date, each { recurring_id, name, account_id, amount_cents, on, move_to, after_income }: move_to is a later day the bill could go, the next declared income after it (after_income names it) or else the day after the dip, always before the bill's following date. It is an offer only: try it with POST /v1/cashflow/preview (kind: move_bill), show the person, and only when they say yes move it with POST /v1/budgets/{budget_id}/recurring/{id}/move, scope: occurrence. Moving a bill moves no money. To test a purchase against the line, use POST /v1/cashflow/preview. How much of the way down is spending. spending_band { through, spending_cents, bills_cents, income_cents, spending_share } is null unless projected spending moves this line: from today to through (the lowest day ahead on the line with spending, or until when the line only rises), spending_cents is the projected everyday spending, bills_cents the declared bills going out and income_cents the declared income landing, and spending_share is spending over spending plus bills (0..1). On a chart it is the band between the bills-only line and the line with spending; say it as 'By Nov 12, projected spending takes $900 and bills $1,500'. envelopes (largest first, at most three) { envelope_id, name, basis, account_id, projected_cents } are the envelopes carrying most of that spending, each with its basis and its spending in those days, and envelope_count how many spend in them. Projected spending is each counted envelope's usual month (its basis) spread evenly per day: say it as prorated daily. Where to cut. cut (and summary_bills_only.cut) is null while the balance stays at or above $0 (and at or above the buffer safe to spend holds back, when it holds one). Otherwise it names the first of those it goes below after today (one it is already below today is skipped: nothing from tomorrow keeps it above) (line: zero for $0, or buffer for the held buffer measured from $0, at line_cents; Alarm and Warning are never a line to keep) and how much less everyday spending keeps it at or above: { line, line_cents, below_on, lowest_cents, lowest_on, short_cents, before, through, days, projected_cents, enough, cut_cents, per_day_cents, envelopes, envelope_count, bill, events }. before is the next declared income after below_on (null when none lands in the window) and through the last day the cut is spread over (the day before before, or until); days counts tomorrow through through. projected_cents is all the projected spending on this line in those days. The cut is spread evenly per day from tomorrow, as the projection is, so it covers every day's gap under the line by that day; cut_cents is that, rounded up to a figure people say ($5s up to $100, $10s up to $1,000, $50s up to $10,000, then $100s), never more than projected_cents, and per_day_cents is it per day, rounded up to a whole dollar. envelopes { envelope_id, name, basis, account_id, projected_cents, cut_cents, per_day_cents }[] (largest part first) share it by each envelope's part of the projected spending in those days, in whole dollars, each at most what it projects; envelope_count is every projected envelope spending in those days. enough is false when cutting spending alone cannot keep the line above (the dip is deeper than the spending): then cut_cents and per_day_cents are null and envelopes and events empty, and bill leads. bill { recurring_id, name, account_id, amount_cents, on, move_to, after_income, fixes } is a scheduled bill whose next date lands from tomorrow through through, offered move_to the pay after the dip (after_income names it; else the day after), always before its following date; fixes is true when that move alone keeps the line above through through (then it is the smallest such bill; otherwise the largest that can move), or null when none can. Try it with kind: move_bill before offering it. On bills only nothing is projected, so summary_bills_only.cut only ever offers the bill. events is the cut as spend_less what-ifs for POST /v1/cashflow/preview: try them before suggesting anything, and say it as 'Spend $390 less before Nov 6 so your balance never goes below $0: about $56 a day. Groceries is $612 of the projected spending in that time, so $250 of it could come from there. Or move TD Auto Finance to Nov 6.' It is an offer only: nothing changes the budget. The answer, in words. answer (and summary_bills_only.answer) { tone, head, sentence } answers 'do I make it to my next pay?' for that view, null for one card: tone is alarm (the balance goes below $0 before the pay, or reaches Alarm on some day in the window), warning (nothing is spare before the pay, or it goes below Warning, the held buffer, or $0 after the pay) or ok; head is the answer in one sentence ('You will be $604.12 short before your Nov 6 pay.', meaning the balance goes $604.12 below $0; 'You just make it to your Nov 6 pay, with nothing spare to spend.'; 'You make it to your Nov 6 pay with room to spare.') and sentence says when and how low. Every figure in them is measured from $0; Alarm and Warning are only named. Show both as they are. Ways to fix it. fixes (and summary_bills_only.fixes) is null while there is nothing to fix (when cut is null); otherwise { line, through, sentence, items }, one list of what would keep the balance at or above line (the cut's: $0, or the held buffer) through through: each item { key, kind (move_bill|spend_less), title, why, enough, events, lowest_cents, lowest_on, bill }. A move_bill item is one scheduled bill paid on a later day (bill { recurring_id, name, amount_cents, on, move_to, after_income }), tried on the line: lowest_cents / lowest_on are the lowest the balance would be with it through the pay, and enough is true when it alone keeps the balance above. The spend_less item is the cut. Bills that are enough on their own come first (smallest first), then the cut, then bills that only help; at most three bills. events are the what-ifs that try an item with POST /v1/cashflow/preview; several can be tried together. title and why are plain sentences for a person ('Pay Rent ($1,000) on Sep 25 instead of Sep 20', 'Your Payroll pay arrives Sep 25. This alone keeps your balance at or above $0.'). An offer only: nothing changes until the person moves the bill or applies a plan with it. Did the forecast come true? The first read of a day (or rows landing from a bank sync) keeps the line's next thirty days, for All cash and each cash account, both views; they are kept thirteen months. forecast_check (null for one card) draws the window's past days against them: { line, started_on, from, until, forecasts, with_spending, bills_only }. forecasts { taken_on, from, until }[] are the forecasts drawn, each over the thirty days after it was taken (a long window is a run of them, each starting from the real balance that day). started_on is the first day one was kept for this line (null: none yet, so nothing can be compared until tomorrow). Each view { days, within_cents, days_within, missed_days, by_cause, biggest, forecast_cents, miss_cents, ratios, overlay_ratios }: each day is judged by its own move against what was forecast for it (never the running gap, so one big miss is one day off), each forecast's first day (a part day) left out; days_within days came within within_cents (a round amount, at least $25) and missed_days were off by more, so the two always add up to days; the off days are counted by their largest cause in by_cause (unscheduled money out with nothing scheduled, unscheduled_income, transfer (moved to or from another of your accounts, nothing scheduled), bill_bigger, bill_smaller, bill_late a bill due that had not landed or landed after its day, bill_early, income_late, income_early, income_different, spending_under everyday spending under the projection, other); biggest { on, miss_cents, kind, causes } is the day that moved it most, each cause { kind, name, amount_cents, scheduled_cents, scheduled_on, recurring_id }, largest first, at most three. forecast_cents and miss_cents (what happened less what was projected) are aligned with points, null where no forecast covers the day; ratios is the forecast on that view's own 0..1 scale and overlay_ratios on overlay's shared scale (all null without one). This read stretches those scales (each point's ratio, overlay and the buffer band) to hold the forecast, so it is always on the chart. Say it as 'On 26 of 30 days the line moved as forecast, within $25. Off on 4 days, 3 of them spending that was not scheduled; the biggest was Oct 12, Costco $412'. GET /v1/cashflow/accuracy gives the same as a list of days. The most you could put in. most { on, through, floor, floor_cents, with_spending, bills_only } (null for one card) is the most that could go out of this line on lump_on (default: the next pay, else today; the line reaches it) and keep the balance at or above $0 every day from then to through, or at or above the buffer safe to spend holds back when it holds one (floor: zero or buffer, at floor_cents). Each view { most_cents, room_cents, lowest_on, lowest_cents }: most_cents in whole dollars, never below 0; lowest_on the day that holds it down. GET /v1/cashflow/most asks the same alone, or on a saved plan's line. Reminders. reminders { plan_id, plan_name, move_id, account_id, on, amount_cents, to }[] are the lump sums of applied cashflow plans in this budget whose day has come and that are not marked done (POST /v1/budgets/{budget_id}/cashflow-plans/{id}/reminders/done): say them, Mintro never moves the money. A READ: nothing is entered and next_due does not move. The forecast kept is about the line, not the budget.

Example

curl 'https://api.mintro.money/v1/cashflow' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

get /v1/streaks

Clerk-work streaks, computed on read, off until asked

scope budget:read · getStreaks

Parameters

  • budget_id query · string

Returns · 200

{ mode, ahead, funded, agreed, filed, prompt }. Off (the default) returns nulls and computes nothing. Quiet and on share the numbers: ahead is consecutive months age of money held or rose (the headline); funded is months every open envelope had what it needed; agreed is months every account with activity was reconciled; filed is 0 while the inbox has a row and the repaired empty window once it does not (Mintro does not store a midnight snapshot, so a previous filing run is not reconstructed). prompt is set only in on, and only when there is something to file. ?budget_id= picks the budget funded is walked for; omit it on a single-budget account.

Example

curl 'https://api.mintro.money/v1/streaks' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

patch /v1/streaks

Set streaks to off, quiet, or on

scope budget:write · setStreaks

Parameters

  • budget_id query · string

Body · JSON

  • mode "off" | "quiet" | "on" · required

Returns · 200

{ mode }. Off stops computing. There are no stored counts to delete: streaks are a bounded read, the same shape as age of money.

Example

curl -X PATCH 'https://api.mintro.money/v1/streaks' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "mode": "off" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.