Cadres IT Operations & Infrastructure
Sheet KEY-10 Rev 2026.08
Start Trial

Sheet KEY-10 · Finance & CRM Manual

Bank Operations

Bank feeds, transaction import, matching rules and bank reconciliation.

Audience: Finance operators and controllers Focus: Bank feeds and reconciliation

Scope

How to connect bank feeds or import statements, match transactions to the ledger, build matching rules and complete a bank reconciliation.

Authority recovery and provider-state guidance

When Keystone cannot prove an older transaction’s legal entity and connection, the row is labeled Authority unavailable and remains non-matchable. Choose the row’s direct Recover Authority action; it opens the recovery form without opening a disabled Match action. Search the eligible business-labelled connections for that bank account, select the legal entity, and enter a specific reason. Recovery is one-time and audited; do not select a convenient current entity unless it is the transaction’s actual authority.

Connected feeds require a legal entity and bank account during setup. Keystone creates the internal ingestion authority automatically. Operators never need an ETL connection ID.

Provider-pending transactions are evidence only and cannot be matched or reconciled. Wait for the provider to post, remove, or reverse the item. A currency mismatch or missing currency blocks cursor advancement and leaves a visible feed recovery state; configure the correct account/entity authority rather than converting the amount manually.

Auto-match reports how many rows were processed, matched, and still require review. It applies only exact signed bank amounts with a confidence of at least 90 and a clear lead over another candidate. If work remains, use the visible Match, Classify, or Exclude recovery action. Reconciliation completion is safe to retry after an ambiguous response; Keystone returns the same completed result and prevents another active reconciliation for the same bank account.

Bank reconciliation and bank feed workflows.

Statement Import Recovery

Statement import history lists the ten most recent immutable upload attempts with source filename/type, original imported/skipped counts, exact persisted legal-entity/bank-GL/connection authority, and current matched, excluded, reconciled, and remaining-review totals. Duplicate-only attempts remain visible as Replay: no new rows. Choose Review exceptions to filter the transaction table to that exact batch and its unmatched rows, or View batch for a resolved population. Each unmatched row exposes named Match and Exclude controls. Classify a row when no journal candidate exists; exclusions require a retained reason. A batch is Resolved only when it has no unmatched rows. Reconciliation completion remains a separate controlled step and still requires zero difference.

Authority unavailable; review required on an older batch means migration could not prove one historical entity and connection. Keystone labels that evidence legacy_incomplete; selecting a current company does not rewrite it. Use the surrounding transaction and audit evidence for controlled review rather than treating missing authority as a clean or resolved claim.

A file Keystone refused because it could not read every row leaves no batch in this history, because nothing was imported. That refusal is reported in the upload panel at the time, row by row — see “When a row cannot be read” below.

The the relevant workflow workspace keeps imports, server-paged transactions, match/exclude/unmatch actions, reconciliation sessions, two-column evidence, drill-down, and structured recovery guidance in one routed workflow. A failed dependency remains visible and retryable instead of being represented as an empty bank state.

Reconciliation Completion Readiness

An in-progress reconciliation now shows its completion readiness beside the statement difference. Unmatched transactions are a separate blocker even when the statement-to-ledger difference is $0.00. Select the unmatched count to open Import & Match already filtered to the reconciliation’s bank account and statement dates. When every row is matched or excluded but the ledger difference remains nonzero, the blocker opens all transactions for that account and period so an incorrect exclusion can be restored and classified. The Complete action appears only when no unmatched transactions remain and the stored difference is within tolerance. A server-side completion rejection remains visible on the affected reconciliation row until it is resolved or dismissed by navigating to the blocker population.

Bank Reconciliation

Recover a Transaction With No Match Candidate

When Match finds no existing journal entry, the modal stays in the reconciliation workflow and opens the classification form. Select the legal entity and search the offset-account picker, confirm the posting date, enter a specific accounting memo, then choose Create Entry & Match. Keystone creates a balanced two-line journal entry and matches the bank transaction as one operation.

Deposits debit the selected bank account and credit the offset account. Withdrawals debit the offset account and credit the bank account. The posting date must belong to an open fiscal period for the selected entity. If it does not, Keystone preserves the unmatched transaction and identifies the period that must be created or opened before retrying.

