API · 10 operations

Accounts

Accounts, reconciliation, cleared state, forecasts.

post /v1/accounts

Add an account by hand

scope budget:write · createAccount

Parameters

  • budget_id query · string

Body · JSON

  • name string · required · ≤ 100 chars

  • type "checking" | "savings" | "credit" | "cash"

  • starting_balance_cents integer · ≥ -9007199254740991

  • as_of string · YYYY-MM-DD

  • budget_id string

Returns · 201

The created account. Until this existed an account could only appear as a side effect of importing into it, so there was no way to start the way every budgeting app starts. starting_balance_cents creates a transaction dated as_of and leaves it UNFILED in the inbox on purpose: for a chequing account it is money to budget, for a card it is a debt, and Mintro does not decide which. type accepts checking | savings | credit | cash; use credit to get card payment envelopes. Manual accounts count against the free plan limit.

Example

curl -X POST 'https://api.mintro.money/v1/accounts' \ -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/accounts

Every account in the budget

scope budget:read · listAccounts

Parameters

  • budget_id query · string

Returns · 200

{ accounts: [{ id, name, type, subtype, mask, connection_id, institution, manual, connection_status, last_synced_at, sync_from, bank_account_id, bank_balance_cents, bank_balance_at, bank_staged_count, bank_gap_cents, auto_land, transaction_count, working_balance_cents, currency, payment_envelope_id, low_cents, ok_cents }], totals: [{ currency, working_balance_cents }] }. working_balance_cents is everything the account holds, cleared or not, in one call rather than a reconciliation read per account. totals adds them across accounts by currency, so nothing on a screen has to; currency is the budget's home currency. On a credit account it is NEGATIVE when money is owed. manual: true accounts are hand-entered or imported and count against the free plan's two-account allowance; bank accounts come from connections, and carry their bank's connection_status (as GET /v1/connections says it), last_synced_at and next_sync_at, both null on a hand-kept account. next_sync_at is when Mintro next asks the bank by itself (every six hours; the bank can push new rows sooner); it is null while the bank wants a fresh sign-in. bank_balance_cents is the bank's own balance in Mintro's sign (a card reads negative), as of bank_balance_at. bank_gap_cents is that balance less what Mintro holds that the bank knows about (cleared rows), taken with and without pending holds and against the working balance, whichever is smallest (a row typed by hand that the bank has but nobody ticked cleared is not a gap): 0 means they agree, positive means the bank has more than Mintro counts. It is null with no bank balance, or while rows wait in review (bank_staged_count). When it is not 0, read GET /v1/bank-activity?account_id= for what the bank sent, changed or took back, and show the human. sync_from is set on a bank account that took over a hand-kept one: the bank's rows before it are not synced. An account belongs to ONE budget: ?budget_id= names it (default resolves as everywhere else), and a sibling budget's accounts never appear. payment_envelope_id on a credit account is the envelope in that budget holding its payment money. low_cents is the account's Alarm line: at or below it the cashflow line turns red; default 0 means overdrawn. ok_cents is its Warning line: at or below it the line turns yellow; null means no Warning.

Example

curl 'https://api.mintro.money/v1/accounts' \ -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/accounts/{id}/reconciliation

Where this account stands against the bank

scope budget:read · getReconciliation

Parameters

  • id path · string · required

  • budget_id query · string

Returns · 200

{ cleared_balance_cents, cleared_count, uncleared_count, uncleared_total_cents, reconciled_count, reconciled_balance_cents, drift_cents, working_balance_cents, last_reconciled_at, last_statement_balance_cents }. Cleared rows are what a statement balance is compared to; the uncleared ones are where a difference hides. reconciled_* are the rows a reconciliation locked and what they sum to; drift_cents is how far that sum has moved from the statement it was locked against (non-zero only after an unlock changed a row; null before any reconciliation). working_balance_cents is everything the account holds, reconciled or not, the figure a person means by "how much is in there".

Example

curl 'https://api.mintro.money/v1/accounts/<id>/reconciliation' \ -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/accounts/{id}/cleared

Tick transactions on or off the cleared list

scope budget:write · setCleared

Parameters

  • id path · string · required

  • budget_id query · string

Body · JSON

  • transaction_ids string[] · required · ≤ 500 items

  • cleared boolean · required

Returns · 200

{ updated }. All-or-nothing: if any id belongs to another account it is 404 and nothing changes.

Example

curl -X PATCH 'https://api.mintro.money/v1/accounts/<id>/cleared' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "transaction_ids": [ "<transaction_ids>" ], "cleared": 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.

    locked: This transaction is reconciled: its date, amount, account and cleared mark were agreed with the bank and are locked. Its envelope can still change. If the bank fact itself is wrong, POST /v1/transactions/{id}/unlock first; the account then shows how far its reconciled balance has moved.

get /v1/accounts/{id}/forecast

Project this account's cash through a horizon

scope budget:read · getAccountForecast

Parameters

  • id path · string · required

  • budget_id query · string

  • until query · string

Returns · 200

{ account_id, until, working_balance_cents, low_cents, lowest_cents, lowest_on, starts_below, below, events }. A read of working balance plus every occurrence of this account's active recurring rows until until (YYYY-MM-DD, default last day of this month). Weekly bills expand; Upcoming stays one row per declaration. Same calendar day applies income first. Does NOT write a transaction and does NOT move next_due. below is each hit after which the running total is at or below low_cents. lowest_on is null when the low point is the starting working balance.

Example

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

