VRPlatformVRPlatform
Build a Product UI

Reconcile Bank Records

Explain suggestions, handle ambiguity, and apply confirmed matches

Last Updated: 2026-09-05

Version: 1.5

Reconciliation links imported BankRecords to balanced Transactions. The API can suggest an exact set of existing Transactions, suggest a saved BankRule, or apply an automatic BankRule that is explicitly configured for automatic execution.

Current Behavior At A Glance

QuestionBehavior
Why this set?Combined amount, currency, dates, ambiguity, and member details
Why this BankRule?Direction, account scope, match mode, and condition results
Which set is first?Fewest Transactions, then the smallest total date difference
Which matching rule wins?Highest priority wins for automatic execution
What if top automatic rules tie?Nothing posts; the BankRecord remains unmatched
Does a manager always confirm?Yes for suggest; no for one top automatic rule
Can one payout cover many bookings?Yes, with one or several Transactions

Read Running Balances

balance.current is the account balance after the BankRecord, in cents. balance.correction is the adjustment needed to match a supplied balance checkpoint. In chronological calculation order, the next closing balance equals the previous closing balance plus the next record's amount and correction. Inactive records contribute zero. Balance checkpoints precede ordinary records with the same timestamp.

For amounts of 50, 75, and 25 cents, a final checkpoint of 150 cents gives closing balances of 50, 125, and 150 cents. Records before the first checkpoint are reconstructed from that checkpoint using the later amounts.

Load Suggestions

Request matching Transactions and saved BankRules with the BankRecord:

curl --get '/bank-records' \
  --data-urlencode 'reconcileStatus=unpaid' \
  --data-urlencode 'includeMatchingTransaction=true' \
  --data-urlencode 'matchingTransactionDaysOffset=7' \
  --data-urlencode 'includeMatchingRules=true'

includeMatchingTransaction=true requests exact transaction-set suggestions. matchingTransactionDaysOffset sets the maximum date difference and defaults to seven days. Request includeMatchingRules=true when the response should also include saved BankRule suggestions.

Each BankRecord can return:

{
  "matchingTransactionSets": [],
  "matchingRules": []
}

Use sort=matched for records without a candidate first, or sort=-matched for records with a candidate first. The API evaluates the complete filtered set before pagination.

Explain A Transaction-Set Suggestion

Every matchingTransactionSets[] item is one exact reconciliation proposal. It contains the combined result, ranking evidence, and one to five eligible Transactions.

Read every suggested Transaction from the set's transactions[]. BankRecord responses do not return a separate list of single-Transaction suggestions.

Set fieldShow the manager
combinedAmountThe signed total of all member Transactions
currencyThe shared currency of the BankRecord and every member
dateEvidence.totalDateOffsetSum of the member date differences
dateEvidence.maxDateOffsetLargest member date difference
matchDetails.ambiguousAnother set has the same ranking evidence
transactions[]The accounting entries that would be reconciled together

Set-level matchDetails.reasons[] explains why the proposal is eligible:

ReasonPlain-language meaning
exactAmountThe combined signed amount equals the BankRecord amount
sameCurrencyThe BankRecord and all members use the same currency
allTransactionsEligibleEvery member is available for reconciliation
dateWithinWindowEvery member is inside the allowed date window

Each member includes its Transaction summary plus matchDetails, listings[], and reservations[].

Member fieldShow the manager
amountThe signed bank-account impact contributed by this member
matchDetails.matchingDateThe date used for the comparison
dateOffsetDays between the BankRecord and matching date
matchDetails.maxDateOffsetMaximum eligible difference
accountExisting bank account, or null when matching assigns the account
listings[]Properties referenced by Transaction lines
reservations[]Booking references represented by Transaction lines
matchDetails.ambiguousAnother candidate has the same date distance

matchDetails.reasons[] is a stable list of satisfied criteria:

ReasonPlain-language meaning
exactAmountThe Transaction's bank-account amount equals the BankRecord amount
sameBankAccountThe Transaction already uses this bank account
bankAccountAssignedOnMatchReconciliation will assign this bank account
dateWithinWindowThe matching date is inside the requested date window

A useful summary is:

Exact total of 1,250.00 USD from two eligible Transactions, covering booking ABC-123 at Ocean House and booking XYZ-456 at Lake House.