In operator navigation this workspace is Bank Statements & Reconciliation. Use it for CSV, OFX, and QFX statement uploads, transaction matching, duplicate import recovery, and reconciliation sessions. Connected Bank Feeds owns provider synchronization. Provider Journal Imports owns provider-generated accounting journals before they post to the ledger.

Bank reconciliation ensures your GL matches your bank statements.

Statement lists and reconciliation sessions remain tenant-scoped, but every manual upload requires an explicit legal entity. Keystone stores that entity together with the selected GL bank account on the import connection so later matching and classification use durable authority rather than an implicit default.

Workflow

  1. Import bank transactions – upload CSV or OFX file
  2. Match transactions – review ranked journal-entry candidates and confirm the right match
  3. Start reconciliation session – specify period and statement balance
  4. Complete reconciliation – verify difference is zero

Import Bank Transactions

Upload a CSV or OFX/QFX file (multipart form upload, max 10 MB).

Form fields:

  • file (required) – the bank statement file
  • date_col – CSV column name for date (default: date)
  • description_col – CSV column name for description (default: description)
  • amount_col – CSV column for single amount (default: amount)
  • debit_col, credit_col – alternative: separate debit/credit columns
  • date_format – date parsing format (default: %Y-%m-%d)
  • force_import – retain CSV rows that match the fallback date/amount/description duplicate check (default: false); exact OFX/QFX transaction IDs are never duplicated

Current shipped UI note: the Bank Reconciliation page now exposes advanced CSV options directly in the routed workflow:

  • required legal-entity and GL bank-account selection; an unavailable entity directory blocks upload and provides Retry
  • custom date/description/amount column mapping
  • separate debit/credit column mode
  • force_import with duplicate-risk warning
  • an import summary showing imported rows, duplicates skipped, batch ID, and the persisted authority snapshot
  • durable attempt history showing original source/outcome evidence beside current exception progress

OFX/QFX uploads still ignore the CSV mapping fields, but they do honor force_import.

Supported formats:

  • CSV – configurable column mapping
  • OFX/QFX – auto-detected by file extension or content; handles both OFX 2.x (XML) and OFX 1.x (SGML)

Which CSV import to use

Keystone has two CSV statement paths and they behave differently. Pick deliberately.

| — | — | — | | Reuse | Retyped each upload | Persisted on the connection and offered again for a matching header row | | An unreadable row | Refuses the whole file with a 422 naming each refused row and why; nothing is imported | Refuses the whole file with a 422 naming the row and column; nothing is imported | | Duplicate handling | force_import toggle over the date/amount/description fallback check | Content fingerprint plus the ETL engine’s content identity |

Use the Connections wizard for a bank whose export you will import again — it maps once and prefills the mapping next time. Use this page for a one-off file where you want the row-level summary and the force_import control. Neither path can silently drop a row. The mapping step and its refusal contract are documented in

Returns import statistics, batch ID, status, and exact persisted authority. Duplicate detection is scoped to the signed-in tenant and selected bank account, so another tenant’s source identity or matching economic row can never suppress an import. OFX/QFX transaction IDs are durable provider identities. CSV and other rows receive a durable identity from the uploaded file fingerprint and source row. Keystone checks exact source identity first, then may link one matching economic candidate per incoming row on date, signed amount, and normalized description; authority_linked counts those cross-format identity links. Normalization folds letter case and collapses runs of whitespace and nothing more, so a bank that re-exports the same transaction with different spacing or casing still deduplicates, while any real difference in the text stays a distinct transaction. This means two overlapping statement files — for example rows 1-15 and rows 10-25 of the same month — import each shared transaction once.

A row that matches an existing transaction on account, date, and amount but not on normalized description is imported and reported under potential_duplicates in the import summary. This count flags near-duplicate transactions that warrant review — Keystone imported them as real money (not skipped) but recorded the mismatch so you can confirm the matches are intentional. This capacity rule preserves legitimate repeated equal transactions instead of allowing one old row to suppress every match.

When a row cannot be read