post /v1/accounts/{id}/reconcile

Compare cleared rows against a statement, and lock if they agree

scope budget:write · reconcileAccount

Parameters

  • id path · string · required

  • budget_id query · string

Body · JSON

  • statement_balance_cents integer · required · ≥ -9007199254740991

  • statement_date string | string · required

  • check_only boolean

Returns · 200

{ reconciled, difference_cents, cleared_balance_cents, statement_balance_cents, reconciliation_id, locked_count }. A mismatch is 200 with reconciled: false. That is the useful answer, and the difference is exactly what has not been entered yet. Mintro will NOT create an adjustment row to force agreement; papering over a difference is how a budget quietly stops being true. On agreement every cleared row on the account is locked: reconciled_at is stamped on it, and from then on its date, amount, account and cleared mark refuse to change (409 locked); its envelope still can. locked_count is how many this call locked; rows a previous reconciliation locked stay as they were. check_only: true runs the same comparison and never locks (at zero it answers reconciled: false with would_lock, the rows a real call would lock), so you can watch the difference close as rows are ticked and lock only when the person says so.

Example

curl -X POST 'https://api.mintro.money/v1/accounts/<id>/reconcile' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "statement_balance_cents": 45000, "statement_date": "2026-09" }'

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.

patch /v1/accounts/{id}

Rename an account, point a credit account at its payment envelope, set its Alarm and Warning lines, or give a card its terms

scope budget:write · updateAccount

Parameters

  • id path · string · required

  • budget_id query · string

Body · JSON

  • name string · ≤ 100 chars

  • type "checking" | "savings" | "cash"

  • payment_envelope_id string | null · ≤ 64 chars

  • low_cents integer · ≥ -9007199254740991

  • ok_cents integer | null · ≥ -9007199254740991

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

  • minimum_payment_cents integer | null · ≥ 0

  • credit_limit_cents integer | null · ≥ 0

Returns · 200

{ account_id, payment_envelope_id, low_cents, ok_cents, annual_rate, minimum_payment_cents, credit_limit_cents }. Credit-card payment categories: once set, spending on this card sets aside the money to pay it: the funded portion of each purchase is reserved in that envelope, so the statement never surprises you. Only the funded portion moves; the rest shows as overspending. Pass null to unlink. ?budget_id= picks the budget the envelope belongs to. low_cents is the Alarm line, where cashflow turns red; default 0 is overdrawn. ok_cents is the Warning line, where it turns yellow; null drops it. annual_rate (percent per year) and minimum_payment_cents (a month) are a card's terms: a debt goal can pair to a card once both are known; null means not known and the card stays out of any payoff. credit_limit_cents is the card's limit: the line its cashflow is drawn against, the way $0 is a bank account's; null draws no line. Only payment_envelope_id needs a budget. name renames any account (unique per tenant); type (checking | savings | cash) is for accounts kept by hand; a card stays a card and a linked account's kind is the bank's.

Example

curl -X PATCH 'https://api.mintro.money/v1/accounts/<id>' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "name": "<name>", "type": "checking" }'

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

Delete a manual account and everything imported into it

scope budget:write · deleteAccount

Parameters

  • id path · string · required

  • budget_id query · string

Returns · 200

{ deleted: true, transactions_deleted, entries_deleted }. Every transaction in the account goes, filed entries come off and envelope totals recompute. Bank accounts cannot be deleted (409 not_deletable); close the connection instead.

Example

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

    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.

post /v1/accounts/{id}/adopt

This bank account is my account: pair it with the one kept by hand

scope connections:write · adoptBankAccount

Parameters

  • id path · string · required

  • budget_id query · string

Body · JSON

  • bank_account_id string · required · ≤ 64 chars

Returns · 200

{ account, matched, skipped_before, sync_from }. {id} is an account kept by hand (manual: true); bank_account_id is one a bank is sending. The hand-kept account takes over the bank account and keeps its id, name, card envelope, loans, goals and reconciliations; the bank account's row goes. Bank rows dated before the earliest row the account already held are dropped (skipped_before) and never sync again (sync_from), because that history is already counted. Each bank row matching a row typed or imported by hand (same cents, within seven days, closest date) replaces it, keeping its envelopes, note and income (matched); the rest stay. Cards pair with cards, everything else with a deposit account. Use it after a link that said needs_pairing, or when a bank was linked before its account was kept.

Example

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

    link_conflict: The accounts are not in the state a bank link needs: pairing takes one account kept by hand and one a bank is sending, and unlinking takes a linked account. GET /v1/accounts (manual, connection_id) shows which is which; the message says what is wrong. Nothing to retry until that changes.

post /v1/accounts/{id}/unlink

Stop syncing this account and keep it by hand; its history stays

scope connections:write · unlinkAccount

Parameters

  • id path · string · required

  • budget_id query · string

Returns · 200

{ account, connection_closed }. The account stops receiving the bank's rows and becomes one kept by hand (manual: true); every transaction, filing, reconciliation and link stays. When it was the bank's last account the bank itself is removed at Plaid (connection_closed: true, the same as DELETE /v1/connections/{id}). Linking it again later: POST /v1/connections/link with account_id.

Example

curl -X POST 'https://api.mintro.money/v1/accounts/<id>/unlink' \ -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.

    link_conflict: The accounts are not in the state a bank link needs: pairing takes one account kept by hand and one a bank is sending, and unlinking takes a linked account. GET /v1/accounts (manual, connection_id) shows which is which; the message says what is wrong. Nothing to retry until that changes.