# Run Bank Rules

`POST /banking/bank-rules/run`

Group: Banking › Bank Rules · Operation ID: `runBankRules`

Run every active bank rule in priority order and report the bank transactions they matched between them, then refresh match suggestions. Takes no input. A rule that cannot run is skipped and listed in skippedBankRules with the reason why, rather than failing the sweep. matchedCount 0 means every rule ran and nothing met its criteria, which is a success. Only one bank rule run executes at a time per tenant, across this route, the "Run bank rules" button, the periodic schedule and the matching dashboard; a request made while another run is in flight returns RUN_ALREADY_IN_PROGRESS and posts nothing — it does not queue, wait or partially run. Every request must carry the Api-Version header.

Runs every **active** bank rule, in priority order, and reports what they matched between them. No rule id, no body — this is the sweep the "Run bank rules" button and the periodic action trigger.

Each rule creates the entry its action object describes for every unmatched transaction on its watched accounts that meets its criteria, posts it, and auto-matches the transaction. Rules run lowest `priority` first, so an earlier rule claims a transaction a later one would also have matched. Afterwards match suggestions are refreshed, so transactions no rule claimed still get proposals.

**A rule that cannot run is skipped, not fatal.** It appears in `skippedBankRules` with a `reason` and `message` and the sweep continues — a rule missing criteria or an action object, or whose every mapped bank account has been deleted or is not ready, would otherwise abort the whole run and roll back the entries earlier rules had already posted. This is a deliberate difference from the UI, which stops at the first such rule. `reason` is `NO_ACCOUNTS_MAPPED`, `RULE_INCOMPLETE` or `RUN_FAILED`; read the rule with `GET banking/bank-rules/{id}` to see the rest of its state.

**`matchedCount: 0` is a success, not a failure** — every rule ran and nothing met its criteria. `matchedCount` is the true total; `matchedBankTransactionIds` is capped at 500, so a shorter array does not mean less work happened.

There is no per-rule run route — the UI has no single-rule run either, and the sweep is the only supported trigger. To run one rule alone, deactivate the others or narrow its criteria.

**One run at a time, tenant-wide.** Rules run serially and each re-queries the unmatched transactions, so two overlapping sweeps could both claim the same transaction and post two entries against it. Every trigger — this route, the "Run bank rules" button, the periodic schedule and the matching dashboard's account-scoped run — takes the same lock. A request that arrives while another run is in flight returns `RUN_ALREADY_IN_PROGRESS` and does nothing: no rule ran, nothing was posted, and retrying once the other run finishes is safe. It does not queue or wait, so poll rather than holding the request open. Note the status is **400, not 409** — read `diagnostics[].code` to tell this apart from a malformed request.

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

## 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. |

## Response

`200` Success

Under `data` (runBankRulesOutput):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `matchedCount` | number | yes | How many bank transactions this run matched across every active rule. Zero is a normal outcome. |
| `matchedBankTransactionIds` | array of string | yes | Ids (UUIDs) of the bank transactions matched, capped at 500. matchedCount is the true total, so a shorter array does not mean less work happened. |
| `skippedBankRules` | array of object | yes | Active rules that could not run and were skipped so one malformed rule does not abort the sweep. There is no per-rule run route, so each entry carries the reason: NO_ACCOUNTS_MAPPED, RULE_INCOMPLETE (missing criteria or not exactly one action object), or RUN_FAILED when the rule itself threw — its mapped bank accounts are all deleted or not ready. Read the rule with GET banking/bank-rules/:id to see what it needs. |
| `skippedBankRules[].bankRuleId` | string | yes | Id (UUID) of the skipped bank rule. |
| `skippedBankRules[].reason` | string | yes | Why it was skipped: NO_ACCOUNTS_MAPPED, RULE_INCOMPLETE or RUN_FAILED. |
| `skippedBankRules[].message` | string | yes | Human-readable explanation naming the rule and what it is missing. |

## 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 |
| --- | --- |
| `RUN_ALREADY_IN_PROGRESS` | 400 |
| `IDEMPOTENCY_KEY_CONFLICT` | 409 |
| `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 |

## Statuses

- `400` — Invalid request or business failure. Codes: RUN_ALREADY_IN_PROGRESS
- `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
