API · 20 operations

Aims

The aim, its outlook, and scenarios.

get /v1/budgets/{budget_id}/aims

Every goal on this budget, in priority order, with where each stands

scope budget:read · plan feature goals · listAims

Parameters

  • budget_id path · string · required

  • month query · string

Returns · 200

{ aims[] }. Each aim carries what was declared (kind, label, want, target_cents, target_date or target_year, monthly_cents, growth_rate percent per year, the paired loan_id/account_id for a debt) and what is computed on read: have_cents and target_cents (the pile for retirement, the envelope for a fund, what has been paid down for a debt), progress_ratio 0..1, standing (ahead | on_track | at_risk | reached | unknown), recommended_monthly_cents (what would reach the target by its date at growth_rate), reached_in_months with _low/_high at 20% either side of the rate, reached_month. A fund's date counts this month and the target's month, YNAB's way: its projection starts from what the envelope held before this month's assignment and months_left includes both ends. The first open retirement goal with a target is judged by the outlook engine instead, with the same three runs, target and verdict as the projection's retirement.covered and the compare row's covered, so they never disagree: have_cents is the pile now, standing is that verdict, reached_month/_low/_high are when each run's pile reaches the target, growth_rate_low/_high are the outlook's rates, and recommended_monthly_cents is the monthly that gets the expected run there by January of target_year. Also this month's assigned_cents/needed_cents/this_month_shortfall_cents on the contribution envelope, and for a debt the debt block (balance, rate, minimum, payoff_month, interest_cents, credit_clears_month for a consumer proposal). A buffer goal has no envelope: it stands on the cashflow line, and its buffer block is the same as the cashflow read's buffer (without the band): amount_cents is target_cents, have_cents is how far the lowest point ahead (over the next ninety days, with projected spending when any is chosen) stays above $0, standing is reached once that is the whole buffer, on_track while above $0, at_risk at or below $0, unknown with no repeating bills to size it from; covers_days is how many days of bills today's cash (above $0) pays for. Every buffer figure is measured from $0, never from Alarm. Nothing here schedules the contribution. ?month= reads a different month's envelope figures.

Example

curl 'https://api.mintro.money/v1/budgets/default/aims' \ -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.

post /v1/budgets/{budget_id}/aims

Add a goal

scope budget:write · plan feature goals · createAim

Parameters

  • budget_id path · string · required

Body · JSON

  • kind "retirement" | "emergency" | "debt" | "purchase" | "house" | "custom" | "buffer" · required

  • label string · ≤ 80 chars

  • want string | null · ≤ 280 chars

  • target_cents integer | null · ≥ 0

  • target_date string | null · YYYY-MM-DD

  • target_year integer | null · ≥ 2026 · ≤ 2100

  • nest_egg_cents integer | null · ≥ 0

  • monthly_cents integer | null · ≥ 0

  • growth_rate string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • envelope_id string | null · ≤ 64 chars

  • loan_id string | null · ≤ 64 chars

  • account_id string | null · ≤ 64 chars

  • proposal_filed_on string | null · YYYY-MM-DD

  • sort_order integer · ≥ 0

  • buffer_unit "weeks" | "months" | "amount" | null

  • buffer_count integer | null · ≥ 1 · ≤ 52

  • buffer_account_id string | null · ≤ 64 chars

  • holds_safe_to_spend boolean

Returns · 200

The new aim, last in priority unless sort_order says otherwise. kind is retirement (the nest egg by target_year; one open per budget; a second is validation_failed, so change the one there), emergency (a balance to keep), debt (pairs to exactly one of loan_id or a credit card account_id; proposal_filed_on marks a consumer proposal), purchase (an amount by target_date), custom, or buffer: a cushion kept above $0 on the cashflow line, buffer_unit weeks or months with buffer_count (1 to 52 weeks, 1 to 12 months) of the repeating scheduled bills that leave the line, or amount with target_cents; buffer_account_id watches one cash account (omit for All cash); holds_safe_to_spend (default true) holds it back from safe to spend. One open buffer per line; a buffer takes no monthly_cents, envelope or date. monthly_cents finds or creates the contribution envelope under the Goals category with a monthly target, and remaining-vs-target is then the reflection. A card's goal shares the card's payment envelope.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/aims' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "kind": "retirement" }'

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.

  • 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.

