Bank Reconciliation
Bank reconciliation matches bank statement lines against journal entry lines to verify that the company's accounting records agree with the bank's records. The process supports automatic matching by amount and date, manual matching for complex cases, rule-based matching for recurring patterns, and the ability to create missing journal entries for unrecorded bank transactions.
Purpose
- Import bank statement lines (manually or from structured data)
- Automatically match statement lines to journal entry lines by amount and date
- Manually match remaining unmatched items
- Define reconciliation rules that auto-match based on description patterns
- Create journal entries for bank transactions not yet recorded in the GL
- Complete and approve reconciliations for audit trail
- Generate reconciliation summary reports
Entities
Bank Reconciliation
| Field | Type | Description |
|---|---|---|
id | integer | Primary key |
company_id | integer | Owning company |
bank_account_id | integer | Bank account being reconciled |
statement_date | date | Date of the bank statement |
period_start | date | Start of the reconciliation period |
period_end | date | End of the reconciliation period |
opening_balance | decimal(12,3) | Opening balance per bank statement |
closing_balance | decimal(12,3) | Closing balance per bank statement |
reconciled_balance | decimal(12,3)? | Reconciled balance after matching |
difference | decimal(12,3)? | Difference between book and bank |
status | enum | in_progress, completed, approved |
completed_at | datetime? | When marked complete |
approved_by | integer? | User who approved |
approved_at | datetime? | Approval timestamp |
notes | text? | Notes |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
Bank Statement (imported lines)
| Field | Type | Description |
|---|---|---|
id | integer | Primary key |
company_id | integer | Owning company |
bank_reconciliation_id | integer | Parent reconciliation |
reference | string? | Bank reference number |
date | date | Transaction date |
description | string? | Transaction description |
debit | decimal(12,3) | Debit amount (outflow) |
credit | decimal(12,3) | Credit amount (inflow) |
balance | decimal(12,3)? | Running balance |
match_status | enum | unmatched, matched, partially_matched |
Reconciliation Match
| Field | Type | Description |
|---|---|---|
id | integer | Primary key |
company_id | integer | Owning company |
bank_reconciliation_id | integer | Parent reconciliation |
bank_statement_id | integer | Matched statement line |
journal_entry_line_id | integer | Matched journal entry line |
match_method | enum | auto or manual |
matched_amount | decimal(12,3) | Amount matched |
notes | string? | Match notes |
Reconciliation Rule
| Field | Type | Description |
|---|---|---|
id | integer | Primary key |
company_id | integer | Owning company |
name | string | Rule name (English) |
name_ar | string? | Rule name (Arabic) |
description_pattern | string | Regex or substring to match statement descriptions |
account_id | integer | GL account to use when creating entries |
is_active | boolean | Whether the rule is active |
Relationships
API Endpoints
Bank Reconciliations
| Method | Path | Description |
|---|---|---|
GET | /api/accounting/bank-reconciliations | List reconciliations (paginated) |
POST | /api/accounting/bank-reconciliations | Create a new reconciliation |
GET | /api/accounting/bank-reconciliations/{id} | Get with statements and matches |
DELETE | /api/accounting/bank-reconciliations/{id} | Delete (only while in progress) |
POST | /api/accounting/bank-reconciliations/{id}/import | Import bank statement lines |
POST | /api/accounting/bank-reconciliations/{id}/auto-match | Run automatic matching (optional date_tolerance body param, default 5 days) |
POST | /api/accounting/bank-reconciliations/{id}/manual-match | Manually match a pair |
POST | /api/accounting/bank-reconciliations/{id}/unmatch | Unmatch a statement line (bank_statement_id) — returns it to unmatched |
POST | /api/accounting/bank-reconciliations/{id}/create-entry | Create journal entry for unmatched line |
POST | /api/accounting/bank-reconciliations/{id}/complete | Mark as completed |
POST | /api/accounting/bank-reconciliations/{id}/approve | Approve a completed reconciliation |
GET | /api/accounting/bank-reconciliations/{id}/report | Get reconciliation summary report |
Auto-match behaviour
Auto-match pairs a statement line with a journal entry line on exact amount within a date tolerance (± date_tolerance days, default 5). When the statement line carries a reference, candidates are first narrowed to journal entries with a matching reference or description. A line is auto-matched only when exactly one candidate remains — ambiguous ties are returned in ambiguous_count and left for manual matching, never resolved arbitrarily.
Statement integrity
On import, the statement must foot: opening_balance + Σ(debit − credit) has to equal closing_balance. An unbalanced upload is rejected with 422 and no rows are written.
Reconciliation Rules
| Method | Path | Description |
|---|---|---|
GET | /api/accounting/reconciliation-rules | List rules (paginated) |
POST | /api/accounting/reconciliation-rules | Create a rule |
GET | /api/accounting/reconciliation-rules/{id} | Get a rule |
PUT | /api/accounting/reconciliation-rules/{id} | Update a rule |
DELETE | /api/accounting/reconciliation-rules/{id} | Delete a rule |
State Diagram
Workflow
Request/Response Examples
POST /api/accounting/bank-reconciliations
Create a new reconciliation.
Request
{
"bank_account_id": 2,
"statement_date": "2026-01-31",
"period_start": "2026-01-01",
"period_end": "2026-01-31",
"opening_balance": "45000.000",
"closing_balance": "52300.000",
"notes": "January 2026 reconciliation"
}Response 201 Created
{
"data": {
"id": 4,
"company_id": 1,
"bank_account_id": 2,
"bank_account": {
"id": 2,
"name": "NBK Main Account"
},
"statement_date": "2026-01-31",
"period_start": "2026-01-01",
"period_end": "2026-01-31",
"opening_balance": "45000.000",
"closing_balance": "52300.000",
"reconciled_balance": null,
"difference": null,
"status": "in_progress",
"status_label": "In Progress",
"notes": "January 2026 reconciliation",
"created_at": "2026-02-05T09:00:00.000000Z"
}
}POST /api/accounting/bank-reconciliations/{id}/import
Import bank statement lines.
Request
{
"lines": [
{
"reference": "TRN-001",
"date": "2026-01-05",
"description": "Customer payment - Al Safat Trading",
"debit": "0.000",
"credit": "5000.000",
"balance": "50000.000"
},
{
"reference": "TRN-002",
"date": "2026-01-10",
"description": "Rent payment - January",
"debit": "1500.000",
"credit": "0.000",
"balance": "48500.000"
},
{
"reference": "TRN-003",
"date": "2026-01-15",
"description": "Bank fees",
"debit": "25.000",
"credit": "0.000",
"balance": "48475.000"
}
]
}Response 200 OK
{
"message": "Bank statement imported successfully"
}POST /api/accounting/bank-reconciliations/{id}/auto-match
Response 200 OK
{
"message": "Auto-match completed: 2 transactions matched",
"matched_count": 2
}POST /api/accounting/bank-reconciliations/{id}/manual-match
Request
{
"bank_statement_id": 7,
"journal_entry_line_id": 45,
"matched_amount": "25.000"
}Response 201 Created
{
"data": {
"id": 5,
"bank_reconciliation_id": 4,
"bank_statement_id": 7,
"journal_entry_line_id": 45,
"match_method": "manual",
"matched_amount": "25.000",
"notes": null
}
}POST /api/accounting/bank-reconciliations/{id}/create-entry
Create a journal entry for an unmatched bank fee.
Request
{
"bank_statement_id": 7,
"account_id": 52
}Response 201 Created
{
"data": {
"id": 115,
"entry_number": "JE-2026-0115",
"date": "2026-01-15",
"description": "Bank fees",
"status": "draft",
"lines": [
{ "account_id": 52, "debit": "25.000", "credit": "0.000" },
{ "account_id": 15, "debit": "0.000", "credit": "25.000" }
]
}
}GET /api/accounting/bank-reconciliations/{id}/report
Response 200 OK
{
"data": {
"reconciliation_id": 4,
"bank_account": "NBK Main Account",
"period": "2026-01-01 to 2026-01-31",
"opening_balance": "45000.000",
"closing_balance": "52300.000",
"total_matched": 3,
"total_unmatched": 0,
"reconciled_balance": "52300.000",
"difference": "0.000",
"status": "completed"
}
}Business Rules
| Rule | Description |
|---|---|
| One active reconciliation | Only one in-progress reconciliation per bank account at a time |
| Complete requires all matched | Cannot complete if any statement lines remain unmatched |
| Approve requires completed | Only completed reconciliations can be approved |
| Delete only in progress | Cannot delete a completed or approved reconciliation |
| Auto-match by amount+date | Automatic matching compares amounts and dates within a tolerance window |
| Manual match overrides | Manual matching overrides any previous auto-match for the same line |
| Rule-based matching | Reconciliation rules match statement descriptions to predefined GL accounts |
| Create entry fills gap | When no journal entry exists for a bank transaction, one can be created inline |
| Company scoped | All reconciliation data is filtered by the authenticated user's company |