API · 13 operations

Connections

Bank connections through Plaid, and the rows a bank sends waiting for a person.

get /v1/connections/{id}/bank-accounts

The accounts a linked bank shared, with the bank's balances and what each is mapped to

scope connections:read · listConnectionBankAccounts

Parameters

  • id path · string · required

  • budget_id query · string

Returns · 200

{ bank_accounts: [{ id, connection_id, institution, name, mask, type, subtype, currency, balance_current_cents, balance_available_cents, limit_cents, balance_at, mapping, account_id, loan_id, sync_from, auto_land, staged_count }] }. A new link arrives with every account mapping: unmapped: nothing from it reaches the budget until the person says what each one is (PUT /v1/bank-accounts/{id}). balance_current_cents is the bank's own figure as the bank signs it; a card or loan's is what is owed. staged_count is how many rows wait for review; a row the bank only sent again is not counted, since sync makes it the row held by itself. Ask the human which account is which; never map on a guess.

Example

curl 'https://api.mintro.money/v1/connections/<id>/bank-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.

  • 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/bank-accounts

Every bank account in staging, across all linked banks

scope connections:read · listBankAccounts

Parameters

  • budget_id query · string

Returns · 200

{ bank_accounts: [...] }, the same shape as GET /v1/connections/{id}/bank-accounts, for every bank. Use it to find what still needs mapping (mapping: unmapped) and what has rows waiting (staged_count).

Example

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

put /v1/bank-accounts/{id}

Say what a bank account is: one of your accounts, a new one, one of your loans, or don't sync

scope connections:write · mapBankAccount

Parameters

  • id path · string · required

  • budget_id query · string

Body · JSON

  • mapping "account" | "loan" | "ignored" · required

  • account_id string · ≤ 64 chars

  • loan_id string · ≤ 64 chars

  • sync_from string | null · YYYY-MM-DD

  • auto_land boolean

  • previous_account "keep" | "remove"

Returns · 200

The bank account after the write. mapping: account with account_id puts the bank on that account: a hand-kept one keeps its id, envelopes, loans and history; without account_id a new account is made from the bank's name, opening at the bank's balance the day before sync_from. mapping: loan with loan_id makes the loan follow the bank's balance and lands none of its transactions (the payments come from the paying account). mapping: ignored stages nothing more. sync_from (YYYY-MM-DD) is where the history you already have ends: earlier rows are suggested as skipped. auto_land: true lands new rows without review (off by default). When it stops feeding an account, previous_account: keep leaves that account kept by hand with every row; remove deletes it and its bank rows (refused if you entered rows there).

Example

curl -X PUT 'https://api.mintro.money/v1/bank-accounts/<id>' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "mapping": "account" }'

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.

get /v1/bank-accounts/{id}/rows

What the bank sent for this account, each staged row with a suggestion

scope connections:read · listBankRows

Parameters

  • id path · string · required

  • budget_id query · string

  • state query · string

Returns · 200

{ rows: [{ id, bank_account_id, date, amount_cents, currency, name, merchant_name, pending, state, transaction_id, suggestion, outcome }] }, oldest first. outcome is what the row will look like once it lands: { payee, payee_from: yours|rule, envelope, envelope_from: yours|rule, rule_id }. A matched row keeps the person's own payee and envelope, a new one takes its payee rule's. state defaults to staged. suggestion is { action: new }, { action: skip, reason: before_start }, or { action: match, transaction_id, name, date, amount_cents, sent_again }, meaning a row you typed or imported with the same cents within seven days, each claimed once. sent_again: true means the bank already sent this payment under another reference: a bank row on the same account with the same cents and words within three days. Banks re-issue rows. When the bank sends a row again and exactly one row held could be it, sync makes it that row by itself, with auto-land on or off: the row held keeps its envelope, note, payee and income and takes the bank's new reference and date, and GET /v1/bank-activity lists it as resent, which can be undone. So a sent_again row waits only when it is not clear: two rows held could be it, two waiting rows point at one row held, a row typed or imported could be it, or the human undid that take-over. Landing it as match keeps the row already held, while new adds it as a second payment. Only a mapped account gets suggestions. Show them to the human; POST .../land does what they decide.

Example

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

Land, match or skip staged bank rows, as the human decided

scope connections:write · landBankRows

Parameters

  • id path · string · required

  • budget_id query · string

Body · JSON

  • decisions object | object | object[] · required · ≤ 1000 items

Returns · 200

{ landed, matched, skipped, named, filed }. named/filed are what the person's payee rules did. Each decision names a bank_row_id and an action: new lands it as a transaction (unfiled, as any bank row); match with transaction_id makes it that row you entered: the bank's date, amount and cleared mark win, your envelopes and note stay; skip sets it aside (kept thirty days, then deleted). One database transaction; envelope totals recompute once. A row no longer staged refuses the whole call, so re-read.

Example

