Bank Statement Import and Matching

Bank statements arrive four ways: CSV, BAI2, and OFX/QFX files, and a daily bank feed. The machine accepts a match only when it is exact to the cent and unique; a person decides everything else, and no match posts an entry. Connected to the ledger integrity scan.

13 min read Security & Infrastructure Published Mar 3, 2026

CommunityPay brings a bank's activity into the books four ways: a CSV file, a BAI2 file, an OFX or QFX file, and a daily bank feed. The machine matches a bank transaction to a posted entry only when the match is exact to the cent and no other entry fits. A person decides everything else. Reconciling a month clears the matched entries, and no match posts an entry. This page describes the pipeline from upload to reconciliation and its relation to the ledger integrity scan.

Sources

Source Arrives as Detection Notes
BAI2 .bai2, .bai file First non-empty line starts with 01, Commercial banking standard. Control totals, more than one hundred transaction type codes, integrity verification.
OFX/QFX .ofx, .qfx file OFXHEADER: or <OFX> in the first kilobyte Supports both OFX 1.x (SGML) and 2.x (XML). Credit card statements supported.
CSV .csv file The default when neither signature is found Delimiter and encoding auto-detected. Common header names map to columns automatically.
Bank feed Read from the bank once a day Not a file Posted transactions and the bank's live balance, assembled into one statement for each calendar month.

The format of a file is detected from its content; nobody selects it. BAI2 and OFX files are structured, so they parse directly. CSV files need a column-mapping step because layouts vary by bank. Each file is hashed when it is uploaded, and a file that was already imported is refused.

The Bank Feed

The bank feed reads the bank's posted transactions and balance once a day through the processor's bank-connection service. Connecting or disconnecting a feed takes bookkeeping authority in the association. The feed never posts an entry.

A feed is reconciled one calendar month at a time, in order. Each month has one statement, and an empty month still gets one, because it still has to be reconciled. A month is in one of four states:

  • Collecting: the month, or the date windows of its last lines, are still open.
  • Established: the completeness proof ties, and the continuity checks hold. The balances are derived and frozen at the first qualifying reading.
  • Not established: the proof cannot be made. The person types the balances at Reconcile.
  • Failed: the proof was computed and does not tie.

The completeness proof asks whether the feed holds every transaction since the last completed reconciliation. The bank's posted balance, less every feed transaction posted since that reconciliation, must equal that reconciliation's ending balance to the cent. A transaction the bank reports after its month was reconciled is carried into the earliest open statement with its true dates.

Importing a File

Step 1: Upload

On the Bank Statements page, choose Import Statement. Importing needs authority to keep the books.

Page title: Import Bank Statement — Step 1 of 3: Select bank account and upload statement file.

The person selects an active bank account and uploads a file. Accepted extensions: .csv, .bai2, .bai, .ofx, .qfx. Beginning and ending balances may be typed. A balance the file carries is used instead, and an OFX file carries no beginning balance.

Button: Upload and Continue

BAI2 and OFX files parse in the same step and go to review. A CSV file goes to Step 2.

Step 2: Map Columns (CSV only)

Page title: Map Columns — Step 2 of 3: Map CSV columns to transaction fields.

The page shows a preview of the detected headers and sample rows. The person maps each field with a dropdown:

Field Required Description
Transaction Date Yes The date of the bank transaction
Post Date No Settlement date, if different from the transaction date
Description Yes Transaction description or memo
Amount Conditional Single amount column (positive = deposit, negative = withdrawal)
Debit Column Conditional Separate debit amount column, as an alternative to a single Amount
Credit Column Conditional Separate credit amount column
Check Number No Check number for check transactions
Reference No Bank reference or confirmation number

Either Amount or Debit and Credit columns are required. If an Amount column is mapped, it is the one used.

Additional options:

  • Reverse sign convention: For banks that use positive for withdrawals and negative for deposits.
  • Date Format Override: An explicit date format, such as %m/%d/%Y. Leave blank for auto-detection.

A sidebar collects optional statement balances. A statement whose balances tie to its transactions is proven, which the machine requires before it matches anything on a file.

Button: Parse and Continue

Step 3: Review

Page heading: Review. The browser tab reads Review Matches for an imported file, and the month for a bank feed statement.

The page shows the statement's period and balances, and states whether the statement ties: its beginning balance plus its transactions must equal its ending balance. A statement with no balances cannot be checked. The person can enter the bank's balances instead, or the bank's balance at the end of one day.