put /v1/budgets/{budget_id}/aims/order

Set the priority order

scope budget:write · plan feature goals · reorderAims

Parameters

  • budget_id path · string · required

Body · JSON

  • aims object[] · required · ≤ 50 items

  • aims[].id string · required · ≤ 64 chars

  • aims[].sort_order integer · required · ≥ 0

Returns · 200

{ aims[] } in the new order, sort_order dense from zero. Each named aim moves to its sort_order; the rest keep their relative order. Priority is what the budget funds first and what found money is offered to first.

Example

curl -X PUT 'https://api.mintro.money/v1/budgets/default/aims/order' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "aims": [ { "id": "<id>", "sort_order": 0 } ] }'

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.

  • 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}/aims/preview

See where a draft goal would stand, without writing it

scope budget:read · plan feature goals · previewAim

Parameters

  • budget_id path · string · required

Body · JSON

  • kind "retirement" | "emergency" | "debt" | "purchase" | "house" | "custom" | "buffer"

  • label string · ≤ 80 chars

  • want string | null · ≤ 280 chars

  • target_cents integer | null · ≥ 0

  • target_date string | null · YYYY-MM-DD

  • target_year integer | null · ≥ 2026 · ≤ 2100

  • nest_egg_cents integer | null · ≥ 0

  • monthly_cents integer | null · ≥ 0

  • growth_rate string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • envelope_id string | null · ≤ 64 chars

  • loan_id string | null · ≤ 64 chars

  • account_id string | null · ≤ 64 chars

  • proposal_filed_on string | null · YYYY-MM-DD

  • sort_order integer · ≥ 0

  • buffer_unit "weeks" | "months" | "amount" | null

  • buffer_count integer | null · ≥ 1 · ≤ 52

  • buffer_account_id string | null · ≤ 64 chars

  • holds_safe_to_spend boolean

  • id string · ≤ 64 chars

Returns · 200

One aim in the GET shape, computed from the draft plus the current picture. With id, the draft merges over that stored goal. Creates nothing.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/aims/preview' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "kind": "retirement", "label": "<label>" }'

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.

  • 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}/aims/{id}

One goal with its standing

scope budget:read · plan feature goals · getAimById

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

Each aim carries what was declared (kind, label, want, target_cents, target_date or target_year, monthly_cents, growth_rate percent per year, the paired loan_id/account_id for a debt) and what is computed on read: have_cents and target_cents (the pile for retirement, the envelope for a fund, what has been paid down for a debt), progress_ratio 0..1, standing (ahead | on_track | at_risk | reached | unknown), recommended_monthly_cents (what would reach the target by its date at growth_rate), reached_in_months with _low/_high at 20% either side of the rate, reached_month. A fund's date counts this month and the target's month, YNAB's way: its projection starts from what the envelope held before this month's assignment and months_left includes both ends. The first open retirement goal with a target is judged by the outlook engine instead, with the same three runs, target and verdict as the projection's retirement.covered and the compare row's covered, so they never disagree: have_cents is the pile now, standing is that verdict, reached_month/_low/_high are when each run's pile reaches the target, growth_rate_low/_high are the outlook's rates, and recommended_monthly_cents is the monthly that gets the expected run there by January of target_year. Also this month's assigned_cents/needed_cents/this_month_shortfall_cents on the contribution envelope, and for a debt the debt block (balance, rate, minimum, payoff_month, interest_cents, credit_clears_month for a consumer proposal). A buffer goal has no envelope: it stands on the cashflow line, and its buffer block is the same as the cashflow read's buffer (without the band): amount_cents is target_cents, have_cents is how far the lowest point ahead (over the next ninety days, with projected spending when any is chosen) stays above $0, standing is reached once that is the whole buffer, on_track while above $0, at_risk at or below $0, unknown with no repeating bills to size it from; covers_days is how many days of bills today's cash (above $0) pays for. Every buffer figure is measured from $0, never from Alarm. Nothing here schedules the contribution.