curl -X POST 'https://api.mintro.money/v1/bank-accounts/<id>/land' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "decisions": [ { "bank_row_id": "<bank_row_id>", "action": "new" } ] }'

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.

    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/bank-activity

What the bank and people did to each bank row, newest first, kept thirteen months

scope connections:read · listBankActivity

Parameters

  • budget_id query · string

  • account_id query · string

  • before query · string

  • limit query · integer · ≥ 1 · ≤ 100

  • kind query · string

  • q query · string

Returns · 200

{ activity: [{ id, kind, account_id, transaction_id, name, amount_cents, date, before, actor, created_at, undone_at, undone_by, undoable }], next }. kind: landed (a new row came into the budget), staged (it waits for review), resent (the bank sent a row again under a new reference, and it took over the row already held; sync does this by itself when only one row held could be it), withdrew (the bank took a posted row back; it is kept, still counts, and is marked), kept (the bank's balance still counts the withdrawn rows on that account to the cent, so sync took the marks off; their withdrew lines read undone by system; before is { bank_balance_cents, without_cents }, strings of cents: the bank's balance that vouched for the row, and what it would read had the bank taken the row back), dropped (a pending row the bank cancelled, unfiled, so it went), posted (pending became posted), changed (the bank corrected the amount, date or name; before is { amount_cents, date, name } as it was, amount as a string of cents), skipped (set aside in review), matched (made one with a row someone entered, or two bank rows merged). actor is { kind: system|person|agent, principal, name? }; system is Mintro's own sync. undoable says undo may still work. ?account_id= narrows to one account, ?kind= (comma separated) to some kinds, and ?q= to rows whose name holds the words; next is the id to pass as ?before= for the page after. limit is up to 200 (default 100).

Example

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

Undo one thing sync or a person did to a bank row

scope connections:write · undoBankActivity

Parameters

  • id path · string · required

  • budget_id query · string

Returns · 200

{ undone: { id, kind } }. landed: the transaction is removed (its envelopes and ready to assign with it) and what the bank sent waits in review again. resent: the row held gets back the reference, date and words it had, and the bank's new row waits in review; later syncs leave it waiting, since the human said it is a payment of its own. withdrew: the taken-back mark comes off; the human checked and the row is real. skipped: the row waits in review again. Anything else, or a second undo, is refused: edit, unfile or delete the row instead. Mintro keeps what the bank sent thirty days, so landed, resent and skipped cannot be undone after that. A reconciled row, or a row paying a loan, buying a holding or part of a transfer, is refused until that is undone first. Confirm with the human before undoing.

Example

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

  • 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/connections

Linked banks

scope connections:read · listConnections

Parameters

  • budget_id query · string

Returns · 200

{ connections[] }: id, institution, manual, status, last_synced_at, next_sync_at, disconnects_at, accounts. next_sync_at is when Mintro next asks the bank by itself (every six hours; the bank can push new rows sooner), null on the manual connection and while the bank wants a fresh sign-in or is closed. status is one of active; new_accounts (still syncing; the bank has accounts not shared yet); expiring (still syncing; Plaid says access ends within about a week); reauth_required (the bank wants a fresh sign-in; nothing syncs); revoked (access was withdrawn at Plaid or the bank); closed. Every state but active and closed is fixed by the same POST /v1/connections/{id}/relink. disconnects_at is set only on reauth_required and revoked: the day Mintro removes the bank at Plaid unless it is relinked first, thirty days after it last delivered; history stays. manual: true is the one virtual connection that holds hand-entered and imported accounts; it never counts against the plan's banks.

Example

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

Hosted link URL in update mode

scope connections:write · plan feature bank_sync · relinkConnection

Parameters

  • id path · string · required

  • budget_id query · string

Returns · 201

{ hosted_link_url, expires_at } for Plaid's update mode, with account selection on: one visit repairs a reauth_required or revoked bank, renews an expiring one, and shares new_accounts. Hand the URL to the human. When they finish, the connection returns to active, new accounts appear, and a sync runs (connection.linked is emitted).

Example

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

  • 503

    bank_linking_unavailable: Bank linking is not available on this instance. Have the human export transactions from their bank and use POST /v1/transactions/import instead.

delete /v1/connections/{id}

Remove the bank at Plaid, keeping your history or deleting it

scope connections:write · deleteConnection

Parameters

  • id path · string · required

  • budget_id query · string

  • unused_only query · string

  • history query · string

Returns · 200

{ closed: true, accounts_deleted }. The Plaid Item is removed (retried daily until Plaid confirms), so nothing syncs again; its access token is cleared, and what the bank sent that never reached a budget (staged rows, account names, masks and balances) is deleted. history=keep (the default): every account it fed stays, kept by hand, with every row and filing. history=delete: those accounts in this budget and every row in them are deleted too (other budgets keep theirs). With unused_only=true (what an agent sends) it is refused unless every bank account is ignored or unmapped and no account of yours is fed by it, because removing such a bank changes nothing in any budget.

Example

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

    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.