Bank statement data extraction
Bank statement data extraction reads a statement PDF or scan and returns the account details, balances and every transaction as structured rows. docRefinery's bank statement OCR API covers 41 field definitions. Extracted cells carry a confidence score and their position on the page, and if the transaction table comes back shorter than the statement, the run is flagged instead of quietly passing.
Updated
Who reads bank statements, and why
Loan processors and underwriters are the heaviest users: they want balances, deposit history and statement dates for income and asset checks, across packets that arrive in a handful of formats. Onboarding analysts use the header alone, because a statement names a client and an address. Bookkeepers want the rows for reconciliation when a bank feed isn't available.
Those jobs need different depths, so an account can switch on only the fields a team asks for. Banks issue statements from a small number of templates: the applicant changes, the layout doesn't. That is the condition docRefinery depends on, and lending and mortgage fits it well.
Bank statement fields docRefinery extracts
There are 41 definitions, including the transaction table and its 12 columns. Deposits and withdrawals have columns of their own, each with a date and description, as well as a general transaction set, which suits statements that split money in and money out and those that share one column.
| Category | Fields |
|---|---|
| Bank and branch | Bank Name, Bank Address, Bank Phone, Bank Website, Branch Name, Branch Address |
| Account holder | Client Name, Client Address, Client Phone |
| Account | Account Number, Account Type, IBAN, SWIFT/BIC, Routing Number, Currency |
| Statement period | Statement Date, Statement Start Date, Statement End Date, Page Number |
| Balances and totals | Starting Balance, Ending Balance, Total Deposits, Total Withdrawals, Total Fees, Interest Earned, Interest Charged, Average Balance, Overdraft Limit |
| Table Item: every transaction | Transaction Date, Transaction Posting Date, Transaction Description, Transaction Type, Transaction Reference, Running Balance |
| Table Item: split money in and out | Transaction Deposit, Transaction Deposit Date, Transaction Deposit Description, Transaction Withdrawal, Transaction Withdrawal Date, Transaction Withdrawal Description |
Example bank statement JSON
A trimmed excerpt with invented values, showing what a short table looks like: this statement prints 48 rows and 46 were read, so the run waits for a person. Extract a document has the full response.
{
"document_type": "bank_statement",
"result": {
"fields": [
{ "alias": "Client Name", "text": "Alex Sample", "field_confidence": 0.97,
"location": { "page": 1, "bbox": [0.08, 0.14, 0.27, 0.16] } },
{ "alias": "Ending Balance", "text": "2,310.45", "field_confidence": 0.98,
"location": { "page": 1, "bbox": [0.72, 0.31, 0.86, 0.33] } },
{ "alias": "Overdraft Limit", "text": null, "location": null },
{ "alias": "Table Item", "table": [[
{ "alias": "Transaction Date", "text": "2026-09-03", "field_confidence": 0.96,
"location": { "page": 2, "bbox": [0.07, 0.21, 0.17, 0.23] } },
{ "alias": "Transaction Description", "text": "EXAMPLE PAYROLL LTD", "field_confidence": 0.94,
"location": { "page": 2, "bbox": [0.19, 0.21, 0.52, 0.23] } },
{ "alias": "Running Balance", "text": "3,120.45", "field_confidence": 0.95,
"location": { "page": 2, "bbox": [0.80, 0.21, 0.92, 0.23] } }
]] }
],
"extraction_approval": "human-approval",
"low_confidence_fields": [
{ "alias": "Table Item", "text": null, "threshold": null,
"reason": "rows_missing", "extracted_rows": 46, "document_rows": 48 }
]
}
}Why statements suit docRefinery
Statements are long, sensitive and repetitive, and that decides which strengths count. Here is what each one does for a statement desk.
| Strength | On a bank statement |
|---|---|
| Long files at scale | A multi-page statement is read to the last page, and several statements scanned into one PDF are segmented into the documents inside it. |
| Rows, not text | Each transaction is a row of date, description and balance cells, with page and box citations on the cells read. |
| Your names | A Return as alias turns Transaction Deposit into credit or Running Balance into balance, matching the columns in your affordability model. |
| One format | Statements share a field and table shape with payslips and utility bills, so a loan file needs one parser. |
| Bank templates remembered | Each bank's layout is learned once, then recalled for every applicant who banks there. |
| Governed scope | The organisation decides what may be extracted, with owner, admin and member roles, so account data isn't pulled by default. |
| Your own network | Enterprise runs in a private VPC or fully air-gapped on premises. That is a deployment capability, not a compliance certificate. |
| Many countries | 70+ languages and scripts are supported, which helps with foreign-currency accounts and overseas applicants. |
What makes bank statements hard to read
- Length. A busy business account runs to dozens of pages, and the table restarts on each one, sometimes with a balance carried forward that isn't a transaction.
- Sign conventions. Debits may be a separate column, a minus sign, brackets or a DR suffix. Misreading one flips the meaning of a row.
- Wrapped descriptions. One transaction often spans two or three printed lines, and those lines must not become extra rows.
- Two dates. Transaction and posting dates differ, and some rows print only one.
- Weak copies. Printed-and-rescanned statements lose thin rules and small type, exactly where the columns are.
- Formats by country. IBANs, routing numbers and decimal separators all vary. An IBAN check confirms form only, never that the account exists.
How low-confidence statement values are handled
A dropped row is the expensive failure here, because a clean-looking table hides it. Beyond per-cell scoring, docRefinery flags rows_missing with both counts, so an underwriter knows to look. Cells scored under their threshold go to the same review queue, marked with their table and row, and the reviewer can correct a value or add the missing row against the highlighted page. A balance not printed on the statement comes back null, never calculated. A workflow rule can state that starting balance plus deposits less withdrawals equals ending balance; a failure is flagged like any other.
Common questions
Can it extract transactions from a multi-page bank statement PDF?
Yes. A long statement is read across every page, transactions come back as table rows, and a cell's location names the page it came from. Several statements in one PDF are segmented into separate documents. If the table is shorter than the statement prints, the run is flagged rows_missing with both counts.
Does docRefinery detect altered or fake bank statements?
No. It reads what is printed and doesn't assess whether a statement is genuine. Treat its output as data to verify, using your own checks, before any lending decision. Forgery and fraud detection is research on our roadmap, not a product.
Can I get the transactions in a spreadsheet?
Yes. Results export as JSON, CSV or XLSX, and an export always reflects the data as it stands after a reviewer's corrections. CSV headers use your Return as aliases, so the sheet arrives with your column names.
Sources
Related
- Bank statement OCRFree bank statement OCR. Extract account details, balances and transactions from a PDF or scan as structured data, with no account or card needed.
- Use casesWhere document extraction fits best: accounts payable, lending, KYC, insurance claims, logistics, healthcare admin and contracts, wherever senders repeat.
- Confidence scoresWhat a confidence score in document extraction means, how to set thresholds per field, when to send values to human review, and why null beats a guess.
- Utility billExtract meters, consumption, charges and service address from electricity, gas and water bills. An OCR API with scores and page box citations on values.
- docRefinery vs Azure Document IntelligencedocRefinery vs Azure Document Intelligence: prebuilt and custom models against governed extraction with review. Containers, compliance and pricing.
- Extract a documentExtract data from an invoice with the docRefinery API: submit with curl, Python or JavaScript, poll the run, then read fields, tables and low-confidence flags.
Try it on your own documents.
Upload a real file and see each field with its confidence score, ready to check.
100 pages a month free. No credit card, no sales call.