mono-ledger-sync
v0.4.0
Published
Local-first TypeScript app for syncing Monobank transactions into a private personal finance ledger.
Maintainers
Readme
mono-ledger-sync
Local-first TypeScript app for syncing Monobank transactions into a private personal finance ledger.
mono-ledger-sync is an early TypeScript app/package for building a local-first Monobank ledger workflow. The product direction is a local web app backed by a local API server and SQLite. The project is designed for people who want to own their financial data locally: tokens and transaction data stay on the user's machine, production sync talks to Monobank directly, and raw Monobank payloads are preserved separately from normalized ledger entries.
Status
This package ships a local-first Monobank personal finance workspace: a Fastify local app, SQLite-backed storage, a typed Monobank HTTP adapter, ledger queries, webhook hint recording, CSV/JSON exports, and a compact browser UI. The product talks to the real Monobank personal API at https://api.monobank.ua for a real user session. Sanitized JSON fixtures exist only to drive offline development, tests, and contributor onboarding; they are never used to render data for a real user.
Live by default
For a real user, every account, jar, statement, currency rate, and webhook comes from that user's own Monobank account via the personal API. The first-run flow leads with Sign in with Monobank: open https://api.monobank.ua/ to create a personal API token, paste it into the local app, and the local Fastify server validates the token with a live GET /personal/client-info before saving it. The product never sends the token to any server controlled by this project.
The Monobank personal API is for the user's own data on their own machine. Do not use this project as a hosted, team, or business service for other people's banking data.
MONOBANK_TOKEN is a CI-only escape hatch. It exists so the opt-in live smoke test (npm run test:live-monobank, gated by MONO_LEDGER_SYNC_LIVE_MONOBANK_TESTS=1) can call the real bank without going through the in-app paste-token flow. It is not part of the user-facing product flow and should not be set in a normal local dev shell.
Release notes
- v0.4.0: adds Parquet ledger exports, redacted SQLite snapshots, DuckDB-friendly BI views, local BI/accountant handoff docs, raw payload retention controls, and a mock-only Monobank provider/FOP spike behind an experimental Settings flag. Provider mode does not graduate; the personal-token local app remains the default path. Test count 239 pass / 0 fail / 4 skipped.
- v0.3.0: modularization of the local API server (
src/server/index.ts4419 → 1765 lines, 11 route modules undersrc/server/routes/) and the Vite web app (src/web/App.tsx11154 → 1111 lines, 10 per-route components undersrc/web/routes/, newsrc/web/api-types.ts). Adds manual recurring items, on-the-fly category-rule creation, ledger review states (migration0022), local cockpit workflow settings (migration0023), thelocal_exportsaudit log (migration0024), richer ledger export filters, anincludedInReportsaccount toggle, and transfer-aware cashflow and savings-rate reports. Release workflow now runsnpm run smoke:web(Playwright) as a gate. Test count 232 pass / 0 fail / 4 skipped. - v0.2.0: live-by-default sign-in flow, bulk edit, category version history, diagnostics endpoint, reporting suite, recurring-payments engine, and a long-running privacy/security hardening pass. 228 commits since
v0.1.1; test count 226 pass / 0 fail / 4 skipped. - v0.1.1: GitHub Release
v0.1.1;[email protected]on npm. Public discoverability metadata follow-up. - v0.1.0: Initial public package release.
[email protected]; initial commit5b1b6c2.
Goals
- Sync personal Monobank transactions into a durable local ledger.
- Keep banking tokens and personal finance data off hosted project servers.
- Support explicit fixture-backed development for tests and offline contributor workflows.
- Provide a small TypeScript API, local server boundary, and browser UI that can grow into SQLite storage, exports, reports, and a Vite web app.
Install
npm install mono-ledger-syncLocal UI
npm run devnpm run dev builds the package and starts the local Fastify server at
http://127.0.0.1:3000. The browser UI starts in Monobank API mode. Save a
personal API token in Settings, then run sync to fetch accounts, jars,
statements, and currency rates into the local SQLite ledger.
Export presets are available through the local API and browser UI for
accountant-handoff, monthly-personal-finance, bookkeeping,
budget-analysis, and raw-transaction-archive. CSV, JSON, JSONL, journal
CSV, Parquet, and redacted SQLite snapshot exports are deterministic for the
same database state and filters so users can diff or version their own local
data. See DuckDB workflow,
local BI, and
accountant handoff.
The in-app sign-in flow is the supported way to start syncing a real Monobank account: open http://127.0.0.1:3000, paste a personal API token from https://api.monobank.ua/, and the local server validates it before saving. MONOBANK_TOKEN is for the opt-in live smoke test only — see Live by default above.
Library API
import { createSyncPlan } from "mono-ledger-sync";
const plan = createSyncPlan({
profile: "default",
source: "monobank",
});Privacy model
- No hosted token relay.
- No default cloud storage.
- No hosted account is required for local browsing, local backups, or local exports.
- Personal API tokens are stored through OS secure storage when available, with a session-only fallback when no secure provider is available.
- Use personal Monobank API tokens only for your own data on your own machine; do not use this project as a hosted or shared service for other people's banking data.
- Webhook events should be treated as hints and reconciled through statement pulls.
- Logs and errors must redact tokens and sensitive financial identifiers.
- Secure token persistence should follow
docs/decisions/0008-secure-token-storage.md: use OS credential stores for packaged builds, keep SQLite out of token storage, and fall back to session-only handling when no secure provider is available. - Raw statement payload retention defaults to 90 days and can be set to
0to keep raw payloads indefinitely. See privacy and retention review.
Webhook endpoint safety
The local server exposes webhook settings in /api/app/config.webhook:
webhook.host: usually127.0.0.1webhook.port: local API portwebhook.path: one high-entropy per-instance path (for example/api/webhooks/monobank-ab12...)webhook.url: full URL to register in Monobank personal webhook settings
The default webhook.url is a loopback URL for the local app. It is useful for
local health checks, but Monobank cannot deliver webhooks to 127.0.0.1 from
outside your machine.
If you need live personal webhook delivery while developing locally:
- Start the local app with the intended port, then read the current
webhook.pathfrom the UI or/api/app/config. - Expose only that local port through a temporary HTTPS tunnel controlled by you.
- Register the tunnel origin plus the exact high-entropy
webhook.pathin Monobank personal webhook settings. - Keep the tunnel open only while you are actively using it, then remove the webhook URL from Monobank or stop the tunnel.
Do not bind the local API to a public interface unless passcode protection is enabled, reuse stale tunnel URLs, share the tunnel URL publicly, or put tokens in webhook URLs. The route path is an unguessable local receiver path, not an authentication system.
Webhook payloads are recorded as local hints and are reconciled through statement pulls before they affect the final ledger state.
Disclaimer
This project is a local data ownership tool, not financial, tax, accounting, or legal advice. Verify exported data before making financial decisions or sending records to an accountant.
Development
npm install
npm run dev
npm run typecheck
npm test
npm run test:live-monobank
npm run coverage
npm run formatnpm run dev starts the local Fastify app server on http://127.0.0.1:3000.
The app exposes the browser UI at /, health and configuration endpoints,
ledger summary/account/transaction endpoints, sync run endpoints, webhook
hint ingestion, and CSV/JSON/JSONL exports. The default product path is
live — the local server talks to https://api.monobank.ua once a personal
API token is saved in the in-app sign-in flow. Sanitized fixtures remain
available only through explicit development mode; pass
MONO_LEDGER_SYNC_SOURCE=fixture npm run dev to skip live calls.
Use MONO_LEDGER_SYNC_PORT=3001 npm run dev if port 3000 is already in use.
The local API binds to 127.0.0.1 by default. Binding to a non-loopback host
requires MONO_LEDGER_SYNC_ACCESS_PASSCODE; the server protects the browser UI
and API with Basic auth while keeping the high-entropy Monobank webhook receiver
available for delivery.
Use npm run web:dev when working on the Vite UI; it starts the same local API
server and proxies browser requests through http://127.0.0.1:5173.
npm run test:live-monobank is an opt-in smoke test for the real Monobank
adapter. It skips unless MONO_LEDGER_SYNC_LIVE_MONOBANK_TESTS=1 and
MONOBANK_TOKEN are set, so default local and pull-request validation never
calls the live API. This is the only supported use of MONOBANK_TOKEN.
The local API token endpoint stores saved Monobank tokens through the default token store. Linux uses Secret Service when available. macOS and Windows keep session-only handling until a packaged desktop host can bridge Keychain Services and Credential Manager without passing secrets through shell arguments. Unsupported or unavailable secure stores fall back to the running session instead of writing plaintext credentials to SQLite or config files.
Rotating a Monobank token
Rotate the personal API token from the local settings screen or the local API; do not edit SQLite, generated exports, or config files to change credentials.
- Create a replacement personal token in Monobank.
- Open Settings -> Monobank token, paste the replacement token, confirm the
local-only handling checkbox, and save it for the active local profile. The
same flow is available through
POST /api/app/tokenwith the activeprofileand replacementtoken. - Confirm the token status in settings. Persistent secure storage means the token survived through the OS credential store; session-only storage means it is available only until the local server process stops.
- Run a Monobank sync after saving the replacement token.
- Revoke the old token in Monobank after the replacement token works.
If a token may have been exposed, remove it from Settings -> Monobank token with the explicit deletion checkbox, revoke it in Monobank, and restart the local server if the UI reported session-only token handling.
Release automation is documented in docs/release.md. Domain contracts are documented in docs/domain-model.md. The privacy and security threat model is documented in docs/threat-model.md. Common local workflows are documented in examples/sample-workflows. Start with the minimum local product flow for the install, token, sync, review, categorization, and export path.
License
MIT
