Bank Statement Parser API: Extract Transactions and Balances as Structured JSON

Any bank’s statement format in — account details, balances, and the full transaction table out.

Bank statements are the hardest common document in business workflows, and the one most often parsed by hand. Lenders need them for underwriting, accountants for reconciliation, property managers for income verification, fintechs for anything the aggregators don’t cover — and every bank formats its statements differently, changes those formats without notice, and spreads transaction tables across many pages.

This guide covers what a statement parser needs to extract, why bank statements defeat template-based tools, how to validate extracted transactions mathematically, and how the API integration works end to end.

What a bank statement contains

Despite wildly different layouts, virtually every consumer and business bank statement carries the same information:

  • Account identity — institution name, account holder, masked account number, statement period start and end dates
  • Balance summary — opening balance, closing balance, and usually totals for deposits/credits and withdrawals/debits
  • The transaction table — one row per transaction with date, description, amount, and (at most banks) a running balance column
  • Section structure — many banks split the table into separate sections for deposits, checks, electronic withdrawals, and fees, each with its own subtotal
  • Noise you must survive — marketing inserts, overdraft notices, page headers repeating mid-table, and multi-account statements that restart the whole structure per account

The extraction schema

A production statement schema captures the summary once and the transactions as an array. This is the shape used in our lending use case:

FieldTypeRequiredNotes
bank_namestringYes
account_holderstringYes
account_number_last4stringYesStatements mask all but the last digits
statement_period_startdateYes
statement_period_enddateYes
opening_balancecurrencyYesAnchor for math validation
closing_balancecurrencyYesAnchor for math validation
total_depositscurrencyNoBank-stated credit total
total_withdrawalscurrencyNoBank-stated debit total
transactionsarrayYesArray of { date, description, amount, balance }

Why statements defeat templates — and what works instead

Invoice-style template parsers fail on bank statements for a structural reason: there is no standard. Chase, Bank of America, Wells Fargo, every credit union, and every neobank lay out the same information differently — different column orders, different section groupings, different date formats, negative amounts as minus signs or parentheses or a separate debit column. A template per bank means maintaining hundreds of templates that silently break on every redesign.

The failure modes are specific and repeatable: transaction tables that span pages with repeated headers mid-table, multi-line transaction descriptions that wrap into what looks like a new row, statement sections that restart numbering per account, and check images embedded between table sections.

Schema-first extraction flips the problem: instead of describing where data lives on each bank’s page, you describe what you want (the schema above), and the extraction maps any layout onto it. New bank, redesigned statement — the same schema slug on the shared endpoint, with no new template.

API integration

One API call per statement; credit use depends on page count, at one credit per 5-page block:

Request — POST /api/v1/extract

curl -X POST https://dokyumi.com/api/v1/extract \
  -H "Authorization: Bearer dk_live_your_api_key" \
  -F "file=@march-statement.pdf" \
  -F "schema=bank-statement-parser"

Response

{
  "id": "8f5416be-972d-48ac-99b1-5c8edfe2a738",
  "status": "completed",
  "request_id": "a2f59b9e-b8bd-4158-93b3-0cd190207471",
  "schema": "bank-statement-parser",
  "data": {
    "bank_name": "First Meridian Bank",
    "account_holder": "Hazel & Pine Cafe LLC",
    "account_number_last4": "4471",
    "statement_period_start": "2026-03-01",
    "statement_period_end": "2026-03-31",
    "opening_balance": 18240.55,
    "closing_balance": 21874.03,
    "total_deposits": 46110.20,
    "total_withdrawals": 42476.72,
    "transactions": [
      {
        "date": "2026-03-02",
        "description": "DEPOSIT - CARD SETTLEMENT 03/01",
        "amount": 1834.90,
        "balance": 20075.45
      },
      {
        "date": "2026-03-03",
        "description": "ACH DEBIT - SYSCO FOODS INV 88213",
        "amount": -1211.38,
        "balance": 18864.07
      }
    ]
  },
  "confidence": {
    "opening_balance": 0.99,
    "closing_balance": 0.99,
    "transactions": 0.94
  },
  "validation": {
    "valid": true,
    "errors": [],
    "low_confidence_fields": []
  },
  "meta": {
    "processing_time_ms": 6480,
    "page_count": 8,
    "credits_used": 2,
    "ocr_cached": false,
    "model": "anthropic/claude-sonnet-4"
  }
}

