> ## Documentation Index
> Fetch the complete documentation index at: https://breadbox-mintlify-7401d007.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Rule read MCP reference

> MCP read tools for listing, filtering, previewing, and finding coverage for transaction rules: list_transaction_rules, query_transaction_rules, preview_rule, find_matching_rules. Rule writes live on a separate page.

Transaction rules are condition trees that fire during sync to auto-categorize, tag, or annotate transactions. These read tools let agents inspect the existing rule set and dry-run conditions before creating new rules.

See [Rule writes](/mcp/reference/rules-write) for `create_transaction_rule` (single or batch via the `rules` array), `update_transaction_rule`, `delete_transaction_rule`, and `apply_rules`.

***

## list\_transaction\_rules

Lists transaction rules with optional filters. Agents should always call this before creating new rules to avoid duplicates. Returns each rule's actions, trigger, priority, hit count, and last-hit timestamp — useful for spotting stale or never-matching rules.

**Scope:** Read

**Mirrors:** [`GET /api/v1/rules`](/api/overview)

### Parameters

<ParamField path="category_slug" type="string">
  Filter to rules that set this category.
</ParamField>

<ParamField path="enabled" type="boolean">
  `true` for only enabled, `false` for only disabled, omit for both.
</ParamField>

<ParamField path="search" type="string">
  Search by rule name. Comma-separated values are ORed.
</ParamField>

<ParamField path="search_mode" type="string" default="contains">
  `contains`, `words`, or `fuzzy`.
</ParamField>

<ParamField path="limit" type="integer" default="50">
  Max 500.
</ParamField>

<ParamField path="cursor" type="string">
  Pagination cursor.
</ParamField>

### Example input

```json theme={null}
{ "enabled": true, "search": "starbucks" }
```

<Accordion title="Example output" icon="braces">
  ```json theme={null}
  {
    "rules": [
      {
        "id": "r9Xm2pQr",
        "name": "name: Starbucks → food_and_drink_coffee",
        "conditions": {
          "field": "merchant_name",
          "op": "contains",
          "value": "starbucks"
        },
        "actions": [
          { "type": "set_category", "category_slug": "food_and_drink_coffee" }
        ],
        "trigger": "on_create",
        "category_slug": "food_and_drink_coffee",
        "category_display_name": "Coffee",
        "priority": 10,
        "enabled": true,
        "hit_count": 47,
        "last_hit_at": "2026-04-12T14:32:00Z",
        "created_by_type": "user",
        "created_by_id": "u4Xm9pQ2",
        "created_by_name": "Alice",
        "created_at": "2026-01-15T09:00:00Z",
        "updated_at": "2026-01-15T09:00:00Z"
      }
    ],
    "next_cursor": "",
    "has_more": false,
    "total": 1
  }
  ```
</Accordion>

***

## query\_transaction\_rules

Richer, filterable query over the rule set — the rules analogue of `query_transactions`. Adds trigger, creator type, hit-count, and unused filters that `list_transaction_rules` doesn't expose, plus sorting by impact. Use this to audit coverage ("which agent-created rules have never fired?") or prune dead rules (`only_unused: true`) without loading the full roster.

**Scope:** Read

**Mirrors:** [`GET /api/v1/rules`](/api/overview)

### Parameters

<ParamField path="category_slug" type="string">
  Filter to rules whose `set_category` action targets this category slug.
</ParamField>

<ParamField path="enabled" type="boolean">
  `true` for only enabled, `false` for only disabled, omit for both.
</ParamField>

<ParamField path="trigger" type="string">
  Filter by firing trigger: `on_create`, `on_change` (alias `on_update`), or `always`.
</ParamField>

<ParamField path="creator_type" type="string">
  Filter by who created the rule: `user`, `agent`, or `system`.
</ParamField>

<ParamField path="search" type="string">
  Search by rule name. Comma-separated values are ORed.
</ParamField>

<ParamField path="search_mode" type="string" default="contains">
  `contains`, `words`, or `fuzzy`.