A statement is one financial document, so Keystone imports it whole or not at all. If any row of your file cannot be read, the upload is refused with a 422, nothing is imported, and the error panel under the upload area lists every refused row: its row number in your file and the reason. The selected account, legal entity and mapping options are preserved, so you correct the file or the mapping and choose the file again. Because nothing was imported, re-uploading the corrected file creates no duplicates.

Row numbers are your file’s own line numbers — the header is line 1 — so they point straight at the row in your spreadsheet. Blank lines do not shift them.

The usual reasons, and what to do:

Reason What to change
The header row has no column named X Fix the column name in Advanced CSV Options to match your file’s header exactly, including capitalization. Every column you map is checked — date, description, and the amount columns
The header row has more than one column named X Two columns share a name, or differ only by surrounding spaces. Keystone cannot tell which one holds the value, so give them distinct names in the export and upload it again
A row’s date is not in the stated format Change Date format to the format your bank exports, or correct the row
A row has no date, or no amount Correct or remove the row in the export
A row’s amount is not a number Correct the row; currency symbols and thousands separators are fine, other text is not
A row populates both the debit and the credit column with non-zero amounts The row’s direction is ambiguous. Correct the export; a 0.00 in the unused column is fine and means “no movement in that direction”
A row has more values than the header row has columns Usually an unescaped comma inside a description. Quote the field, or correct the row, and upload the file again
An OFX transaction has no or , or one that cannot be read Re-download the statement from your bank; the file is malformed

If more than 50 rows were refused, the panel lists the first 50 and says how many more there were. The refused count itself is always exact.

A mapping that does not fit the file at all — a column name your file does not have, two columns sharing a name, or no amount column — is reported once against the header row rather than once per row, and the panel says that none of the file’s rows could be read. Nothing is imported either way.

An import that succeeds reports total_in_file as your file’s own row count, and imported plus duplicates skipped add up to it.

List Bank Transactions

Returns a paged response:

Current shipped UI note: the Bank Reconciliation page now pages transactions from this server response directly instead of preloading the entire list into the browser. The routed workspace also exposes a bank-account filter and an explicit account column so multi-account review stays visible.

Choose a bank account

Every bank-account picker in the workspace — import, the transaction and session filters, new reconciliation, and auto-match — offers the same set: every active asset account in your chart, which is exactly what the bank endpoints accept. Accounts the system recognises as cash are listed first. Type to search; the search runs on the server, so a chart with hundreds of accounts is fully heading “Which GL accounts the bank account picker offers”.

If you previously could not find a bank account here, that is why: the picker used to show only accounts numbered 10xx.

Page through long lists

Transactions, reconciliation sessions, the drill-down, and the statement import history each have a rows-per-page selector and a “Go to page” box next to First/Prev/Next/Last, so you can reach any page directly instead of clicking through. The import history now shows every batch you have ever workspace” for the page sizes available and why they are capped where they are.

Turn a match into a rule

Once you have matched a transaction, the row offers Create rule from this match. It opens the matching-rules editor with that transaction already attached as the rule’s evidence, so you do not have to go and find it again.

It fills the form in — it does not save anything. Review the rule, then use the preview, which tells you how many transactions the rule would affect before you commit to it. That check matters: a rule built from one match will go on to act on transactions you have not seen.

The link appears only on matched rows, because only a match shows what the rule should be.

Match a Transaction

Link a bank transaction to a journal entry or source document.

Fields:

Before completing a reconciliation, Keystone rechecks every matched row against its persisted journal or allowed source. If a legacy row only says matched but its target is missing or invalid, completion identifies that transaction and remains blocked. Unmatch the row, then match or classify it against current evidence.

Review Match Candidates

Returns ranked journal-entry candidates for the selected bank transaction.

  • Candidates are limited to the same bank account, posted journal entries, and a short date-review window. Scores are review guidance, not permission to bypass the server’s entity and amount checks.
  • The routed Bank Reconciliation page uses this lookup to power the candidate picker in the Match modal.
  • If no candidate is returned, the operator still knows the transaction, bank account, and amount that need exception review.

Exclude a Transaction

Mark an unmatched transaction as excluded from reconciliation (e.g., personal transactions or duplicate provider rows). Matched transactions must be unmatched first so exclusion cannot leave contradictory journal linkage.

