API · 11 operations

Loans

Loans, their balances and payments, and debt payoff.

get /v1/budgets/{budget_id}/loans

List installment loans on this budget

scope budget:read · plan feature assets · listLoans

Parameters

  • budget_id path · string · required

Returns · 200

{ loans[] }. Declared remaining principal in the loan's currency, plus home_currency, home_cents (the remaining in the budget's currency, null with no rate) and fx; optional holding, optional paying account, next_payment split from the terms (estimated: true). Not a credit account. Each loan also carries payoff: { paid_cents, of_cents, paid_ratio, payments_left, paid_off_month, interest_left_cents, never }, when it ends at its own regular payment and rate with nothing extra (the debt payoff on Goals is the plan with extra money), how much has come off since it was borrowed (of_cents is the original amount, else what was owed when it was added), and paid_ratio 0..1 for a progress bar. never: true when the payment does not cover the interest; with no regular payment payments_left and paid_off_month are null.

Example

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

Declare an installment loan; optionally against a holding and a paying account

scope budget:write · plan feature assets · createLoan

Parameters

  • budget_id path · string · required

Body · JSON

  • name string · required · ≤ 200 chars

  • holding_id string | null · ≤ 64 chars

  • account_id string | null · ≤ 64 chars

  • currency string · ≥ 3 chars · ≤ 3 chars

  • original_principal_cents integer | null · ≥ 0

  • remaining_principal_cents integer · required · ≥ 0

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

  • payment_cents integer | null · ≥ 0

  • payments_per_year integer · ≥ 1 · ≤ 52

  • note string | null · ≤ 1000 chars

Returns · 201

The loan. next_payment is the estimated interest/principal split of payment_cents against remaining. Does not write an entry.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/loans' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "name": "<name>", "remaining_principal_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.

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

One loan

scope budget:read · plan feature assets · getLoan

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

The current remaining plus the next-payment split.

Example

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

Rename, retarget a holding or paying account, change terms, or agree remaining

scope budget:write · plan feature assets · updateLoan

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • name string · ≤ 200 chars

  • holding_id string | null · ≤ 64 chars

  • account_id string | null · ≤ 64 chars

  • status "open" | "paid"

  • original_principal_cents integer | null · ≥ 0

  • remaining_principal_cents integer · ≥ 0

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

  • payment_cents integer | null · ≥ 0

  • payments_per_year integer · ≥ 1 · ≤ 52

Returns · 200

The updated loan. A remaining change appends a balance event noted as agreed with the lender.

Example

curl -X PATCH 'https://api.mintro.money/v1/budgets/default/loans/<id>' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "name": "<name>", "holding_id": "<holding_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.

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

Delete a loan and its balance events

scope budget:write · plan feature assets · deleteLoan

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/loans/<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}/loans/{id}/balances

The loan's history: every payment with the bank row that made it (newest first, cap 100)

scope budget:read · plan feature assets · listLoanBalances

Parameters

  • budget_id path · string · required

  • id path · string · required

  • order query · string

Returns · 200

{ balances[] }, each { id, kind, remaining_principal_cents, payment_cents, interest_cents, principal_cents, fee_cents, as_of, note, transaction_id, transaction: { date, name, account_id, amount_cents } | null, reversed_at, reverses_id }. kind is opened | regular | extra_principal | interest_only | fee | agreed | reversal. remaining_principal_cents is the running remaining after that row. A payment that came from a bank row names it in transaction; GET /v1/transactions?loan_id= lists those rows. A reversal undoes the row in reverses_id (its own reversed_at is set), when a payment was unlinked or re-applied after the bank changed the amount. order=asc walks oldest first.

Example

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

post /v1/budgets/{budget_id}/loans/{id}/payments

Record a payment with no bank row: a lender-charged fee, or history from before Mintro

scope budget:write · plan feature assets · applyLoanPayment

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • amount_cents integer · required · ≥ 1

  • kind "regular" | "extra_principal" | "interest_only" | "fee"

  • interest_cents integer · ≥ 0

  • principal_cents integer · ≥ 0

  • fee_cents integer · ≥ 0

  • note string | null · ≤ 1000 chars

Returns · 201

The loan after the split, plus payment (the balance row). kind regular (default; interest and principal from the terms unless interest_cents/principal_cents are given and add up with fee_cents to the amount), extra_principal, interest_only, or fee. A fee with no bank row is one the lender ADDED to what is owed (an NSF fee on the statement), so remaining goes UP by it. When the payment left a bank account, prefer POST /v1/transactions/{id}/loan-payment (the row already exists) or POST .../pay-from-account (Mintro writes it): those keep the history tied to the money. PATCH remaining to agree with the lender.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/loans/<id>/payments' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "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.

  • 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}/loans/{id}/pay-from-account