Example

curl 'https://api.mintro.money/v1/budgets/default/aims/<id>' \ -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.

  • 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}/aims/{id}

Change a goal

scope budget:write · plan feature goals · updateAim

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • kind "retirement" | "emergency" | "debt" | "purchase" | "house" | "custom" | "buffer"

  • label string · ≤ 80 chars

  • want string | null · ≤ 280 chars

  • target_cents integer | null · ≥ 0

  • target_date string | null · YYYY-MM-DD

  • target_year integer | null · ≥ 2026 · ≤ 2100

  • nest_egg_cents integer | null · ≥ 0

  • monthly_cents integer | null · ≥ 0

  • growth_rate string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • envelope_id string | null · ≤ 64 chars

  • loan_id string | null · ≤ 64 chars

  • account_id string | null · ≤ 64 chars

  • proposal_filed_on string | null · YYYY-MM-DD

  • sort_order integer · ≥ 0

  • buffer_unit "weeks" | "months" | "amount" | null

  • buffer_count integer | null · ≥ 1 · ≤ 52

  • buffer_account_id string | null · ≤ 64 chars

  • holds_safe_to_spend boolean

  • status "open" | "closed"

Returns · 200

The aim after the change. Skip a field to keep it; pass null to clear it. status: closed keeps the goal and its history but drops it from the headline and from found money. Changing monthly_cents retargets the contribution envelope.

Example

curl -X PATCH 'https://api.mintro.money/v1/budgets/default/aims/<id>' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "kind": "retirement", "label": "<label>" }'

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.

  • 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/budgets/{budget_id}/aims/{id}

Remove a goal; the contribution envelope and its money stay

scope budget:write · plan feature goals · deleteAimById

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

{ deleted: true }.

Example

curl -X DELETE 'https://api.mintro.money/v1/budgets/default/aims/<id>' \ -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.

  • 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}/aim

Deprecated: the headline goal. Use GET /aims.

scope budget:read · plan feature goals · getAim

Parameters

  • budget_id path · string · required

Returns · 200

{ aim } or { aim: null }: the first open retirement goal, else the first open goal, in the same shape as /aims/{id}. Kept one release.

Example

curl 'https://api.mintro.money/v1/budgets/default/aim' \ -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.

put /v1/budgets/{budget_id}/aim

Deprecated: set or replace the headline goal. Use POST /aims and PATCH /aims/{id}.

scope budget:write · plan feature goals · putAim

Parameters

  • budget_id path · string · required

Body · JSON

  • kind "retirement" | "emergency" | "debt" | "purchase" | "house" | "custom" | "buffer"

  • label string · ≤ 80 chars

  • want string | null · ≤ 280 chars

  • target_year integer | null · ≥ 2026 · ≤ 2100

  • nest_egg_cents integer | null · ≥ 0

  • monthly_cents integer | null · ≥ 0

  • assumed_return string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • growth_rate string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • envelope_id string | null · ≤ 64 chars

Returns · 200

The headline aim after the write. Creates a retirement goal when there is none. assumed_return is accepted as the old name for growth_rate.

Example

curl -X PUT 'https://api.mintro.money/v1/budgets/default/aim' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "kind": "retirement", "label": "<label>" }'

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.

  • 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/budgets/{budget_id}/aim

Deprecated: remove the headline goal. Use DELETE /aims/{id}.

scope budget:write · plan feature goals · deleteAim

Parameters

  • budget_id path · string · required

Returns · 200

{ deleted: true }.

Example

curl -X DELETE 'https://api.mintro.money/v1/budgets/default/aim' \ -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.

  • 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}/aim/preview

Deprecated: preview the headline goal. Use POST /aims/preview.