An exclusion remains reversible until its reconciliation is completed. Choose Restore on an excluded row to return it to unmatched, then match it to an existing journal or use Create Entry & Match for a real bank fee, interest item, or other ledger difference. Restore clears the exclusion reason and records the prior reason in audit evidence. Completed reconciliation evidence remains immutable and returns controlled-correction guidance.

Unmatch a Transaction

  • The transaction must currently be in “matched” status.
  • Unmatching is blocked if the transaction belongs to a completed reconciliation (returns 400).
  • Transactions in an in-progress reconciliation can be unmatched.
  • The routed Bank Reconciliation page shows an “Unmatch” button on every matched transaction row.

Auto-Match

Use the durable, entity-scoped run from the Bank Reconciliation workspace. It returns immediately, polls bounded progress, and exposes cancel/retry recovery; the removed synchronous endpoint is not a supported contract.

For CSV/OFX statement rows, Keystone also requires the journal to belong to the connection’s displayed legal entity and to contain an exact debit or credit on the displayed GL bank account. Each exact journal bank line provides one match unit. This permits separate bank lines in one balanced journal to reconcile their corresponding statement rows, but retries and duplicate statement rows cannot reuse an already-consumed line or create another journal entry.

The routed workspace retains that result after the toast disappears. A non-zero unmatched count is a visible exception outcome and directs the operator to Match or Exclude each remaining row before reconciliation completion. A zero unmatched count directs the operator to open the reconciliation session and verify its statement difference.

Start Reconciliation

Fields:

  • period_start (required)
  • period_end (required)
  • statement_balance_cents (required) – ending balance from your bank statement

Complete Reconciliation

Finalizes the reconciliation. Fails if:

  • Any unmatched transactions remain in the period (match or exclude them first)
  • The difference between statement balance and GL balance exceeds 1 cent

When the reconciliation completes successfully, the cleared transactions in that period are stamped with the reconciliation ID so the closed session can be reconstructed later.

List Reconciliations

Returns a paged response with items plus the full total count so the routed UI can page reconciliation history server-side.

Two-Column Reconciliation View

Returns the AS 2301-style two-column reconciliation view for one reconciliation:

  • book side: GL balance plus bank-fee, interest, and NSF adjustments
  • bank side: statement balance plus deposits-in-transit and outstanding-check adjustments

Auto-categorization note: all bank transaction creation points (CSV/OFX import, provider sync, API push ingest, ETL engine) now auto-populate transaction_category using core.utils.bank_categorizer.categorize_bank_transaction(). Categories are inferred from description patterns (bank fees, interest, NSF), check numbers (outstanding checks), amount sign (deposits in transit), and match status (cleared). For historical transactions without a category, the endpoint derives the category on-the-fly as a fallback.

Current routed UI note: the “Reconciliation Sessions” tab shows a Columns icon on each completed reconciliation. Clicking it opens a two-column modal with side-by-side cards for the Book Side (GL balance + bank fee/interest/NSF adjustments = adjusted book balance) and Bank Side (statement balance + deposits-in-transit - outstanding checks = adjusted bank balance). A green or red alert shows whether the adjusted balances match. A “View Transactions” button switches to the drill-down modal.

Reconciliation Transaction Drill-Down

Returns transactions stamped with a specific reconciliation, with filtering and pagination.

Query params:

  • skip, limit (max 200)

Current routed UI note: the “Reconciliation Sessions” tab shows an Eye icon on each completed reconciliation. Clicking it opens a drill-down modal with a category filter dropdown, a paginated transaction table (date, description, amount, category badge, status badge), and check-number display where applicable. A “Two-Column View” button switches to the AS 2301 view. Both modals cross-link to each other for seamless navigation.

Every bank-data page keeps the full ingestion path visible in the shell: Connections, Imports, Transform rules, Matching rules, Bank feed, and Reconcile. The active step is marked, and unauthorized steps are omitted. This is the canonical recovery path when an import, mapping, match, or reconciliation needs follow-up.

Bank Feeds

Bank feeds provide automated or semi-automated import of bank transactions from external sources.