Below that, each bank transaction that needs a decision is listed with one sentence saying why it is not matched, for example "No posted entry for $412.00 within 5 days." What a person can do with a line:

  • Match an exact entry, or mark Not this entry. The exact entries are posted entries that equal the line to the cent.
  • Match several entries, for one deposit of several checks: tick two or more entries whose amounts add up to the line exactly.
  • Show all uncleared entries on the account.
  • Record bank fee or Record interest. The page notes when the description reads like one, and asks before recording a second entry for an amount an entry already records. These write a decision. The entry is posted when the month is reconciled.
  • Exclude the line, and Re-include it later.
  • Undo a match, whether a person's or the machine's. A line holding a match accepts no other action until the match is undone.

Entries within fifty dollars of a line appear as context. A match must be exact, so none of them can be matched as it stands. A difference is recorded as its own entry first.

How a Line Is Matched

Matching has two parts, hints and the exact gate. Neither posts an entry.

Hints

When the review page renders, the matching engine offers each line the entries a person could match it to. It stores nothing and never labels a pair exact. It offers, in order:

  1. Exact entries. Posted entries whose net on the account equals the line to the cent, inside the account's date window, not cleared or claimed by another line, not one the person already rejected on this line, and not a reversal leg whose partner never reached the bank.
  2. Context. Entries within fifty dollars, and the vendor payment or trust ledger entry the line most likely is.
  3. A fee or interest pattern. Offered only when no exact entry exists and the sign fits: a fee is a withdrawal, and interest is a deposit.
  4. A split by the bank. A line that is part of one uncleared entry, where another undecided line makes up the rest.

The date window is the bank account's own setting. The default is five days.

The Exact Gate

The machine accepts a line only when all nine conditions hold. The rule is the same for every source:

  1. The statement is in review and not tied to a settled reconciliation.
  2. The line is open.
  3. No person has decided the line. A person's "no" ends automation for that line.
  4. The date window has closed. The machine waits until it has.
  5. The statement is proven. A feed month is proven when its state is established. A file is proven when its transactions tie to both of its balances, each taken from the file or typed by a person. A balance derived from a checkpoint does not prove a statement, because its own check would be circular.
  6. Exactly one eligible entry fits: the same amount to the cent, inside the window, not already cleared or claimed.
  7. That entry is dated on or before the statement's end.
  8. That entry is not a reversal leg. A person may match one. The machine never does.
  9. No other line wants the same entry.

The gate is pure: the same inputs give the same decisions. Each machine match records the nine results it passed, the candidates it considered, and the date it was evaluated. When a condition fails on a computed fact, a machine match is withdrawn. A withdrawal is never a person's "no". When the gate is withheld — the statement is not proven, or the feed is not connected — nothing is accepted and nothing is withdrawn.

The machine never accepts a bank fee or interest, a group of entries, a reversal leg, or a match with no entry. A database constraint confines a machine match to one existing entry at no difference with no entry created, so that the machine never posts is a database fact, not a convention.

Reconcile

Reconcile brings a statement's decided lines into the reconciliation for its period. A person presses it, and nothing calls it on its own. It needs authority to keep the books and to create reconciliations. In order:

  1. Every refusal is read first, so a refused Reconcile posts nothing: the period, the statement in review, the balances, an open overlap, and the order rule, which refuses a period on or before the latest settled one.
  2. Each bank fee or interest a person recorded, and no entry exists for yet, is posted through JournalEngine and the enforcement dispatcher. A fee debits bank charges and credits cash. Interest debits cash and credits interest income. The entry books to the bank account's own cash account and fund, dated inside the statement period, and is stamped on the match at once, so pressing again reuses it and never posts a second.
  3. The statement balances are resolved: the statement's own, or typed. A missing balance is never read as $0.00.
  4. The reconciliation for exactly that period is reused or created, and the statement is linked to it.
  5. Each accepted match is cleared. A machine match is re-proved first, and a computed failure withdraws it and skips it.
  6. Every applied machine match is re-proved. A failure withdraws it, which un-clears its items.
  7. The difference is refreshed. The statement stays in review until the reconciliation completes, so an undecided line is never frozen by Reconcile.

Pressing it again is the same call. Applied matches are skipped, items are flipped rather than duplicated, and created entries are reused. That is how a late decision reaches the reconciliation.