Build that sentence from the typed fields. Do not parse descriptions or infer additional reasons.

Matching Date

For a paid expense, matchingDate is normally its payment date. Other eligible Transactions normally use their Transaction date. This can make matchingDate different from the displayed accounting date.

Set Order And Ties

Transaction sets are ordered by:

  1. one-member sets before multi-member sets;
  2. fewest members;
  3. lowest dateEvidence.totalDateOffset; and
  4. sorted Transaction IDs for deterministic output.

When two sets have the same member count and total date offset, each has matchDetails.ambiguous=true. The ID tie-break only stabilizes the response; it is not a recommendation.

The manager should compare:

  • listing and reservation references;
  • matchingDate and dateOffset;
  • Transaction type, contact, description, and account; and
  • the complete Transaction detail when the summaries still look identical.

One Payout Covering Multiple Bookings

A transaction set can group booking Transactions from different months when their combined signed bank-account impact equals the BankRecord amount. Show the member Transactions with their listing and reservation context, then send all confirmed IDs to PUT /bank-records/{id} in reconciledTransactionIds.

BankRecord:                1,250.00
July booking Transaction:    700.00
August booking Transaction:  550.00
Selected impact:           1,250.00

Bounded, best-effort suggestion discovery evaluates the 20 closest eligible Transactions, returns at most 10 sets, and includes no more than five Transactions per set. Multi-Transaction sets use a maximum seven-day window. The reconciliation mutation validates the selected IDs and exact combined total again before it applies the match.

Bank Rules

A BankRule turns recurring bank descriptions or amounts into a proposed balanced Transaction. Rules support deposits and expenses.

What A Rule Matches

InputSupported behavior
DirectionDeposit or expense
Bank-account scopeAny bank account, or selected accountIds
Descriptioncontains, equals, or startsWith
Amount>, >=, =, <, or <= using integer cents
Multiple conditionsmatchMode=all or matchMode=any

matchingRules[].matchDetails explains the evaluation:

  • directionMatches shows whether the BankRecord direction is correct;
  • hasAccountScope shows whether the rule is restricted to accounts;
  • accountMatches shows whether this BankRecord is inside that scope;
  • matchMode says whether all or any conditions are required; and
  • conditions[] returns every configured condition with its matches result.

A client can summarize the result as:

“Stripe payout” matched this deposit account and both configured conditions. It proposes a deposit with a merchant-fee line and a remainder line.

Use preview.transaction to show exactly what the rule will create.

Suggest Or Apply Automatically

modeAuthorization and behavior
suggestShow the proposal and require manager confirmation
autoCreateAndMatchThe saved rule authorizes creation and reconciliation during ingest

There is no separate global auto-match switch. Authorization belongs to each saved rule.

Priority And Ambiguity

Rule suggestions are ordered by:

  1. priority, highest first;
  2. creation time, newest first;
  3. rule ID for deterministic output.

Rules with the same mode and priority have matchDetails.ambiguous=true. For suggestion rules, let the manager compare the per-condition results and proposed Transactions.

During ingest, the API applies the single matching autoCreateAndMatch rule at the highest priority. If several automatic rules share that priority:

  • no rule is selected;
  • no Transaction is created;
  • the BankRecord remains unmatched; and
  • the batch returns CONFLICT with priority and bankRuleIds in context.

This is a deliberate manager-review state, not a request to select the first rule in the array.

Allocate The Transaction

Line typeUse
flatA fixed cent amount
percentageA percentage in basis points
remainderThe amount left after fixed lines

Rules cannot mix flat and percentage lines. A fixed allocation requires one remainder line. A percentage allocation contains only percentage lines and must total 100%. Every line needs an account or supported assignment.

Use the deposit_merchantFee assignment for a merchant-fee percentage. The API gives that allocation the fee direction while keeping the complete Transaction balanced to the imported amount.

Expenses require vendorContactId; deposits do not accept it. Only expense rules support usePriorMonthEnd. Expense rule lines can select an applied-amount tax rate. See Transaction type constraints.