Connection Types

Type Description
plaid First-party provider onboarding and sync
manual_import Manual CSV/OFX upload
api_push External system pushes transactions

Create a Bank Feed Connection

Fields:

  • name (required) – display name
  • institution_name (required) – bank name
  • account_number_masked – last 4 digits of account
  • sync_frequency_hours – default: 24

Connections start in pending_auth status.

Provider-connected feeds are no longer expected to be synced only by manual button press. Keystone now runs a scheduler-backed bank-feed cadence loop that checks sync_frequency_hours, respects retry backoff windows, and leaves the manual Sync Now action as an operator override or recovery tool.

Connection Statuses

Status Description
pending_auth Awaiting authentication setup
active Connected and syncing
inactive Deactivated
error Sync failed (check last_error)

Trigger Manual Sync

Only works on provider-connected active or error feeds.

Current shipped behavior: this action imports unseen provider transactions using the feed’s stored provider cursor, updates freshness metadata, and clears retry state on success. If the provider requires reauthorization or a transient sync error occurs, Keystone returns an explicit recovery state and, on transient failures, schedules the next retry window.

If Plaid reports a modification or removal for a matched or reconciled transaction, Keystone preserves the provider cursor and stores a structured controlled_transaction_conflict on the feed. The feed detail and failed sync response identify the provider transaction and recovery action. The issue remains visible until reauthorization completes or a later provider sync succeeds; those recovery paths clear the issue without disturbing the encrypted feed credential.

Check whether a bank feed is complete

Open Connected Bank Feeds and select the feed. The completeness panel answers one question: has anything the bank recorded failed to reach Keystone?

Before it can answer, you set a baseline — the point you agree the two sides are reconciled.

  1. Get the balance the bank currently reports, and note which balance it is: current, available, or ledger. This matters; picking the wrong one makes every later comparison meaningless.

  2. Enter that amount and basis and save the baseline.

Keystone records the difference between the bank’s balance and its own imported total. From then on, each completed sync re-checks that the difference has not changed.

What the answer means

Status What it tells you
Baseline required No baseline set yet. Nothing is being checked.
Consistent since baseline Nothing has gone missing since you set the baseline.
Divergent since baseline The bank and Keystone disagree. The panel shows the exact amount.
Unavailable Not measured — for example the basis or currency changed, or no observation has been recorded yet.

Read “consistent” precisely: it means nothing has been lost since the baseline. It is not a statement about history before that point, and Keystone will not pretend otherwise.

A divergence is a real amount of money. Common causes, in the order worth checking: a sync that failed part-way, a provider backfill that arrived while the connection was in error, or a transaction the provider later removed. If the cause is missing history, replay the feed (below). If you have investigated and the difference is explained, set a new baseline — that records your decision and means future divergence is genuinely new.

Pending transactions are excluded from this comparison. They have not settled and their amounts can still change, so counting them would show a difference that is not a gap.

Replay a bank feed’s history

Use this when transactions are missing because the feed moved past them — a sync that failed after the position advanced, or history the provider added later.

  1. On the feed, choose Replay history.
  2. Either give the provider position to replay from, or leave it empty to replay from the start of available history.

  3. Give a reason. It is recorded as audit evidence.

While a replay runs, the live feed keeps working normally from its own position. Nothing about the live feed changes until the replay finishes completely. If it fails part-way it pauses and keeps its place; if you cancel it, nothing it did is promoted and the feed is exactly where it was.

Only one replay can run per feed at a time, and only provider-connected feeds can replay. For a manual feed, re-upload the statement instead — duplicate rows are rejected automatically.

When a feed stops working, you will be told

A feed that has stopped delivering appears in your work queue. You do not have to go looking for it.

What you see What happened What to do
Needs authorization The bank revoked access. Nothing is being imported. Reconnect the feed.
Balance divergence The bank and Keystone disagree since your baseline. Check completeness, then replay if history is missing.
Never synced The feed is active but has never finished a first sync. Finish connecting it.
Stale The feed has gone half again as long as its own sync interval without succeeding. Check the provider connection; replay if transactions are missing.

A feed needing authorization also raises a notification, which resolves itself once you reconnect.