A bank fee entry receives the same enforcement decision as a manual journal entry. The bridge does not bypass any accounting control.

BAI2 Parser

The BAI2 parser handles the commercial banking standard:

  • Integrity verification: 6 checks — the transaction count in each account trailer; balance integrity per account (opening plus credits less debits equals closing); the group control total against the sum of its account trailers; and the account count and group count in the trailers. The file trailer's control total is read and reported, and it is not compared.
  • Type codes: A registry of more than one hundred BAI2 type codes with human-readable descriptions.
  • Classification: Each transaction is classified as a deposit, withdrawal, check, wire, ACH, fee, loan (6xx codes), or non-monetary item from its type code.
  • Value dates: Funds type V supplies the value date for accurate transaction dating.
  • Several accounts: One account is read from a file: the first in the file, unless a target account number is given.
  • Duplicate detection: A file that was already imported is caught by its hash. Within an import, each line carries a key hashed from its date, description, amount, and line number.

OFX/QFX Parser

The OFX parser handles both versions of the Open Financial Exchange standard:

  • OFX 1.x (SGML): Unclosed tags are closed before the file is parsed.
  • OFX 2.x (XML): Standard XML parsing.
  • Credit card support: Handles CCSTMTRS (credit card statement) response elements.
  • FITID: The Financial Institution Transaction ID is stored as the line's reference.
  • CURDEF extraction: Reads the currency designation from the statement header.
  • Date range: Extracts DTSTART and DTEND for the statement period bounds.

Connection to Integrity Scanning

Matching reads the ledger. The nightly ledger integrity scan checks it. The scan runs 13 automated checks on the general ledger:

Check What It Verifies
Balance Check Every journal entry has equal debits and credits
Orphaned Lines No journal entry lines exist without a parent entry
Enforcement Decision Every journal entry posted after the enforcement cutover has an associated enforcement decision
Fund Assignment Every journal entry has a fund assignment
Closed Period No entries posted to closed or locked fiscal periods without an override
Control Account Reconciliation GL control accounts (AR, AP, Loans) match subledger totals within $0.02 tolerance
Payment Receipt Every completed receipt has a GL posting, and every GL payment posting has a receipt
Subscription Invoice Every paid platform subscription invoice has its expense entry posted
Report Recompute The trial balance, balance sheet, accrual income statement, and general ledger endings recompute exactly from posted lines, and stored account balances match a full recompute
Decision Chain The enforcement decision log is an unbroken hash chain
Interfund Reserve cash has not paid an operating expense, budgeted reserve contributions have been transferred, and no statutory restoration deadline has passed
Vendor Payment Record Every posted bill-payment entry has exactly one vendor payment record, every executed record names its entry, a reversed entry's record is stamped with the reversal, a paid platform payout carries its Stripe payout id, and a payout unconfirmed for more than a day is reported

Viewing Scan Results

Navigation: Insights tab > Audit > Scans

The scan history page shows:

  • KPI cards: Latest status, total scans, last scan date, checks passed count, findings (critical, warning, info)
  • Run Scan button: Triggers an on-demand scan
  • Scan history table: Date, type, status, checks, findings by severity, duration, content hash

Status display:

  • GREEN: Plain text "Passed" (no badge)
  • YELLOW: Badge "Warnings"
  • RED: Badge "Failed"

Each scan creates an immutable IntegritySnapshot with a SHA-256 content hash. Clicking a scan row shows the detail page with its findings and the change since the previous scan.

Bank fee and interest journal entries created at Reconcile are included in the next integrity scan. They are verified like any other journal entry because they were posted through JournalEngine with full enforcement.


This describes pr

How CommunityPay Enforces This
  • Format auto-detected from file content signatures — nobody selects a format, and a file already imported is refused
  • The machine accepts a bank line only when the amount is exact to the cent, one entry fits, the date window has closed, and the statement's balances tie
  • A database constraint confines a machine match to one existing entry at no difference, so a machine match can never post an entry
  • A person's "no" is never erased, and a machine match whose conditions stop holding is withdrawn and re-proved at Reconcile
  • Bank fee and interest entries are posted through JournalEngine and the enforcement dispatcher, only when a person reconciles
  • BAI2 integrity verification: 6 checks covering transaction counts, balance integrity, the group control total, and the account and group counts
  • Every IntegritySnapshot is immutable with SHA-256 content hash — scan results cannot be altered after creation
Login