Bank Statement Converter: PDF to CSV, Excel, QuickBooks & Xero avatar

Bank Statement Converter: PDF to CSV, Excel, QuickBooks & Xero

Pricing

Pay per event

Go to Apify Store
Bank Statement Converter: PDF to CSV, Excel, QuickBooks & Xero

Bank Statement Converter: PDF to CSV, Excel, QuickBooks & Xero

Convert bank and credit card statement PDFs and bank CSV exports into transactions: date, description, amount, running balance and currency. Every statement gets a balance check (opening + transactions = closing). Download CSV, Excel or JSON, plus QuickBooks, Xero and OFX files.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Michael Costa

Michael Costa

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Security

Limited permissions

Share

What does Bank Statement Converter do?

Bank Statement Converter turns bank statement PDFs and bank CSV exports into clean transactions: one row per transaction with the date, description, amount (in cents and as a decimal), running balance and currency. Every statement also gets a summary row with a balance check: opening balance + transactions = closing balance, passed or failed, and by how much.

Download the rows as CSV, Excel or JSON from the Output tab, or have each statement written as a QuickBooks Online CSV, a Xero CSV or an OFX file, ready to import. Statements that fail the balance check come back free, with the rows where the running balance stops adding up.

Input fields · API · Use it from Claude, ChatGPT or Cursor (MCP)

Jump to: Fields · Price · How to use · Input · Output · Balance check · What it reads · AI agents (MCP) · Limits · FAQ

Try it in one click: the input comes pre-filled with a sample statement (a 3-page checking account statement from an invented bank, 73 transactions). That's 1 statement, or $0.05 (1 × $0.05, plus $0.00005 for the run start). Then replace it with links to your own statements.

What data does Bank Statement Converter return?

FieldExampleNotes
rowTypetransactionstatement for a statement's summary row, transaction for each transaction.
date2025-01-03Transaction date, YYYY-MM-DD. postedDate too when the statement prints two dates.
descriptionONLINE TRANSFER TO SAVINGS XXXXXX9884 REF #IBXWDE4PUHAs printed, wrapped lines joined.
amountCents, amount-57225, -572.25Negative = money out (or a card charge), positive = money in (or a card payment).
balanceCents, balance147798, 1477.98The running balance printed on the row; null when the row shows none.
currencyUSDRead from the statement, or the Currency you set.
balanceCheckverifiedSummary rows: verified, mismatch or unavailable (see The balance check).
openingBalance, sumOfTransactions, closingBalance, difference3606.09, 3457.86, 7063.95, 0Summary rows; each also in cents (openingBalanceCents, ...).
badRows[]Summary rows: transactions where the running balance stops adding up.
institution, accountLast4, periodStart, periodEndCopperfield Trust, 1544, 2025-01-01, 2025-01-31As printed on a PDF statement; null when not printed.
exportFiles[{"format": "quickbooks", "key": "...", "url": "..."}]Summary rows: the accounting files written for the statement.
statementIdsample-statement-cd346ae9Links each transaction to its statement; the same on every run for the same link.

Each statement's summary row comes first, then its transactions. The full list of fields is under Output.

How much does it cost to convert a bank statement?

You pay per statement converted: $50.00 per 1,000 statements ($0.05 each, whatever the number of pages or transactions), plus $0.00005 each time a run starts. The rows, the summary and the QuickBooks, Xero and OFX files are included.

  • The example below: 1 statement = $0.05, plus the start fee.
  • A month, for example: a bookkeeper converting 30 clients' checking and card statements, 60 statements: 60 × $0.05 = $3.00.
  • Caps: Max statements per run in the input, and Maximum cost per run in the run options. The run stops cleanly at whichever comes first: a statement is only delivered if it can be paid for, and links past the limit aren't downloaded (they're listed in RUN_STATS as queriesSkipped).