scope budget:read · plan feature goals · previewHeadlineAim

Parameters

  • budget_id path · string · required

Body · JSON

  • kind "retirement" | "emergency" | "debt" | "purchase" | "house" | "custom" | "buffer"

  • label string · ≤ 80 chars

  • want string | null · ≤ 280 chars

  • target_year integer | null · ≥ 2026 · ≤ 2100

  • nest_egg_cents integer | null · ≥ 0

  • monthly_cents integer | null · ≥ 0

  • assumed_return string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • growth_rate string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • envelope_id string | null · ≤ 64 chars

Returns · 200

One aim in the GET shape, the draft merged over the headline goal. Creates nothing.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/aim/preview' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "kind": "retirement", "label": "<label>" }'

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.

  • 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}/outlook

The assumptions behind every projection, with the defaults applied

scope budget:read · plan feature goals · getOutlook

Parameters

  • budget_id path · string · required

Returns · 200

{ stored, fields, birth_year, retire_at_age, plan_to_age, return_low, return_expected, return_high, inflation, withdrawal_rate, retirement_spend_percent, other_retirement_income_cents, current_age, retire_year, plan_to_year, months_to_retirement, months_to_plan_end, monthly_income_cents, retirement_spend_cents, fi_number_cents }. fields is what was stored (null where the default applies); the rest has every default in place. Defaults (FP Canada 2026): 6% expected after fees, 4.8% low, 7.2% high, 2.1% inflation, retire at 65, plan to 95, spend 70% of income, withdraw 4%. monthly_income_cents is the twelve-month average from the assignment ledger; retirement_spend_cents is the figure, else the percent of that; fi_number_cents is spend less other income, times twelve, over the withdrawal rate. Without a birth year, retirement is the headline goal's year.

Example

curl 'https://api.mintro.money/v1/budgets/default/outlook' \ -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.

put /v1/budgets/{budget_id}/outlook

Set the assumptions

scope budget:write · plan feature goals · putOutlook

Parameters

  • budget_id path · string · required

Body · JSON

  • birth_year integer | null · ≥ 1900 · ≤ 2100

  • retire_at_age integer | null · ≥ 18 · ≤ 120

  • plan_to_age integer | null · ≥ 18 · ≤ 120

  • return_low string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • return_expected string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • return_high string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • inflation string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • retirement_spend_cents integer | null · ≥ 0

  • retirement_spend_percent string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • withdrawal_rate string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • other_retirement_income_cents integer | null · ≥ 0

Returns · 200

The outlook after the write. Skip a field to keep it; null goes back to the default. Rates are percent per year as decimal strings; low ≤ expected ≤ high; plan_to_age ≥ retire_at_age.

Example

curl -X PUT 'https://api.mintro.money/v1/budgets/default/outlook' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "birth_year": 1900, "retire_at_age": 18 }'

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.

  • 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}/outlook/projection

Net worth to the end of the plan at three rates, with milestones and the history before it

scope budget:read · plan feature goals · readProjection

Parameters

  • budget_id path · string · required

  • scenario query · string

  • history_months query · string

Returns · 200