Preview, Save, And Run

  1. Preview an unsaved definition with POST /bank-rules/preview.
  2. Show every matching BankRecord and its proposed Transaction.
  3. Save the confirmed rule with POST /bank-rules.
  4. Dry-run selected records with POST /bank-rules/{id}/run?dryRun=true.
  5. Apply the same record IDs without dryRun.
{
  "name": "Allocate processor payout",
  "transactionType": "deposit",
  "mode": "suggest",
  "priority": 100,
  "matchMode": "all",
  "conditions": [
    {
      "type": "description",
      "operator": "contains",
      "value": "processor payout"
    }
  ],
  "accountIds": ["22222222-2222-4222-8222-222222222222"],
  "lines": [
    {
      "type": "flat",
      "amount": 250,
      "assignment": "deposit_merchantFee"
    },
    {
      "type": "remainder",
      "accountId": "44444444-4444-4444-8444-444444444444"
    }
  ]
}

Saved-rule runs return one outcome per BankRecord: applied, wouldApply, skipped, or failed. A rule version is applied at most once to the same BankRecord.

Apply Or Remove A Match

To reconcile one BankRecord to one or several Transactions, update the BankRecord's reconciledTransactionIds. To reconcile one normal Transaction to one BankRecord, the Transaction can instead be saved with the ID in payment.bankRecordIds. Normal Transactions cannot fan out to several BankRecords; explicit bank transfers can link their source and destination sides.

For PUT /bank-records/{id}, omit reconciledTransactionIds to leave the match unchanged. Send null or [] to remove every reconciliation link. Omit status or send null to leave status unchanged.

Dry-run the exact supported mutation before applying it. To remove the match, submit the remaining Transaction or BankRecord IDs rather than deleting accounting history.

Manual reconciliation has these cardinality rules:

  • One BankRecord can reconcile to multiple Transactions when their exact total matches the BankRecord.
  • A non-transfer Transaction cannot reconcile across multiple BankRecords.
  • One explicit bank-to-bank transfer Transaction has a source and destination bank journal leg. It can reconcile to one BankRecord on each account side.
  • Match each transfer side from its corresponding bank-account reconciliation screen. Matching one side does not match the other or remove the transfer from the other account's eligible Transactions.
  • The API does not impose a five-Transaction selection limit. The five-member bound applies only to automatic suggestion discovery.

POST /bank-records/actions/batch applies one reversible action to at most 500 distinct BankRecords. It supports unmatch, exclude, and reactivate, with the standard dryRun=true preflight. The request is atomic: every requested row is reported in input order, and one blocked row prevents all changes. There is no best-effort mode and no bulk-delete operation.

OperationEligibility and locksBalance and reconciliationDry runUndo
MatchActive, supported sourceBalance unchanged; links replacedYesUnmatch
UnmatchExisting recordBalance unchanged; links clearedYesMatch again
ExcludeExisting recordRemoved from active balance; links clearedYesReactivate
ReactivateInactive recordActive balance restored; stays unmatchedYesExclude
DeleteUnmatched CSV source; no delete lock reasonsPermanently removedYesNone

Exclude only rows that do not represent real cash movement, such as a feed artifact or balance snapshot. If a bank error and its reversal both cleared the trust account, record both cash events as manager Transactions and reconcile them even when they net to zero. A typical pair is a Deposit for the error and an Expense or negative Deposit for the reversal.

Match has single-record dry run. Unmatch, exclude, and reactivate have both single-record and atomic batch dry run. Books-closed and statement locks do not block reconciliation changes. Unmatch leaves Transaction payment state unchanged.

Delete has individual dry run only. Its lock.delete.reasons[] may contain notCsvSource, plaidSource, reconciled, balanceRecord, or bankRuleMatched.

Reconciliation locks amount, Transaction date, bank account, and line-count changes until the match is removed. Descriptions and other documented field-level edits can remain available. See Transaction editability.

Eligibility Boundaries

Each existing-Transaction suggestion requires an active eligible Transaction, an available reconciliation slot, a date inside the allowed window, and an exact impact on the selected bank account. Set suggestions combine eligible Transactions only when their aggregate impact exactly matches the BankRecord.

Eligible types include deposits, paid or unpaid expenses, opening accounts payable, payouts, and transfers when their account side is valid. Unpaid expenses and opening accounts payable can receive the BankRecord's account during matching.

API Reference

On this page