{
  "markdown": "# Monarch Money (Node.js)\n\nNode.js/TypeScript library for accessing [Monarch Money](https://www.monarchmoney.com) data.\n\n> **Disclaimer:** This project is unofficial and not affiliated with Monarch Money.\n\n## Installation\n\n```bash\nnpm install @hakimelek/monarchmoney\n```\n\nRequires **Node.js 18+** (uses native `fetch` and `AbortSignal.timeout`).\n\n## Quick Start\n\n```ts\nimport {\n  MonarchMoney,\n  EmailOtpRequiredException,\n  RequireMFAException,\n} from \"@hakimelek/monarchmoney\";\n\nconst mm = new MonarchMoney();\n\ntry {\n  await mm.login(\"your@email.com\", \"password\");\n} catch (e) {\n  if (e instanceof EmailOtpRequiredException) {\n    // Monarch sent a verification code to your email\n    const code = await promptUser(\"Enter the code from your email:\");\n    await mm.submitEmailOtp(\"your@email.com\", \"password\", code);\n  } else if (e instanceof RequireMFAException) {\n    // TOTP-based MFA is enabled on the account\n    await mm.multiFactorAuthenticate(\"your@email.com\", \"password\", \"123456\");\n  }\n}\n\n// Fetch data — fully typed responses\nconst { accounts } = await mm.getAccounts();\nconsole.log(accounts[0].displayName, accounts[0].currentBalance);\n```\n\n## Authentication\n\nMonarch's API requires email verification (OTP) for new devices/sessions, even when MFA is disabled. The library handles this with distinct exception types so your app can respond appropriately.\n\n### Login with email OTP handling\n\n```ts\ntry {\n  await mm.login(email, password);\n} catch (e) {\n  if (e instanceof EmailOtpRequiredException) {\n    // A code was sent to the user's email — prompt them for it\n    const code = await yourApp.promptForEmailCode();\n    await mm.submitEmailOtp(email, password, code);\n  }\n}\n```\n\n### With MFA secret key (automatic TOTP)\n\n```ts\nawait mm.login(\"email\", \"password\", {\n  mfaSecretKey: \"YOUR_BASE32_SECRET\",\n});\n```\n\nThe MFA secret is the \"Two-factor text code\" from **Settings > Security > Enable MFA** in Monarch Money.\n\n### Session persistence & token reuse\n\nAfter a successful login (including email OTP), you can save the token to avoid re-authenticating on every run:\n\n```ts\n// Save token after login\nmm.saveSession(); // writes to .mm/mm_session.json (mode 0o600)\n\n// Next time, login() loads the saved session automatically\nawait mm.login(email, password); // uses saved token, no network call\n\n// Or pass the token directly (skip login entirely)\nconst mm = new MonarchMoney({ token: \"your-saved-token\" });\n```\n\n```ts\nmm.saveSession();          // save to disk\nmm.loadSession();          // load from disk\nmm.deleteSession();        // remove the file\nmm.setToken(\"...\");        // set token programmatically\n```\n\n### Interactive CLI\n\n```ts\nawait mm.interactiveLogin(); // prompts for email, password, email OTP or MFA code\n```\n\n## API\n\nAll methods return **typed responses**. Hover over any method in your editor for full JSDoc and type information.\n\n### Read Methods\n\n| Method | Returns | Description |\n|--------|---------|-------------|\n| `getAccounts()` | `GetAccountsResponse` | All linked accounts |\n| `getAccountTypeOptions()` | `GetAccountTypeOptionsResponse` | Available account types/subtypes |\n| `getRecentAccountBalances(startDate?)` | `GetRecentAccountBalancesResponse` | Daily balances (default: last 31 days) |\n| `getAccountSnapshotsByType(startDate, timeframe)` | `GetSnapshotsByAccountTypeResponse` | Snapshots by type (`\"year\"` / `\"month\"`) |\n| `getAggregateSnapshots(options?)` | `GetAggregateSnapshotsResponse` | Aggregate net value over time |\n| `getAccountHoldings(accountId)` | `GetAccountHoldingsResponse` | Securities in a brokerage account |\n| `getAccountHistory(accountId)` | `AccountHistorySnapshot[]` | Daily balance history |\n| `getInstitutions()` | `GetInstitutionsResponse` | Linked institutions |\n| `getBudgets(startDate?, endDate?)` | `GetBudgetsResponse` | Budgets with actuals (default: last month → next month) |\n| `getSubscriptionDetails()` | `GetSubscriptionDetailsResponse` | Plan status (trial, premium, etc.) |\n| `getTransactionsSummary()` | `GetTransactionsSummaryResponse` | Aggregate summary |\n| `getTransactions(options?)` | `GetTransactionsResponse` | Transactions with full filtering |\n| `getAllTransactions(options?)` | `Transaction[]` | All matching transactions (auto-paginates) |\n| `getTransactionPages(options?)` | `AsyncGenerator<Transaction[]>` | Async generator yielding pages |\n| `getTransactionCategories()` | `GetTransactionCategoriesResponse` | All categories |\n| `getTransactionCategoryGroups()` | `GetTransactionCategoryGroupsResponse` | Category groups |\n| `getTransactionDetails(id)` | typed response | Single transaction detail |\n| `getTransactionSplits(id)` | typed response | Splits for a transaction |\n| `getTransactionTags()` | `GetTransactionTagsResponse` | All tags |\n| `getCashflow(options?)` | `GetCashflowResponse` | Cashflow by category, group, merchant |\n| `getCashflowSummary(options?)` | `GetCashflowSummaryResponse` | Income, expense, savings, savings rate |\n| `getRecurringTransactions(start?, end?)` | `GetRecurringTransactionsResponse` | Upcoming recurring transactions |\n| `isAccountsRefreshComplete(ids?)` | `boolean` | Check refresh status |\n\n### Write Methods\n\n| Method | Returns | Description |\n|--------|---------|-------------|\n| `createManualAccount(params)` | `CreateManualAccountResponse` | Create manual account |\n| `updateAccount(id, updates)` | `UpdateAccountResponse` | Update account settings/balance |\n| `deleteAccount(id)` | `DeleteAccountResponse` | Delete account |\n| `requestAccountsRefresh(ids)` | `boolean` | Start refresh (non-blocking) |\n| `requestAccountsRefreshAndWait(opts?)` | `boolean` | Refresh and poll until done |\n| `createTransaction(params)` | `CreateTransactionResponse` | Create transaction |\n| `updateTransaction(id, updates)` | `UpdateTransactionResponse` | Update transaction |\n| `deleteTransaction(id)` | `boolean` | Delete transaction |\n| `updateTransactionSplits(id, splits)` | `UpdateTransactionSplitResponse` | Manage splits |\n| `createTransactionCategory(params)` | `CreateCategoryResponse` | Create category |\n| `deleteTransactionCategory(id, moveTo?)` | `boolean` | Delete category |\n| `deleteTransactionCategories(ids)` | `(boolean \\| Error)[]` | Bulk delete |\n| `createTransactionTag(name, color)` | `CreateTransactionTagResponse` | Create tag |\n| `setTransactionTags(txId, tagIds)` | `SetTransactionTagsResponse` | Set tags on transaction |\n| `setBudgetAmount(params)` | `SetBudgetAmountResponse` | Set/clear budget |\n| `uploadAccountBalanceHistory(id, csv)` | `void` | Upload balance history CSV |\n\n## Error Handling\n\n```ts\nimport {\n  MonarchMoneyError,          // base class for all errors\n  EmailOtpRequiredException,  // email verification code needed — call submitEmailOtp()\n  RequireMFAException,        // TOTP MFA required — call multiFactorAuthenticate()\n  LoginFailedException,       // bad credentials or auth error (includes .statusCode)\n  RequestFailedException,     // API/GraphQL failure (includes .statusCode, .graphQLErrors)\n} from \"@hakimelek/monarchmoney\";\n\ntry {\n  await mm.login(email, password);\n} catch (e) {\n  if (e instanceof EmailOtpRequiredException) {\n    // e.code === \"EMAIL_OTP_REQUIRED\"\n    // Prompt user for the code sent to their email\n    const code = await getCodeFromUser();\n    await mm.submitEmailOtp(email, password, code);\n  } else if (e instanceof RequireMFAException) {\n    // e.code === \"MFA_REQUIRED\"\n    // Prompt for TOTP code or use mfaSecretKey\n  } else if (e instanceof LoginFailedException) {\n    // e.code === \"LOGIN_FAILED\", e.statusCode\n    console.error(\"Login failed:\", e.message);\n  }\n}\n\ntry {\n  await mm.getAccounts();\n} catch (e) {\n  if (e instanceof RequestFailedException) {\n    console.error(e.statusCode);     // HTTP status, if applicable\n    console.error(e.graphQLErrors);  // GraphQL errors array, if applicable\n    console.error(e.code);           // \"HTTP_ERROR\" | \"REQUEST_FAILED\"\n  }\n}\n```\n\n## Configuration\n\n```ts\nconst mm = new MonarchMoney({\n  sessionFile: \".mm/mm_session.json\", // session file path\n  timeout: 10,                        // API timeout in seconds\n  token: \"pre-existing-token\",        // skip login\n  retry: {\n    maxRetries: 3,                    // retry on 429/5xx (default: 3, set 0 to disable)\n    baseDelayMs: 500,                 // base delay with exponential backoff + jitter\n  },\n  rateLimit: {\n    requestsPerSecond: 10,            // token-bucket throttle (default: 0 = unlimited)\n  },\n});\n\nmm.setTimeout(30); // change timeout later\n```\n\nRetry automatically handles transient failures (429 Too Many Requests, 500, 502, 503, 504) with exponential backoff and jitter. The `Retry-After` header is respected on 429 responses.\n\n## Auto-Pagination\n\n`getTransactions()` returns a single page. For large datasets, use the auto-pagination helpers:\n\n```ts\n// Async generator — yields one page at a time (memory-efficient)\nfor await (const page of mm.getTransactionPages({ startDate: \"2025-01-01\", endDate: \"2025-12-31\" })) {\n  for (const tx of page) {\n    console.log(tx.merchant?.name, tx.amount);\n  }\n}\n\n// Or collect everything into a flat array\nconst all = await mm.getAllTransactions({\n  startDate: \"2025-01-01\",\n  endDate: \"2025-12-31\",\n  pageSize: 100, // transactions per page (default: 100)\n});\nconsole.log(`${all.length} total transactions`);\n```\n\nBoth methods accept the same filter options as `getTransactions()` (date range, category, account, tags, etc.).\n\n## Refresh Progress\n\nTrack account refresh progress with the `onProgress` callback:\n\n```ts\nawait mm.requestAccountsRefreshAndWait({\n  timeout: 300,\n  delay: 10,\n  onProgress: ({ completed, total, elapsedMs }) => {\n    console.log(`${completed}/${total} accounts refreshed (${(elapsedMs / 1000).toFixed(0)}s)`);\n  },\n});\n```\n\n## MCP Server (AI Agent Integration)\n\nThis package includes a built-in [Model Context Protocol](https://modelcontextprotocol.io) server with **30 tools**, making your Monarch Money data accessible to AI assistants like Claude Desktop, Cursor, and any MCP-compatible client.\n\n### Setup\n\n1. Get your Monarch Money auth token by logging in with the library (see [Authentication](#authentication)) and saving `mm.token`.\n\n2. Add to your MCP client config (e.g. Claude Desktop `claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"monarch-money\": {\n      \"command\": \"npx\",\n      \"args\": [\"@hakimelek/monarchmoney\"],\n      \"env\": {\n        \"MONARCH_TOKEN\": \"your-token-here\"\n      }\n    }\n  }\n}\n```\n\nOr run it directly:\n\n```bash\nMONARCH_TOKEN=your-token npx @hakimelek/monarchmoney\n```\n\n### Available Tools\n\n**Read (18 tools):** `get_accounts`, `get_account_holdings`, `get_account_history`, `get_account_type_options`, `get_recent_account_balances`, `get_aggregate_snapshots`, `get_institutions`, `get_budgets`, `get_subscription_details`, `get_transactions`, `get_transactions_summary`, `get_transaction_details`, `get_transaction_categories`, `get_transaction_category_groups`, `get_transaction_tags`, `get_cashflow`, `get_cashflow_summary`, `get_recurring_transactions`\n\n**Write (12 tools):** `create_transaction`, `update_transaction`, `delete_transaction`, `create_manual_account`, `update_account`, `delete_account`, `refresh_accounts`, `is_refresh_complete`, `set_budget_amount`, `create_transaction_tag`, `set_transaction_tags`, `create_transaction_category`\n\nEvery tool has typed parameters with descriptions, so AI agents know exactly what arguments to pass.\n\n## Project Structure\n\n```\nsrc/\n  index.ts      — public exports\n  client.ts     — MonarchMoney class with all API methods\n  mcp.ts        — MCP server (30 tools for AI agents)\n  errors.ts     — error classes (MonarchMoneyError hierarchy)\n  endpoints.ts  — API URL constants\n  queries.ts    — all GraphQL query/mutation strings\n  types.ts      — TypeScript interfaces for all API responses\n```\n\n## Testing\n\n```bash\nnpm test              # run tests once\nnpm run test:watch    # run tests in watch mode\nnpm run test:coverage # run with coverage report\n```\n\nTests use [Vitest](https://vitest.dev) and do not require real API credentials (fetch is mocked where needed).\n\n**Test the API connection** (against the live api.monarch.com):\n\n```bash\nnpm run build\n\n# Login with email + password (will prompt for email OTP code if required)\nMONARCH_EMAIL=your@email.com MONARCH_PASSWORD=yourpassword npm run test:connection\n\n# Use a saved token (skips login)\nMONARCH_TOKEN=your-token npm run test:connection -- --token\n```\n\nSet these in a `.env` file for convenience (see `.env.example`).\n\n## FAQ\n\n**How do I use this if I login to Monarch via Google?**\n\nSet a password on your Monarch account at [Settings > Security](https://app.monarchmoney.com/settings/security), then use that password with this library.\n\n**Why does Monarch ask for an email code every time I login?**\n\nMonarch requires email verification for new/unrecognized devices. After login, save the session token with `mm.saveSession()` or store `mm.token` — subsequent runs will reuse it without re-authenticating.\n\n## How This Library Compares\n\nThere are several unofficial Monarch Money integrations. Here's how `@hakimelek/monarchmoney` stacks up.\n\n### Landscape\n\n| | **@hakimelek/monarchmoney** | **monarch-money-api** (pbassham) | **monarchmoney** (keithah) | **monarchmoney** (hammem) |\n|---|---|---|---|---|\n| **Platform** | Node.js / TypeScript | Node.js / JavaScript | Node.js / TypeScript | Python |\n| **npm weekly downloads** | — | ~440 | ~130 | N/A (pip: ~103K/mo) |\n| **Runtime deps** | **1** (speakeasy) | 5 | 7 | 3 |\n| **TypeScript types** | Full (every response) | None | Yes | N/A |\n| **Email OTP flow** | Yes | No | No | No |\n| **MFA / TOTP** | Yes | Yes | Yes | Yes |\n| **Session persistence** | Yes (0o600 perms) | Yes | Yes (AES-256) | Yes |\n| **Interactive CLI login** | Yes | Yes | Yes | Yes |\n| **HTTP client** | Native `fetch` | node-fetch | node-fetch + graphql-request | aiohttp |\n| **Error hierarchy** | 4 typed exceptions | Generic throws | Generic throws | 1 exception |\n| **Read methods** | 20 | 15 | ~20 | ~16 |\n| **Write methods** | 14 | 9 | ~12 | ~10 |\n| **Rate limiting** | Yes | No | Yes | No |\n| **Retry with backoff** | Yes | No | Yes | No |\n| **Auto-pagination** | Yes | No | No | No |\n| **Dual CJS + ESM** | Yes | No | Yes | No |\n| **Refresh progress events** | Yes | No | No | No |\n| **Built-in MCP server** | Yes (30 tools) | No | No | No |\n\n### Where this library wins\n\n**Minimal footprint.** One runtime dependency vs 5-7 in the JS/TS alternatives. Native `fetch` means zero HTTP polyfills on Node 18+.\n\n**Email OTP support.** Monarch now requires email verification for unrecognized devices, even when MFA is off. This is the only Node.js library that handles the full `EmailOtpRequiredException` → `submitEmailOtp()` flow. Without it, automated scripts break on first login from a new environment.\n\n**Typed everything.** Every API response has a dedicated TypeScript interface — 50+ exported types covering accounts, transactions, holdings, cashflow, budgets, recurring items, and mutations. The `monarch-money-api` package has no types at all.\n\n**Structured error handling.** Four distinct exception classes (`LoginFailedException`, `RequireMFAException`, `EmailOtpRequiredException`, `RequestFailedException`) with error codes and status codes. Competitors throw generic errors or strings.\n\n**Broader write coverage.** Includes `updateTransaction()`, `setBudgetAmount()`, `uploadAccountBalanceHistory()`, `getCashflow()`, `getCashflowSummary()`, and `getRecurringTransactions()` — all missing from `monarch-money-api`.\n\n**Clean, flat API.** One class, direct methods, no sub-objects or verbosity levels to learn. Import `MonarchMoney`, call methods, get typed results.\n\n## Contributing\n\nContributions welcome. Please ensure TypeScript compiles cleanly (`npm run build`) and tests pass (`npm test`).\n\n## License\n\nMIT\n",
  "bytes": 15837,
  "sha": "44d7d6bb36b5603a42cc1368df2e2f4fe2056665107fb579497846b0758e9915",
  "repo_slug": "hakimelek/monarchmoney-node",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_hakimelek_monarchmoney_56f88518/readme"
}