How privacy works
Last updated: November 1, 2026
Rowkeel converts bank statement PDFs inside your browser. The PDF file is not uploaded to our servers. This page explains how that works, how you can check it yourself, and lists every piece of data the site does send.
How local conversion works
- When you choose a file, the page reads it in your browser’s memory. Nothing is sent over the network at this point.
- A background process in the same tab extracts the text, finds the transaction table and checks the rows against the statement’s opening and closing balances.
- The result stays in the memory of that browser tab. When you close the tab, it is gone.
- Excel, CSV, QBO, OFX and Xero files are built in the browser and saved straight to your device. Export makes no network request.
There is no server-side file processing. Our servers do not receive the PDF, its password, its text, the transactions, amounts or balances.
Check it yourself
You do not have to take our word for it.
- Network tab. Open your browser’s developer tools (F12, or right-click and choose Inspect), go to the Network tab, then convert a statement. You will see small requests to our own domain (listed below) and no upload of the file. You can click each request and read its body.
- Go offline. Load the page, turn off Wi-Fi or mobile data, then convert a statement. Conversion and export still work, because everything they need is already in the browser.
What the browser enforces
Every page is served with a Content Security Policy (CSP) header. It tells the browser which servers the page may talk to:
default-src 'self'; connect-src 'self' https://cloudflareinsights.com;
script-src 'self' https://static.cloudflareinsights.com <hashes>;
worker-src 'self' blob:; style-src 'self' <hashes>; img-src 'self' data: blob:;
font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self';
frame-ancestors 'none'
In plain words: CSP blocks our pages from connecting to other domains, apart from our hosting provider’s cookieless page-view statistics. The browser blocks requests to other domains (data requests, beacons, images, fonts, scripts, forms), including any that third-party code in the page might attempt. You can read the header yourself in the Network tab (click the page request, then Response Headers).
CSP has limits, and we want you to know them:
- It does not block requests to our own domain. That is why the next section lists every field those requests contain. Our server checks each such request against a fixed list of allowed fields and rejects any request with an unknown field, a value outside the allowed list, or a body that is too large.
- It does not cover top-level navigation (clicking a link, opening a new window) or WebRTC.
Offline cache
The site keeps an offline copy of itself so the converter works without a connection. It stores only our own pages, scripts, styles and fonts in your browser’s cache, not your files, conversion results or the data requests listed below.
What the site does send
Free conversion limit
Before a conversion the page asks our server how many free statements you have left this calendar month (5 a month without an account, 15 when signed in), and after a successful conversion it reports it. A file over 30 pages counts as one statement for every 30 pages or part of them. A file that is not a bank statement does not count. The request contains:
pages: number of pages in the fileauthorization: your account token, only if you are signed in or bought Pass or Pro
To count statements per visitor without storing IP addresses, the server computes a one-way hash of your IP address combined with a random salt that changes every calendar month (UTC). The salt is deleted after the month ends, so last month’s hashes cannot be linked to this month’s or turned back into an IP address. The monthly counters are deleted the same way.
Automatic diagnostic report
When a conversion fails or is only partly successful, and for a random 2% of successful conversions, the page sends a diagnostic summary so we can find and fix conversion problems without asking you for your statement. It is built in your browser from an allow-list. It contains only these fields:
schemaV: format version of the fingerprintengineVersion: parser versionappVersion: website versionbankId: bank layout id from our public list (or "generic")profileVersion: version of that bank layoutoutcome: ok, degraded or failedrecon: balance check result: verified, mismatch or unverifiablecodes: up to 16 failure codes from a closed list, with small counters (capped at 999), page rotation, export format and number shapespages: page count as a range: 1, 2–5, 6–20 or 21+pageSize: A4, Letter, Legal or otherrotation: page rotation: 0, 90, 180 or 270itemsPerPage: how many pages fall into each text-density rangefontsCount: number of fonts, as a rangeheaderWords: column header words, only words from our public header dictionary (such as Date, Description, Balance)columnCount: number of columnscolumnX: column positions as a share of page width, rounded to 0.05dateShapes: date formats such as DD.MM.YYYY, from a closed listmoneyShapes: amount formats with every digit replaced by 9, for example -9.9 or 9,999.99decimalsHist: how many amounts have 0 to 4 decimals, as rangescurrencies: up to 4 currency codes from a closed listrowsFound: number of rows found, as a rangedatedLines: number of dated lines, as a rangeskipped: number of skipped rows, as a rangelowConfidence: number of low-confidence rows, as a rangehypothesis: which parser path was used: bank layout, generic, or bank layout then genericdetectScore: bank detection score (two decimals)secondScore: score of the second-best bank match (two decimals)order: whether rows run oldest-first or newest-firstbreakRowIndex: row number where the running balance first breaks (capped at 999)breakCount: number of balance breaks (capped at 999)deltaClass: type of balance difference: sign, missing row, digit or total onlydeltaBucket: order of magnitude of the difference, for example 10–99sampled: true when this was a random sample of a successful conversion
It does not contain:
- the PDF file or any part of its text
- amounts and balances
- names, addresses and transaction descriptions
- account, card or IBAN numbers, even masked
- file names and PDF passwords
- error messages and stack traces
To count how many different people hit the same problem on a given day, the server stores a short hash of your IP address with that day’s salt. Because the salt is deleted after a day, this cannot be linked across days or turned back into an IP address. It is sent only while “Help improve Rowkeel” is on: off by default in the European Economic Area and Switzerland, and off everywhere if your browser sends Do Not Track or Global Privacy Control, unless you turn it on; see the privacy policy.
“This row is wrong”
If you flag a row, the page sends only:
bankId: bank layout idprofileVersion: version of that bank layoutissue: one reason from a fixed list: wrong amount, wrong date, wrong sign, not a transaction, split rowappVersion: website version
No row number, text, date or amount is sent.
“Send page layout”
Only if you click it, see the preview and click Send a second time:
skeleton: the masked page layout you saw in the preview (first 3 pages at most): positions and sizes of text pieces, every letter replaced by a or A and every digit by 9, except header dictionary words and the bank namecode: which screen the report came from (for example "not a statement")appVersion: website versionbankName: bank name, only if you typed itcountry: country, only if you typed itemail: email address, only if you typed it, so we can tell you when the bank is supportedmarkup: beta banks only: for each line of the masked layout, its line number and whether you ticked it as a transaction (no text)issue: beta banks only: one problem type from a fixed list: missing rows, wrong amounts, wrong dates, wrong signbankId: beta banks only: bank layout idprofileVersion: beta banks only: version of that bank layout
Usage statistics
Anonymous product events (for example “a file was converted” or “the pricing page was viewed”) are sent without cookies or sign-in credentials. They have no user id and are turned off the same way as the diagnostic report, which also stops the page-view statistics script from loading. Each event holds at most:
ts_hour: date and hour (no minutes or seconds)event: event name, such as file_dropped, parse_ok, export or pricing_viewpage_type: type of page (home, bank, format)bank_id: bank layout idpages_bucket: page count as a rangestatus: balance check result or error codeformat: export formatplan: free, pass or proreason: failure code from the closed listproduct: product chosen at checkoutdisabled: whether an option was turned offlocale: site languagecountry: country code derived from the connection (two letters, not the IP address)
If you have an account
You only need an account to pay for Pass or Pro.
- Sign-in runs on a separate site,
app.rowkeel.com, through our authentication provider, which stores your email address and sign-in details. Its code does not load on the converter pages, so it has no access to your files. - Payments are handled by our payment provider, the merchant of record, which stores your payment details; we do not receive your card number.
- We store your plan and, for Pro, a conversion history with metadata only:
user_id: your account id from our sign-in providercreated_at: date and time of the conversionfile_name: file name (you can turn this off in account settings)bank_id: bank layout idpages: number of pagesstatus: balance check resultformat: export format
You can turn off file names, clear the history or delete your account in your account settings.
What we do not claim
We have no third-party security certification or audit. This page describes how the site is built; the privacy policy covers your rights and how long data is kept.
Reporting a problem
If you find a security issue, please write to support@rowkeel.com. We usually reply within one business day.