Note page_count: 8 — an eight-page statement is one submission costing 2 credits (one per 5 pages), not eight separately metered pages. That distinction is the whole cost model for statement-heavy workflows, because underwriting typically wants three months of statements per applicant.

Amounts are normalized to signed floats (credits positive, debits negative) regardless of how the bank printed them, and dates arrive as ISO 8601 no matter the source format.

Math validation: the statement parser’s superpower

Bank statements are the one document type where you can verify extraction correctness arithmetically. If opening_balance plus the sum of transaction amounts equals closing_balance, the transaction table is almost certainly complete and correct — a dropped row, a duplicated row, or a misread amount breaks the equation immediately. Run that check on every extraction; it costs one line of code.

Use the model-reported confidence map and validation details alongside arithmetic checks. When the model reports a score for the transactions array, apply the schema threshold; on any review result, inspect both validation errors and low-confidence fields before accepting totals.

What it costs

Open-ended per-page pricing punishes bank statements more than any other document. Dokyumi uses flat monthly credit tiers instead: each five-page block consumes one credit, so longer statements use a predictable number of credits.

At two credits per statement, an underwriting flow pulling 3 statements per applicant can process about 80 applicants/month within Starter's 500-credit allowance. The free tier's 25 credits are enough to benchmark representative bank formats before paying.

  • Free — $0/month: 25 extraction credits, 2 schemas. Enough to validate extraction quality on your real documents before paying anything.
  • Starter — $99/month: 500 extraction credits, 10 schemas, REST API and webhook delivery.
  • Growth — $499/month: 3,000 extraction credits, 50 schemas, 25 white-label upload portals.
  • Enterprise — quoted: custom volume, documents beyond the 50-page self-serve limit, unlimited schemas and portals, schemas built for you.
  • One credit covers a document of up to 5 pages. A 6–10 page document uses 2 credits, 11–15 uses 3, and so on up to the 50-page self-serve ceiling — no per-page metering and no overage billing.

Full details on the pricing page.

Bank Statement Parser FAQ

Which banks are supported?+
There is no per-bank layout template requirement. Define the statement fields you need, test representative files from the institutions and layouts in your workflow, and inspect validation errors and low-confidence fields before accepting the data.
How do multi-page transaction tables work?+
Supported multi-page PDFs can be sent in one request. Test transaction arrays that cross page boundaries on representative statements and route uncertain results to review. An 8-page statement is one API call and 2 credits.
Can I verify the extraction is complete?+
Yes, arithmetically: opening_balance + sum(transactions) should equal closing_balance, and the bank’s own stated deposit/withdrawal totals give you a second cross-check. This math validation catches dropped or misread rows deterministically.
Is this a Plaid alternative?+
Different job. Aggregators like Plaid connect to live accounts with the account holder’s credentials. A statement parser handles the documents themselves — the PDF the applicant uploads because the connection failed, the bank is unsupported, or your process is document-based (underwriting, audit, reconciliation). Many lending stacks use both.
What about sensitive account data?+
Your schema decides which account fields to capture. Documents are stored encrypted and OCR results are cached by file hash. Self-service deletion is not currently available; contact hello@dokyumi.com to request deletion and confirm the applicable scope and timing. See /security for the current controls and limitations.

Try the bank statement parser on your own documents.

25 free credits every month — one credit covers a document up to 5 pages, with no credit card required.