# Match Bank Transactions

`POST /banking/bank-transactions/match`

Group: Banking › Bank Transactions · Operation ID: `matchBankTransactions`

Match one or more bank transactions against one or more documents named by their sfdType and sfdId pair, reconciling them. The document amounts must total the bank transaction amounts exactly. A fund transfer is the one case whose transactions may sit on different bank accounts, and it yields one match per leg. Runs in one transaction: a refusal reconciles nothing. Every request must carry the Api-Version header.

Matches bank transactions against documents and reconciles them. Both sides are arrays, so this covers 1:1, 1:N, M:1 and M:N in one route — there is no separate bulk form and no transaction in the path.

**A document is named by the `sfdType` + `sfdId` pair, and both are required.** `sfdId` is only unique WITHIN a type — a bank entry and an outbound payment can carry the same id — so the pair is the key. `sfdId` is the document's own id (a UUID).

**The amounts must balance.** The documents must total exactly what the bank transactions total, otherwise `AMOUNT_MISMATCH` names both figures. Send several documents when one does not cover the transactions, and several `bankTransactionIds` to reconcile a group against one document set. A repeated id is counted once.

**Every transaction must share one bank account and legal entity**, except for a fund transfer (below) — otherwise `TRANSACTIONS_SPAN_ACCOUNTS`.

**A fund transfer is the one cross-account case.** Send the sending and receiving transactions together with the single `fundTransfer` document and nothing else. Because its legs sit on different accounts, this produces **one match per leg**, so `matches` carries two entries rather than one; the legs may even belong to different legal entities. A fund transfer cannot be mixed with other documents (`FUND_TRANSFER_MUST_BE_ALONE`) and covers at most two transactions (`TOO_MANY_FUND_TRANSFER_TRANSACTIONS`).

`journalEntryLineId` narrows to a single line of a document that reconciles line by line, such as an outbound payment or a manual journal entry. Bank and credit card entries always match as a whole document, so a line given for those is ignored.

Matching is reversible — `POST banking/bank-transactions/{id}/unmatch` on any member detaches the whole match.

Everything runs in one transaction: a refusal reconciles nothing.

[Open in the interactive reference](https://docs.api.lensing.ai/?op=matchBankTransactions)

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Api-Version` | header | `"v1"` | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. |
| `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. |

## Request body

Required, `application/json`: matchBankTransactionsInput.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `bankTransactionIds` | array of string | yes | Ids (UUIDs) of the bank transactions to reconcile. At least one is required. Every transaction must belong to the same bank account and legal entity, except when the document set is a single fund transfer, whose two legs sit on different accounts. A repeated id is counted once. |
| `documents` | array of object | yes | The documents to reconcile the transactions against. At least one is required, and their amounts must total the transaction amounts exactly. A 'fundTransfer' must be the only entry. |
| `documents[].sfdType` | string, one of `"outboundPayment"`, `"customerPayment"`, `"manualJournalEntry"`, `"bankEntryNew"`, `"creditCardEntry"`, `"fundTransfer"`, `"payroll"` | yes | Which kind of document this is. Both sfdType and sfdId are required: sfdId is only unique within a type, so the pair is the key. 'netZeroImpact' cannot be named by a caller — it is an internal construct the suggestion engine mints. |
| `documents[].sfdId` | string | yes | Id of the document itself (a UUID) — the outbound payment, journal entry, bank entry, credit card entry, fund transfer or payroll detail. |
| `documents[].journalEntryLineId` | string | no | [fk: JournalEntryApi.journalEntryLines.id] Narrows to a single line of a document that reconciles line by line, such as an outbound payment or a manual journal entry, given as the line's id (a UUID). Bank and credit card entries always match as a whole document and ignore this. |

Example:

```json
{
  "bankTransactionIds": [
    "9f2c6a10-7d34-4c8e-9b21-5a0f3e6d81b7"
  ],
  "documents": [
    {
      "sfdType": "bankEntryNew",
      "sfdId": "c41d0b58-2e97-4f6a-8d13-7b5e9c204af6"
    }
  ]
}
```

## Response

`200` Success

Under `data` (matchBankTransactionsOutput):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `matches` | array of object | yes | The match records created. Ordinarily one; a fund transfer yields one per leg, because its legs sit on different bank accounts and cannot share a match. |
| `matches[].matchId` | string | yes | Id of the match record (a UUID). |
| `matches[].status` | string | yes | Status the match settled at. One of 'matched', 'autoMatched', 'pending' — 'pending' when a document has no posted journal entry line yet. |
| `matches[].bankTransactionIds` | array of string | yes | Ids (UUIDs) of the bank transactions this particular match covers. |
| `matchedBankTransactionIds` | array of string | yes | Ids (UUIDs) of every bank transaction reconciled, in the order supplied, with repeats collapsed. |
| `matchedDocuments` | array of object | yes | The documents that were matched, echoed with the amount each contributed. |
| `matchedDocuments[].sfdType` | string | yes | Kind of the document, as supplied. |
| `matchedDocuments[].sfdId` | string | yes | Id of the document (a UUID), as supplied. |
| `matchedDocuments[].amount` | string | yes | Amount this document contributed to the match, as a bare decimal. |
| `matchedDocuments[].currency` | string | yes | Currency of the amount, as an ISO 4217 code. |

## Response headers

- `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`.

## Errors

| Code | Status |
| --- | --- |
| `NOT_FOUND` | 400 |
| `ALREADY_MATCHED` | 400 |
| `NO_DOCUMENTS` | 400 |
| `NO_TRANSACTIONS` | 400 |
| `INVALID_DOCUMENT_REFERENCE` | 400 |
| `DOCUMENT_NOT_MATCHABLE` | 400 |
| `MULTIPLE_FUND_TRANSFERS` | 400 |
| `FUND_TRANSFER_MUST_BE_ALONE` | 400 |
| `TOO_MANY_FUND_TRANSFER_TRANSACTIONS` | 400 |
| `ACCOUNT_NOT_IN_RECONCILIATION_BOOK` | 400 |
| `AMOUNT_MISMATCH` | 400 |
| `TRANSACTIONS_SPAN_ACCOUNTS` | 400 |
| `CURRENCY_MISMATCH` | 400 |
| `BEFORE_RECONCILIATION_START_DATE` | 400 |
| `IDEMPOTENCY_KEY_CONFLICT` | 409 |
| `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 |

## Statuses

- `400` — Invalid request or business failure. Codes: NOT_FOUND, ALREADY_MATCHED, NO_DOCUMENTS, NO_TRANSACTIONS, INVALID_DOCUMENT_REFERENCE, DOCUMENT_NOT_MATCHABLE, MULTIPLE_FUND_TRANSFERS, FUND_TRANSFER_MUST_BE_ALONE, TOO_MANY_FUND_TRANSFER_TRANSACTIONS, ACCOUNT_NOT_IN_RECONCILIATION_BOOK, AMOUNT_MISMATCH, TRANSACTIONS_SPAN_ACCOUNTS, CURRENCY_MISMATCH, BEFORE_RECONCILIATION_START_DATE
- `401` — Missing or invalid access token
- `403` — Not authorized
- `404` — Not found
- `409` — The Idempotency-Key is still being processed by another request.
- `422` — The Idempotency-Key was already used with a different request payload.
- `429` — Too many requests
- `500` — Internal server error