</ParamField>

<ParamField path="min_hit_count" type="integer">
  Return only rules whose `hit_count` is at least this value. Surfaces high-impact rules. Ignored when `only_unused` is `true`.
</ParamField>

<ParamField path="only_unused" type="boolean">
  When `true`, return only rules that have never fired (`hit_count = 0`) — dead or over-specific rules worth reviewing for deletion.
</ParamField>

<ParamField path="sort_by" type="string" default="priority">
  Sort column: `priority` (pipeline execution order), `hit_count`, `last_hit_at`, `created_at`, or `name`.
</ParamField>

<ParamField path="sort_order" type="string">
  `asc` or `desc`. Default per column: `desc` for `hit_count`, `last_hit_at`, `created_at`; `asc` for `priority` and `name`.
</ParamField>

<ParamField path="limit" type="integer" default="50">
  Max 500.
</ParamField>

<ParamField path="cursor" type="string">
  Pagination cursor. Only applies to the default `priority` sort — an explicit `sort_by` returns a single top-N page with no `next_cursor`.
</ParamField>

<ParamField path="fields" type="string">
  Comma-separated fields to include. Omit for the summary projection (`name`, `enabled`, `priority`, `trigger`, `category`, `hit_count`, `last_hit_at` — no conditions or actions trees). Pass `fields=all` for every field.
</ParamField>

### Example — find dead rules

```json theme={null}
{ "only_unused": true, "sort_by": "created_at", "sort_order": "desc" }
```

### Example — highest-impact rules for a category

```json theme={null}
{ "category_slug": "food_and_drink_groceries", "sort_by": "hit_count", "limit": 10 }
```

***

## preview\_rule

Dry-run a condition tree against existing transactions — no writes, no hit-count bump. Returns the match count, total scanned, and a sample of matching transactions. Agents use this to test a condition before committing to a rule.

**Scope:** Read