{ scenario, horizon, rates, series: { low, expected, high }, retirement, milestones[], history, chart }. Each series walks the picture month by month: invested compounding at its rate, contributions from goals until retirement, loans and cards paying down at their terms, one-time events from the scenario, then the retirement spend (inflated) drawn from invested then steady. It reports a point each December: invested_cents, steady_cents, held_cents, loans_cents, secured_loans_cents, pile_cents, net_cents, real_net_cents (today's dollars), plus at_retirement_net_cents, at_retirement_pile_cents, depleted_month, debt_free_month, secured_free_month. The pile is money to live on: invested + steady − the loans not against a held thing; a house stays out with its mortgage. While working, payments come from income; after retirement they come out of the pile, and a payment no debt needs any more joins invested while working (debt.freed_to_invest). retirement says what enough is and whether the plan gets there: target_cents is the first open retirement goal's target when it names one (target_source: goal, due January of its year), else the FI number from a known retirement spend (target_source: spend, due at retirement), else null; covered is whether the expected run's pile (pile_at_target_cents) reaches it by target_month, and null when there is no target (no retirement spend known and no goal target): unknown, never yes. The retirement goal's own standing is this same verdict. It also says at what age each rate runs out. Money for goals that are not retirement (an emergency fund, a purchase) is kept apart: net worth counts it as cash, the pile does not, and a goal's contribution stops once its target is there. debt_free_month is null while a card or loan has no rate or minimum; so is secured_free_month while a loan on a held thing has none. milestones are goal reached, debt free (consumer debts), the mortgage's own end (secured_free), credit clears, coast, enough, retirement, runs out, above zero. chart is the same in 0..1 ratios with today_index, ticks and milestone indices. Draw it, do not recompute it. ?scenario= reads a named scenario over the baseline. Never Monte Carlos: three deterministic runs.

Example

curl 'https://api.mintro.money/v1/budgets/default/outlook/projection' \ -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.

  • 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}/scenarios

The scenarios: the baseline and every named what-if

scope budget:read · plan feature goals · listScenarios

Parameters

  • budget_id path · string · required

Returns · 200

{ scenarios[] }, the baseline first. Each carries overrides: outlook (any assumption), contributions (goal id → cents a month), events (each with a kind: once is on YYYY-MM, cents, label, a split across invest, steady, spend, aim:<id> or debt:<id> that sums to the amount; monthly is the same, repeating every month to until; sell means holding_id goes that month at price_cents or, blank, what it is expected to be worth then (GET /holdings/{id}?at=), the loans on it are paid first, an optional split takes fixed parts off the proceeds, and the rest goes to rest_to, invest by default), debt (the payoff block). Each also carries warnings when a stored shape no longer parses (it reads as empty, never a 500). The baseline is made on first read and holds the payoff and expected inflows. Pass a scenario's id as ?scenario= on the projection and the payoff to read it over the baseline.

Example

curl 'https://api.mintro.money/v1/budgets/default/scenarios' \ -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.

post /v1/budgets/{budget_id}/scenarios

Add a what-if

scope budget:write · plan feature goals · createScenario

Parameters

  • budget_id path · string · required

Body · JSON

  • name string · required · ≤ 80 chars

  • overrides object

  • overrides.outlook object

  • overrides.outlook.birth_year integer | null · ≥ 1900 · ≤ 2100

  • overrides.outlook.retire_at_age integer | null · ≥ 18 · ≤ 120

  • overrides.outlook.plan_to_age integer | null · ≥ 18 · ≤ 120

  • overrides.outlook.return_low string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.return_expected string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.return_high string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.inflation string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.retirement_spend_cents integer | null · ≥ 0

  • overrides.outlook.retirement_spend_percent string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.withdrawal_rate string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.other_retirement_income_cents integer | null · ≥ 0

  • overrides.contributions object

  • overrides.events object[] · ≤ 50 items

  • overrides.events[].kind "once" | "monthly" | "sell" · required · default "once"

  • overrides.events[].on string · required · YYYY-MM

  • overrides.events[].until string · YYYY-MM

  • overrides.events[].cents integer · ≥ -9007199254740991

  • overrides.events[].label string · required · ≤ 80 chars

  • overrides.events[].split object[] · ≤ 8 items

  • overrides.events[].holding_id string · ≤ 64 chars

  • overrides.events[].price_cents integer | null · ≥ 0

  • overrides.events[].rest_to string · matches ^(invest|steady|spend|aim:[A-Za-z0-9_]+|debt:[A-Za-z0-9_]+)$

  • overrides.debt object

  • overrides.debt.strategy "planned" | "avalanche" | "snowball" | "custom"

  • overrides.debt.extra_monthly_cents integer · ≥ 0

  • overrides.debt.order string[] · ≤ 50 items

  • overrides.debt.snowflakes object[] · ≤ 50 items

  • overrides.debt.freed_to_invest boolean

