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.

The 41 bank statement field definitions, by category
CategoryFields
Bank and branchBank Name, Bank Address, Bank Phone, Bank Website, Branch Name, Branch Address
Account holderClient Name, Client Address, Client Phone
AccountAccount Number, Account Type, IBAN, SWIFT/BIC, Routing Number, Currency
Statement periodStatement Date, Statement Start Date, Statement End Date, Page Number
Balances and totalsStarting Balance, Ending Balance, Total Deposits, Total Withdrawals, Total Fees, Interest Earned, Interest Charged, Average Balance, Overdraft Limit
Table Item: every transactionTransaction Date, Transaction Posting Date, Transaction Description, Transaction Type, Transaction Reference, Running Balance
Table Item: split money in and outTransaction 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.

Excerpt: bank statement result
{
  "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.

docRefinery's strengths, applied to bank statements
StrengthOn a bank statement
Long files at scaleA 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 textEach transaction is a row of date, description and balance cells, with page and box citations on the cells read.
Your namesA Return as alias turns Transaction Deposit into credit or Running Balance into balance, matching the columns in your affordability model.
One formatStatements share a field and table shape with payslips and utility bills, so a loan file needs one parser.
Bank templates rememberedEach bank's layout is learned once, then recalled for every applicant who banks there.
Governed scopeThe organisation decides what may be extracted, with owner, admin and member roles, so account data isn't pulled by default.
Your own networkEnterprise runs in a private VPC or fully air-gapped on premises. That is a deployment capability, not a compliance certificate.
Many countries70+ 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

  1. SIX: what an IBAN check confirms

Related

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.