<Warning>
  **Preview does not simulate the rule pipeline.** It evaluates the supplied condition in isolation against stored data. Tags or categories that earlier-stage rules would have added mid-pass are not visible. For questions like "what does this condition match today?" preview is exactly right; for "what would this rule actually categorize once I ship it?", you still need to create the rule (ideally disabled) and use [`apply_rules`](/mcp/reference/categorization#apply_rules) on a scoped filter.
</Warning>

### Parameters

<ParamField path="conditions" type="object" required>
  Condition tree. Same grammar as [`create_transaction_rule.conditions`](/mcp/reference/rules-write#create_transaction_rule). Leaf: `{"field":"...","op":"...","value":...}`. Combinators: `{"and":[...]}`, `{"or":[...]}`, `{"not":{...}}`.
</ParamField>

<ParamField path="sample_size" type="integer" default="10">
  Number of sample matching transactions to return. Max 50. The `match_count` reflects the full match set regardless of sample size.
</ParamField>

### Example input

```json theme={null}
{
  "conditions": {
    "and": [
      { "field": "merchant_name", "op": "contains", "value": "starbucks" },
      { "field": "amount", "op": "gte", "value": 5 }
    ]
  },
  "sample_size": 3
}
```

<Accordion title="Example output" icon="braces">
  ```json theme={null}
  {
    "match_count": 47,
    "total_scanned": 12540,
    "sample_matches": [
      {
        "transaction_id": "k7Xm9pQ2",
        "date": "2026-04-12",
        "amount": 6.85,
        "name": "STARBUCKS STORE #4591",
        "category_primary_raw": "FOOD_AND_DRINK",
        "current_category_slug": "food_and_drink_coffee"
      },
      {
        "transaction_id": "k8Yn0qR3",
        "date": "2026-04-10",
        "amount": 12.40,
        "name": "STARBUCKS STORE #4591",
        "category_primary_raw": "FOOD_AND_DRINK",
        "current_category_slug": "food_and_drink_coffee"
      },
      {
        "transaction_id": "k9Zo1rS4",
        "date": "2026-04-08",
        "amount": 5.25,
        "name": "STARBUCKS MOBILE ORDER",
        "category_primary_raw": "FOOD_AND_DRINK",
        "current_category_slug": "food_and_drink_coffee"
      }
    ]
  }
  ```
</Accordion>

### Condition grammar

Full DSL is in the Breadbox repo's `docs/rule-dsl.md`. Short form:

* **Fields** — `name`, `merchant_name`, `amount`, `category_primary`, `category_detailed`, `category` (the assigned slug, live-updated by earlier-stage rules), `pending`, `provider`, `account_id`, `account_name`, `user_id`, `user_name`, `tags`.
* **Operators** —
  * String/category: `eq`, `neq`, `contains`, `not_contains`, `matches` (RE2), `in`.
  * Numeric: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`.
  * Bool: `eq`, `neq`.
  * Tags: `contains`, `not_contains`, `in`.
* **Combinators** — `and`, `or`, `not` (nest freely, max depth 10).

Pass `{}` (or omit conditions entirely on rule creation) to match every transaction.

***

## find\_matching\_rules

Reports which existing active rules already match a given transaction or merchant — the inverse of `preview_rule`. Call this before creating a rule to answer "is this merchant already covered?" without loading the entire rule set into context.

**Scope:** Read

### Parameters

<ParamField path="transaction_id" type="string">
  A transaction ID or short ID to evaluate the full active rule set against. Every condition field (amount, category, tags, provider, account, …) is checked against the row's real values. Provide exactly one of `transaction_id` or `merchant`.
</ParamField>

<ParamField path="merchant" type="string">
  Free-text merchant name to check name-based rule coverage for. Builds a synthetic context with only the name fields set — rules that condition on amount, category, or tags won't match and that is the correct, conservative behavior. Provide exactly one of `transaction_id` or `merchant`.
</ParamField>

### Example input

```json theme={null}
{ "merchant": "Starbucks" }
```

<Accordion title="Example output" icon="braces">
  ```json theme={null}
  {
    "matched_count": 1,
    "rules": [
      {
        "short_id": "r9Xm2pQr",
        "name": "name: Starbucks → food_and_drink_coffee",
        "sets_category": "food_and_drink_coffee",
        "trigger": "on_create",
        "priority": 10,
        "enabled": true,
        "hit_count": 47,
        "match_all": false
      }
    ]
  }
  ```
</Accordion>

### Response fields

<ResponseField name="matched_count" type="integer">
  Number of matching rules. Zero means no existing rule covers this transaction or merchant — safe to create a new one.
</ResponseField>

<ResponseField name="rules" type="array">
  Matching rules ordered by priority ascending — the same order the sync pipeline would apply them.

  <Expandable title="Rule fields">
    <ResponseField name="short_id" type="string">Rule short ID.</ResponseField>
    <ResponseField name="name" type="string">Rule name.</ResponseField>
    <ResponseField name="sets_category" type="string">Category slug the rule's first `set_category` action assigns, or omitted when the rule sets no category (e.g. a tag-only rule).</ResponseField>
    <ResponseField name="trigger" type="string">`on_create`, `on_change`, or `always`.</ResponseField>
    <ResponseField name="priority" type="integer">Pipeline priority — lower runs first.</ResponseField>
    <ResponseField name="enabled" type="boolean">Whether the rule is active.</ResponseField>
    <ResponseField name="hit_count" type="integer">Lifetime number of transactions the rule has matched at sync time.</ResponseField>
    <ResponseField name="match_all" type="boolean">`true` for conditionless rules (e.g. the seeded `needs-review` tagger) that match every transaction. These are not merchant coverage — distinguish them from rules with specific conditions.</ResponseField>
  </Expandable>
</ResponseField>

<Note>
  Trigger is not filtered — a rule is reported if its condition matches, regardless of `on_create`/`on_change`. This answers coverage questions, not sync-time simulation. A rule with `match_all: true` covers every transaction but is not a meaningful merchant match.
</Note>