Returns · 200

The new scenario. Blank fields inherit from the baseline. Contribution and event targets must name goals and liabilities that exist; a split must sum to the event's amount. Up to twenty per budget.

Example

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

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.

  • 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}/scenarios/compare

Every scenario side by side

scope budget:read · plan feature goals · compareScenarios

Parameters

  • budget_id path · string · required

Returns · 200

{ rows[] }: per scenario, net and pile at retirement, the FI number, target_cents and target_source (as on the projection's retirement), covered (null when there is no target: unknown, never yes; the retirement goal's standing is the same verdict), when it runs out and at what age, the debt-free month (consumer debts; null while untracked_debts, the debts with no rate or minimum, is above zero) and the mortgage's own end, total interest, each open goal's reached month and standing, and how many events it holds. Same engine as the projection; the difference is the decision.

Example

curl 'https://api.mintro.money/v1/budgets/default/scenarios/compare' \ -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}/scenarios/{id}

One scenario

scope budget:read · plan feature goals · getScenario

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

The scenario with its overrides and warnings.

Example

curl 'https://api.mintro.money/v1/budgets/default/scenarios/<id>' \ -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.

  • 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}/scenarios/{id}

Change a scenario

scope budget:write · plan feature goals · updateScenario

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • name string · ≤ 80 chars

  • overrides object

  • overrides.outlook object

  • overrides.outlook.birth_year integer | null · ≥ 1900 · ≤ 2100

  • overrides.outlook.retire_at_age integer | null · ≥ 18 · ≤ 120

  • overrides.outlook.plan_to_age integer | null · ≥ 18 · ≤ 120

  • overrides.outlook.return_low string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.return_expected string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.return_high string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.inflation string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.retirement_spend_cents integer | null · ≥ 0

  • overrides.outlook.retirement_spend_percent string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.withdrawal_rate string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • overrides.outlook.other_retirement_income_cents integer | null · ≥ 0

  • overrides.contributions object

  • overrides.events object[] · ≤ 50 items

  • overrides.events[].kind "once" | "monthly" | "sell" · required · default "once"

  • overrides.events[].on string · required · YYYY-MM

  • overrides.events[].until string · YYYY-MM

  • overrides.events[].cents integer · ≥ -9007199254740991

  • overrides.events[].label string · required · ≤ 80 chars

  • overrides.events[].split object[] · ≤ 8 items

  • overrides.events[].holding_id string · ≤ 64 chars

  • overrides.events[].price_cents integer | null · ≥ 0

  • overrides.events[].rest_to string · matches ^(invest|steady|spend|aim:[A-Za-z0-9_]+|debt:[A-Za-z0-9_]+)$

  • overrides.debt object

  • overrides.debt.strategy "planned" | "avalanche" | "snowball" | "custom"

  • overrides.debt.extra_monthly_cents integer · ≥ 0

  • overrides.debt.order string[] · ≤ 50 items

  • overrides.debt.snowflakes object[] · ≤ 50 items

  • overrides.debt.freed_to_invest boolean

Returns · 200

The scenario after the change. overrides replaces the whole set. The baseline keeps its name.

Example

curl -X PATCH 'https://api.mintro.money/v1/budgets/default/scenarios/<id>' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "name": "<name>", "overrides": { "outlook": { "birth_year": 1900, "retire_at_age": 18 }, "contributions": {} } }'

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.

  • 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/budgets/{budget_id}/scenarios/{id}

Remove a scenario

scope budget:write · plan feature goals · deleteScenario

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

{ deleted: true }. The baseline cannot be removed (409 not_deletable).

Example

curl -X DELETE 'https://api.mintro.money/v1/budgets/default/scenarios/<id>' \ -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.

  • 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_deletable: A row from your bank's feed, or a bank account, cannot be deleted: it is a fact from the feed. Rows you entered can be. Unfile it (delete its entries) if it is misfiled, or close the connection to stop the feed.