Repository navigation
Add balance history download and upload tools #7
Description
Summary
Monarch has REST endpoints for downloading and uploading account balance history as CSV. These are needed for loan servicer transfers, correcting stale balances, and maintaining accurate net worth history. Currently no SDK, MCP, or CLI support exists.
API Details
Download Balance History
POST https://api.monarch.com/download-balances/
Content-Type: application/json
Authorization: Token <token>
{"account_ids": ["<account_id>"]}
Returns CSV with columns: Date,Balance,Account
- One row per day from account creation through today
- Negative values for liabilities
- Account name as display name (not ID)
Upload Balance History
POST https://api.monarch.com/account-balance-history/upload/
Content-Type: multipart/form-data
Authorization: Token <token>
Parts:
files: <CSV file binary>
account_files_mapping: {"<filename>": "<account_id>"}
files_column_mapping: {"<filename>": {"date": 0, "balance": 1}}
Returns: {"session_key": "wrapper-upload-balance-history-session-...", "previews": []}
Then trigger processing via GraphQL:
mutation Web_ParseUploadBalanceHistorySession($input: ParseBalanceHistoryInput!) {
parseBalanceHistory(input: $input) {
uploadBalanceHistorySession {
sessionKey
status
}
}
}Variables: {"input": {"sessionKey": "<session_key>"}}
Then poll for completion:
query Web_GetUploadBalanceHistorySession($sessionKey: String!) {
uploadBalanceHistorySession(sessionKey: $sessionKey) {
sessionKey
status
}
}Poll until status is "completed". Typical completion time: < 2 seconds.
Reading Balance History (existing)
Common_AccountDetails_getAccount already returns snapshots — array of {date, signedBalance} objects. This is the read-only equivalent.
Behavior Notes (verified through testing)
| Behavior | Result |
|---|---|
| Upload replaces entire history | Yes — all previous snapshots are overwritten |
| Upload updates currentBalance | Yes — final row's balance becomes the account's currentBalance |
| Works on linked (non-manual) accounts | Yes |
| Works on manual accounts | Yes — verified |
| Creates any transactions | No — balance history and transactions are independent |
| Affects income/expense reports | No — only balance snapshots |
create_transaction(update_balance=true) on linked accounts |
Does NOT update balance — silently ignored |
Proposed Tools
download_balance_history(account_id) -> CSV string or list of snapshots
Returns the daily balance history for an account. Could return raw CSV or parsed list of {date, balance} objects.
upload_balance_history(account_id, snapshots)
Accepts a list of {date, balance} objects (or a CSV file path) and uploads as the account's balance history. Handles the full workflow: upload file → parse mutation → poll for completion.
WARNING in tool docs: This replaces the entire balance history. The tool should automatically download and cache the existing history before uploading, in case a rollback is needed.
CLI
monarch balances download <account_id> [-o file.csv]
monarch balances upload <account_id> <file.csv>
Use Cases
- Loan servicer transfers: Zero out old account from transfer date, populate new account from transfer date
- Correcting stale balances: When a linked account stops syncing, fill in correct balances from statements
- Manual account history: Import historical balances for manually-tracked accounts
- Balance migration: Move balance history between accounts (download from one, modify, upload to another)
Related
- bills-agent#5 — Loan accounting skill (primary consumer of these tools)
- Recurring stream retrieval misses liability streams, newly created merchants, and pending items — switch to aggregated query #5 — Recurring stream retrieval improvements
Activity
krisrowe commentedon May 25, 2026
Additional motivation — real-world drift case
Hit this exact gap in practice working with a manual liability account (~20 historical transactions, low-five-figure balance). The path through the dysfunction:
create_transactionwith the API's defaultupdate_balance=false— transactions registered in the audit trail but the account's balance field didn't move. This is the opposite of Monarch's UI default, where the "adjust balance" toggle is checked by default for transactions added via the UI.delete_transactionunconditionally reverses the transaction amount from the balance — regardless of how the original create was flagged. So the asymmetry (create-with-false is a no-op on balance vs delete-always-reverses) means a delete-then-recreate round trip on transactions originally created withupdate_balance=falseleaves accumulated balance drift in the account's balance field, even though the transaction-sum reconciles.Suggestion for the future
upload_balance_historytool docstring: explicitly recommend it as the canonical reset path after any delete+recreate cycle on a manual account — cheaper and cleaner than chasing residual drift with offset transactions. Worth mentioning alongside the loan-servicer-transfer and stale-balance-correction use cases already in the issue.Adding as additional context for prioritization, not raising urgency.
krisrowe commentedon May 30, 2026
Implemented in
9a30a22(upload safety interlock added inaea350a), released in0.5.0.download_balance_history(REST/download-balances/) andupload_balance_history(multipart upload → parse mutation → session poll). Upload is a full-history replace (no append), and setscurrentBalanceto the final row.download_balance_historyreturns ahistory_tokendigest of the current history;upload_balance_historyrequires it asexpected_token, re-reads the live history, and refuses (changing nothing) on mismatch. This guarantees the prior history was captured (so the replace is reversible) and unchanged. The replaced history is returned asprevious_snapshotsfor rollback.monarch balances download/uploadCLI (CLI reads current history, supports a pre-upload backup, and prompts before replacing).currentBalanceupdate, the token interlock (wrong/stale token rejected with no mutation), and round-trip rollback. README updated.Cloud-verified:
download_balance_historywas invoked end-to-end against the deployed HTTP MCP instance — snapshots returned andhistory_tokensurfaced. The token guard was additionally validated against the live API (a bad token is refused with no mutation). A live upload was not executed.