Pay the loan from an account: write the outflow and link it as the payment

scope budget:write · plan feature assets · payLoanFromAccount

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • account_id string · required · ≤ 64 chars

  • envelope_id string · required · ≤ 64 chars

  • amount_cents integer · required · ≥ 1

  • date string · required · YYYY-MM-DD

  • kind "regular" | "extra_principal" | "interest_only" | "fee"

  • interest_cents integer · ≥ 0

  • principal_cents integer · ≥ 0

  • fee_cents integer · ≥ 0

  • note string | null · ≤ 1000 chars

Returns · 201

{ loan, outflow }. One transaction on the paying account, filed into envelope_id (required: a loan payment comes out of an envelope, as in YNAB, so the envelope's activity carries it) and linked to the loan, plus the split (kind and an explicit split as on POST /v1/transactions/{id}/loan-payment; the terms estimate it otherwise). Not a transfer: a loan is not an account. When the bank already delivered the row, link that row instead of writing a second one.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/loans/<id>/pay-from-account' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "account_id": "<account_id>", "envelope_id": "<envelope_id>", "amount_cents": 45000, "date": "2026-09-15" }'

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}/debt-payoff

The debt payoff: order, extra, lump sums, and when each debt ends

scope budget:read · plan feature goals · getDebtPayoff

Parameters

  • budget_id path · string · required

  • scenario query · string

Returns · 200

{ scenario, strategy, extra_monthly_cents, order[], snowflakes[], debts[], excluded[], debt_free_month, secured_free_month, total_interest_cents, minimums_only, interest_saved_cents, months_saved, burndown }. Every open loan and every card with terms, each paired to the goal that names it. strategy is planned (goal priority), avalanche (rate, highest first), snowball (balance, smallest first) or custom (order); a consumer proposal is always first and takes no extra; only a lump sum shortens it. A loan on a held thing (secured, a mortgage) sits last and takes only its own payment unless a custom order names it; debt_free_month means the consumer debts and secured_free_month is the mortgage's own end. Each month every debt takes its minimum (plus its goal's extra), and the pool (extra_monthly_cents, this month's snowflakes, and the minimums of debts already paid) goes to the first unpaid in order. Per debt: this_month_cents, payoff_month, interest_cents, credit_clears_month, and secured with holding_id / holding_name when it is against a held holding (a mortgage). freed_to_invest says whether a payment no debt needs any more joins retirement money. minimums_only is the same debts with nothing extra; interest_saved_cents and months_saved are against it. excluded names cards and loans without terms (needs_terms), each with secured; while a consumer debt is excluded debt_free_month (and minimums_only.debt_free_month) is null rather than a date that leaves it out, and the same holds for secured_free_month and an excluded loan on a held thing. burndown is balances by month with stacked 0..1 ratios for a chart. A read; ?scenario= reads a named scenario's block.

Example

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

put /v1/budgets/{budget_id}/debt-payoff

Set the payoff: strategy, extra a month, custom order, lump sums, freed payments

scope budget:write · plan feature goals · putDebtPayoff

Parameters

  • budget_id path · string · required

Body · JSON

  • strategy "planned" | "avalanche" | "snowball" | "custom"

  • extra_monthly_cents integer · ≥ 0

  • order string[] · ≤ 50 items

  • snowflakes object[] · ≤ 50 items

  • snowflakes[].on string · required · YYYY-MM

  • snowflakes[].cents integer · required · ≥ 0

  • snowflakes[].label string · ≤ 80 chars

  • freed_to_invest boolean

  • scenario_id string · ≤ 64 chars

Returns · 200

The payoff after the write. Writes the scenario's debt block (the baseline unless scenario_id names one); fields left out keep their value. Nothing moves and no target changes until /debt-payoff/apply.

Example

curl -X PUT 'https://api.mintro.money/v1/budgets/default/debt-payoff' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "strategy": "planned", "extra_monthly_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.

  • 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}/debt-payoff/apply

Apply the payoff to the budget: each paired envelope's target

scope budget:write · plan feature goals · applyDebtPayoff

Parameters

  • budget_id path · string · required

Body · JSON

  • dry_run boolean

  • scenario_id string · ≤ 64 chars

Returns · 200

{ dry_run, changes[], skipped[] }. For every debt whose goal has a contribution envelope, the envelope's target becomes what is left to pay (balance plus the interest to come) by the payoff month, as a debt target: owed every month, never counted against what the envelope already holds; a debt that never ends at this payment gets a monthly target of this month's payment. Written through the same envelope update an agent would make; not an entry, not a scheduler. dry_run: true lists the changes and writes nothing. skipped names debts with no goal or no envelope.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/debt-payoff/apply' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "dry_run": true, "scenario_id": "<scenario_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.

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