Never charged: a statement that fails the balance check (its rows still come back, free, so you can see what's off), a CSV export with rows that couldn't be imported (each listed in rowErrors), a statement with no transactions, and links that fail: scanned PDFs, password-protected PDFs, files over Max pages per statement, web pages, missing files, or addresses the site's robots.txt disallows.

A PDF that holds several accounts (savings and checking on one credit-union statement) counts each account as one statement.

How to convert a bank statement PDF to CSV or Excel

  1. Open Bank Statement Converter and click Try for free (or Start if you're signed in).
  2. Put direct links to your statements in Statement URLs, one per line: PDFs as your bank sends them, or CSV exports from online banking.
  3. Optional: pick Also write accounting files (QuickBooks, Xero, OFX), set Date order for day-first dates, Currency for CSV files without a currency symbol, or Flip CSV signs for card exports.
  4. Click Start. The Transactions view of the Output tab shows the rows, the Statements (balance check) view the checks. Export as CSV, Excel or JSON; the accounting files are in the run's key-value store (Storage tab).

Example: the sample statement

The pre-filled input, with the accounting files switched on:

{"urls": ["https://whitehatharbor.com/statements/sample-statement.pdf"],
"exportFormats": ["quickbooks", "xero", "ofx"]}

It returned 1 summary row and 73 transactions. The summary row (real output from a local run on 2026-10-13; on Apify each file also gets its download url):

{
"rowType": "statement",
"statementId": "sample-statement-cd346ae9",
"fileName": "sample-statement.pdf",
"pageCount": 3,
"statementKind": "bank",
"institution": "Copperfield Trust",
"accountLast4": "1544",
"periodStart": "2025-01-01",
"periodEnd": "2025-01-31",
"currency": "USD",
"transactionCount": 73,
"openingBalanceCents": 360609,
"openingBalance": 3606.09,
"sumOfTransactionsCents": 345786,
"sumOfTransactions": 3457.86,
"computedClosingBalance": 7063.95,
"closingBalance": 7063.95,
"difference": 0,
"balanceCheck": "verified",
"balanceCheckPassed": true,
"badRows": [],
"charged": true,
"exportFiles": [
{"format": "quickbooks", "key": "sample-statement-cd346ae9-quickbooks.csv", "url": null},
{"format": "xero", "key": "sample-statement-cd346ae9-xero.csv", "url": null},
{"format": "ofx", "key": "sample-statement-cd346ae9.ofx", "url": null}
]
}

And its first transactions:

datedescriptionamountbalance
2025-01-01Check 1650-1149.462456.63
2025-01-01BRIGHTPATH ELECTRIC AUTOPAY WEB ID 437965622-237.182219.45
2025-01-02HEARTH GAS CO AUTOPAY WEB ID 312706058-169.222050.23
2025-01-03ONLINE TRANSFER TO SAVINGS XXXXXX9884 REF #IBXWDE4PUH-572.251477.98

The QuickBooks file starts Date,Description,Amount / 01/01/2025,Check 1650,-1149.46.

Input

FieldWhat it does
Statement URLs (urls)Direct links to statement PDFs or bank CSV exports, one per line (at most 1,000 per run).
Also write accounting files (exportFormats)quickbooks, xero and/or ofx: one file of each per statement, in the run's key-value store. Default none.
Date order (dateOrder)auto (default), MDY (month first, US) or DMY (day first, UK, EU, AU).
Statement type (statementType)auto (default), bank or card. Decides signs on PDFs and the OFX account type.
Currency (currency)A 3-letter code such as GBP, used instead of the one on the statement.
Flip CSV signs (invertCsvSigns)For CSV exports that show money out as positive numbers. Default off.
Max pages per statement (maxPagesPerStatement)Refuse longer PDFs, uncharged: 1 to 200, default 100.
Max statements per run (maxStatements)Stop after this many statements.
{
"urls": [
"https://example.com/statements/checking-2026-09.pdf",
"https://example.com/statements/card-export-2026-09.csv"
],
"exportFormats": ["quickbooks", "ofx"],
"dateOrder": "DMY",
"currency": "GBP",
"maxStatements": 50
}

Output

One summary row per statement, then one row per transaction, in one dataset. Fields a row type doesn't have are left out; fields the statement doesn't print are null.

A transaction row:

{
"rowType": "transaction",
"statementId": "sample-statement-cd346ae9",
"sourceUrl": "https://whitehatharbor.com/statements/sample-statement.pdf",
"fileName": "sample-statement.pdf",
"accountLast4": "1544",
"currency": "USD",
"index": 2,
"date": "2025-01-01",
"postedDate": null,
"description": "BRIGHTPATH ELECTRIC AUTOPAY WEB ID 437965622",
"amountCents": -23718,
"amount": -237.18,
"balanceCents": 221945,
"balance": 2219.45,
"checkNumber": null,
"flags": [],
"page": 1,
"line": 11
}

A summary row also has sourceUrl, fileType (pdf or csv), statementIndex and statementsInFile (for PDFs holding several accounts), accountName, computedClosingBalanceCents, differenceCents, dateOrder, warnings (what to double-check, e.g. a statement that prints no year) and rowErrors (CSV rows that couldn't be imported, with their row number).

Only the transactions: in the Output tab pick the Transactions view, or from the API add ?fields=date,description,amount,balance and filter rowType in your spreadsheet. The accounting files hold only the transactions.

The run's RUN_STATS record has every link with what it gave or why it failed (queries, failed ones also in queriesFailed), the links not read because a cap was reached (queriesSkipped), and counts of statements verified, mismatched and without a balance.

The balance check

The converter adds up every transaction and compares the result with the statement's own balances:

  • verified: opening balance + every transaction = closing balance, to the cent, and every running balance printed on a row follows from the row before. Nothing is missing or misread.
  • mismatch: they don't add up. difference says by how much, and badRows lists the transactions where the running balance stops agreeing (those rows also carry the balance-break flag). Usually a row was misread or the file is incomplete. Not charged; the rows still come back.
  • unavailable: the statement shows no balances to check against, as with most card CSV exports.

All money is handled as whole cents, never as floating-point numbers, so the check is exact. For CSV exports with a running-balance column, the opening balance is the first row's balance minus its amount.

What it reads, and what it doesn't

  • PDFs with a text layer, which is what banks and card issuers send and what online banking downloads. Several accounts in one PDF come back as separate statements. Statements that print no year use the statement period; MM/DD and DD/MM are told apart from the dates themselves, or set with Date order.
  • Bank CSV exports (also semicolon- and tab-separated, with comma decimals, UTF-16 or Windows-1252): one signed amount column, separate debit and credit columns, or an amount with a type column (debit/credit). The header row is found in the first 15 rows. Dates that fit both MM/DD and DD/MM are refused until you set Date order; nothing is guessed.
  • Not read: scanned or photographed statements (no OCR), password-protected PDFs, Excel files (export CSV instead), OFX/QFX/MT940/CAMT files, and links to a bank's login or viewer page.

The engine is tested on synthetic statements in 25 layout families (invented banks, people and merchants), not on every real bank's layout. In its blind tests no statement that passed the balance check had a wrong amount, date or row (0 of 327). When a statement reads wrong, the check is what tells you, and that statement isn't charged.

Run it on a schedule, or from your own code

  1. Save your input as a task (Save as a new task, top right of the actor page). Every run converts, and charges for, every statement you list, so a monthly schedule fits a link that's replaced each month.
  2. Collect results: download the dataset as JSON, CSV or Excel; fetch the latest run's results from the API (GET https://api.apify.com/v2/actor-tasks/<task id>/runs/last/dataset/items?status=SUCCEEDED&format=csv, with your API token); let a webhook tell your system when a run succeeds; or connect it to Make, Zapier or n8n through Apify's integrations.

Can I use Bank Statement Converter from an AI agent (MCP)?

Yes, through Apify's MCP server, from Claude, ChatGPT, Cursor or any other MCP client. Add this to your client's MCP configuration (or let the agent find it with the server's actor search); your client signs you in to Apify:

{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com?tools=humble-echidna/bank-statement-converter"
}
}
}

To use an Apify API token instead of signing in, add "headers": {"Authorization": "Bearer <APIFY_TOKEN>"} next to url.

The agent passes statement links, e.g. {"urls": ["https://example.com/statement.pdf"]}, and should read balanceCheck on the summary row before trusting the transactions.

Who it's for

Bookkeepers and accountants who get clients' statements as PDFs every month and need them in QuickBooks, Xero or a spreadsheet; lenders and underwriters checking income and spending from statements; developers and AI agents that need statement transactions as data, with a check they can rely on.

Why this one?

  • A balance check on every statement, to the cent, with the rows where it breaks, and no charge when it fails.
  • One flat price per statement, whatever its length (up to 200 pages): a 30-page business statement costs the same as a 2-page one.
  • QuickBooks Online, Xero and OFX files written by the same engine, not just a CSV to re-map yourself.
  • Exact money: amounts in whole cents as well as decimals, so totals never drift by a cent.
  • No guessing: ambiguous dates, rows without a direction and CSV rows that don't parse are reported, not quietly filled in or dropped.
  • Polite and safe. It identifies itself honestly (User-Agent HumbleEchidnaApify), follows each site's robots.txt, and only requests public web addresses on the standard ports (80 and 443).

Limits

  • No OCR: scanned statements are reported as such, not charged.
  • No password-protected PDFs: save an unprotected copy first.
  • At most 200 pages per PDF (100 by default) and 30 MB per file; at most 1,000 links per run.
  • One link is read at a time.
  • CSV exports without a currency symbol or code are read as USD unless you set Currency.
  • Bank and card statements only: not investment, loan or payroll statements.

Privacy and your files

Your files are downloaded and read inside your own run; the results go to your run's storage in your Apify account, which keeps them for your plan's data retention period. The converter sends your data nowhere else and keeps nothing between runs. Links you give must open without a login; for private statements, use a link that expires (a signed URL).

FAQ

Can it read scanned statements?

No. A PDF without a text layer is reported as scanned ("error": "Scanned" for its link in RUN_STATS) and not charged. Most banks' downloaded statements have a text layer; a scan or a phone photo doesn't.

Why does my statement fail the balance check?

Usually a row was read wrong or the PDF is incomplete (a page missing, two months in one file). badRows points at the first transactions where the running balance stops adding up, so you can compare them with the PDF. Failed statements aren't charged. If a statement from your bank fails every month, please open an issue (without the statement itself) saying which bank and what the rows look like.

My dates came out wrong, or a CSV was refused for its dates.

Set Date order to DMY for day-first dates (UK, Europe, Australia) or MDY for month-first (US). A CSV whose dates all fit both orders (every day 12 or lower) is refused until you choose, rather than guessed.

Something that used to work now fails. Why?

Banks change their statement layouts now and then. The balance check and the run log say which statement and which rows; please open an issue with the bank's name and what changed.

Yes: it converts files you provide, which you have the right to use. It doesn't log in to any bank or scrape any site; it only downloads the links you give it, following the site's robots.txt. Statements contain personal financial data: only convert statements you're entitled to handle.

Feedback and support

Found a bug, or need a field or a bank's layout that isn't read well? Open an issue on the Issues tab with the input you used (never attach a real statement; describe its layout instead).

Versions

Current version: 0.1. See the Changelog tab for what changed in each version.

Engine notes

For maintainers. The conversion is the Cleanrow engine (mms/statements/engine/src), copied unchanged into src/engine/ by npm run vendor-engine (origin and commit in src/engine/MANIFEST.json). Changes the actor would like upstream, not made here:

  • addCents isn't exported from the engine's index; the actor imports it from money.ts directly.
  • A CSV export has no StatementResult builder in the engine, so the actor assembles one (period from the dates, balances from the running-balance check). An engine statementFromCsv would keep hosts from each writing their own.
  • prepareCsv falls back to USD when a file names no currency and doesn't say it fell back, so the actor can't warn about it; a currencySource (column, symbol or default) on PreparedCsv would let it.
  • Parsing runs in a worker stopped after 120 s.