Versions follow MAJOR.MINOR.PATCH (src/version.ts); Apify shows MAJOR.MINOR from .actor/actor.json.
Every run logs its version and the engine commit, and records both in the RUN_STATS key-value record.
- Xero CSV: a bank CSV's check number that starts like a formula now gets an apostrophe, like the other text columns.
- Engine updated to the current Cleanrow engine, which also brings its CSV fixes: more currency codes and symbols read in
amounts, and a note on debit or credit columns that mix signs. No change to price.
- A run that returns nothing (every input bad, private, blocked or unreachable, or an empty input) now ends
SUCCEEDED with an empty dataset and the reason as its status message and in the log, instead of FAILED. Only an
unexpected error still fails the run.
RUN_STATS keeps the reason as before.
- No change to output fields or price.
- Engine re-vendored at 2d19e31: the account-number pattern no longer backtracks exponentially on a line of many
asterisks (such a PDF used to run into the 120 s parse limit and fail, uncharged; now it's read normally).
First release. A Node actor (the rest of the portfolio is Python): the conversion is the Cleanrow engine,
vendored unchanged from mms/statements/engine/src at 635a9b9 (src/engine/MANIFEST.json).
- Statement PDFs (with a text layer) and bank CSV exports from links in; one summary row per statement (the balance
check: opening + transactions = closing, verified/mismatch/unavailable, the difference, the rows where it breaks),
then one row per transaction (date, description, amount in cents and as a decimal, running balance, currency).
- PDFs with several accounts give one statement per account. CSV: signed, debit/credit and type-column exports,
any delimiter, comma decimals, UTF-16/Windows-1252; ambiguous dates refused until Date order is set.
- Optional QuickBooks Online CSV, Xero CSV and OFX files per statement in the key-value store, from the engine's
exporters.
- Charged per statement through a
statement-converted event, only for statements with transactions, every row
imported and a balance check that didn't fail. Max statements per run, and the maximum cost per run honoured: a
statement is delivered only if it can be paid for, and links past the limit aren't downloaded.
- Each file is parsed in a worker thread with a 120 s limit and a heap cap, so a PDF that sends the engine's
account-number pattern into exponential backtracking fails only itself (README "Engine notes"); PDFs over Max pages
per statement (100, at most 200) are refused before parsing.
- Downloads follow the repo's fetch rules (ACTOR-CHECKLIST.md 2b), in TypeScript since mms_common is Python: honest
User-Agent, robots.txt per origin, public addresses on ports 80/443 only on every redirect hop, the connection pinned
to the checked address, a 30 MB cap and a 120 s deadline per file.
- Sources and terms: the actor reads only files the user links to; no site is scraped and no login is used, so no
third-party API or terms apply beyond the hosting site's robots.txt, which is followed. The default input is a
synthetic sample statement on our own site (whitehatharbor.com/statements/sample-statement.pdf; robots.txt allows
all, read 2026-10-13).