Runs 100% in your browser: your file is never uploaded

See what's quietly costing your business money, for free

Upload a transaction export (CSV, Excel, or a PDF bank/card statement) and get an instant breakdown of recurring subscriptions, duplicate tools, fee increases, and unusual charges. The analysis happens entirely on your own device. Nothing is sent anywhere.

Click to choose your file(s) or drag them here
CSV, Excel, or a text-based PDF statement. Drop in everything you have, multiple files are combined automatically.
Loading the in-browser analysis engine…

How this actually works, in detail

1
Your file never leaves your device. When you choose a file, your browser reads it locally using standard web APIs (the same ones for a CSV, an Excel file, or a PDF). The file's contents are handed directly to a program running inside this same browser tab. They are never attached to a network request, never uploaded to a server, and this page has no backend to receive them even if it wanted to.
2
The analysis itself is real Python code, run inside your browser. This page uses a project called Pyodide, which compiles the Python interpreter to WebAssembly so it can run client-side. Excel files are read with a library called SheetJS, and PDFs are read with Mozilla's pdf.js, both also running entirely in your browser. The same expense-analysis logic that would normally run on a server runs instead on your CPU, inside your browser tab, with no network access to your data at any point.
3
You can verify this yourself. Open your browser's developer tools (F12 or Cmd+Opt+I) and watch the Network tab while you upload a file. No request containing your file's data will appear. You can even disconnect from the internet entirely after this page has finished loading, then upload your file: it will still work, which is only possible if nothing is being sent out.
4
Nothing is stored, anywhere. There's no database, no account, no login, and no cookies tracking this session. If you refresh or close this tab, the results and your file are gone. There is nothing left over on any server because no server ever saw them.
5
There are two exceptions, both clearly bounded. Clicking "Generate My Report" sends the aggregated findings already shown on-screen (vendor names, categories, dollar totals, never your raw transaction rows) through a small server we control, to write up the explanations in plainer, less templated language. If that step doesn't succeed for any reason, the report falls back to the version already computed locally, so you always get a report either way. Separately, if a file's columns genuinely can't be identified by name or by their values, an optional "let the system take a closer look" step sends a small real sample (the header row plus 3 example rows) to figure out which column is which, since that specific task needs to see a few real values to work at all. Neither exception ever receives your full file, and "Email me this summary" stays entirely under your control too, it's just a pre-filled email you choose to send.
I'm technical: show me the actual mechanics
The analysis code is a plain, readable Python file (engine.py) loaded alongside this page. It mirrors the open logic of a CLI tool: it parses the transaction data, normalizes vendor names, categorizes spend, and runs a handful of rule-based detectors (recurring-charge detection via interval consistency, fee-creep via early/late period averages, duplicate-service detection by category overlap, and z-score anomaly detection). None of this code performs any network I/O. there are no fetch, XMLHttpRequest, or Python socket/requests calls anywhere in it. Pyodide, pdf.js, and SheetJS each only make network requests once, on initial page load, to fetch their own runtime/library files, never your data. For Excel files, SheetJS converts the workbook to a CSV-like table directly. PDFs have no real rows or columns, just text positioned on a page, so pdf.js extracts each text fragment's position and the page reconstructs a table by grouping fragments into rows and columns based on their coordinates, then hands that to the same Python pipeline as a normal CSV upload. This works well for typical text-based bank/card statement PDFs; it can't read a scanned image with no underlying text layer, since there's nothing to extract.