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

# Understanding rules

> A primer on the Breadbox rule DSL — how to build condition trees, what actions are available, and four worked examples you can adapt.

Rules are the workhorse of any Breadbox setup. A well-tuned set of rules can categorize the overwhelming majority of your transactions during sync, leaving only the genuinely ambiguous ones for a human or agent to look at. This guide walks through the DSL by example — four rules you can adapt and drop into your instance today.

If you haven't yet, skim [Breadbox in a nutshell](/guides/breadbox-in-a-nutshell) first for the vocabulary. The full specification lives in [Auto-categorize transactions with rules](/transactions/rules) and the [rules API reference](/api/overview).

## The DSL, in one screen

A rule is a JSON document with three important pieces:

1. **A condition** — a recursive tree of leaves (`field`/`op`/`value`) and combinators (`and` / `or` / `not`).
2. **One or more actions** — `set_category`, `add_tag`, `remove_tag`, `add_comment`, or `assign_series` (link the charge to a recurring [series](/guides/tracking-subscriptions)).
3. **A trigger and stage** — when the rule runs (<Tooltip headline="on_create stage" tip="The rule pipeline stage that fires once for every newly-synced transaction. The seeded `needs-review` rule runs here." cta="Rule pipeline" href="/transactions/rules">`on_create`</Tooltip>, `always`, `on_change`) and where in the pipeline (`baseline`, `standard`, `refinement`, `override`).

Amounts use Plaid convention: **positive = money out** (purchases, payments), **negative = money in** (refunds, paychecks). Every example below respects that.

## Example 1 — Amazon purchases → Shopping

The canonical "merchant name contains a substring" rule. Two conditions combined with `and`: the description must contain `AMAZON`, and the amount must be positive (so we don't catch Amazon refunds).

```json theme={null}
{
  "name": "Amazon purchases",
  "conditions": {
    "and": [
      { "field": "name", "op": "contains", "value": "AMAZON" },
      { "field": "amount", "op": "gt", "value": 0 }
    ]
  },
  "actions": [
    { "type": "set_category", "category_slug": "general_merchandise" }
  ],
  "trigger": "on_create",
  "stage": "standard"
}
```

Create it via the API:

```bash theme={null}
curl -X POST \
  -H "X-API-Key: bb_your_key" \
  -H "Content-Type: application/json" \
  -d @amazon-rule.json \
  http://localhost:8080/api/v1/rules
```

Before saving, dry-run against your history to sanity-check the match count:

```bash theme={null}
curl -X POST \
  -H "X-API-Key: bb_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "conditions": {
      "and": [
        { "field": "name", "op": "contains", "value": "AMAZON" },
        { "field": "amount", "op": "gt", "value": 0 }
      ]
    }
  }' \
  http://localhost:8080/api/v1/rules/preview
```

## Example 2 — Uber / Lyft / Waymo → Transportation

When a single condition isn't enough, an `or` group handles the "any of these merchants" case. Here we use the `in` operator on `merchant_name` to check against a list in one shot.

```json theme={null}
{
  "name": "Ridesharing → Transportation",
  "conditions": {
    "and": [
      { "field": "merchant_name", "op": "in", "value": ["Uber", "Lyft", "Waymo"] },
      { "field": "amount", "op": "gt", "value": 0 }
    ]
  },
  "actions": [
    { "type": "set_category", "category_slug": "transportation_taxi_and_ride_share" },
    { "type": "add_comment", "content": "Auto-categorized as rideshare by rule." }
  ],
  "trigger": "on_create",
  "stage": "standard"
}
```

<Note>
  `merchant_name` is only populated for providers that enrich the raw description (Plaid does this; Teller and CSV imports often don't). If your data comes primarily from Teller, prefer `name` with `contains` and match on a substring of the raw description.
</Note>

## Example 3 — Threshold tag: flag anything over \$500

Not every rule has to change the category. This one only *tags* — anything over \$500 gets the `high-amount` tag so you can scan the queue for big-ticket items before the rest.

```json theme={null}
{
  "name": "Flag transactions over $500",
  "conditions": {
    "and": [
      { "field": "amount", "op": "gt", "value": 500 },
      { "field": "pending", "op": "eq", "value": false }
    ]
  },
  "actions": [
    { "type": "add_tag", "tag_slug": "high-amount" }
  ],
  "trigger": "on_create",
  "stage": "standard"
}
```

You'd pair this with a tag you've pre-created in the dashboard (**Tags** → **New tag**, slug `high-amount`). Then filter to `/transactions?tags=high-amount` in the UI, or `query_transactions(tags=["high-amount"])` from an agent.

## Example 4 — A `not` rule: auto-categorize groceries, but not Whole Foods prepared food

Sometimes the cleanest way to express a rule is "match this *except* for these cases." The `not` combinator wraps a sub-condition and inverts it.

```json theme={null}
{
  "name": "Grocery stores → Groceries (except prepared food)",
  "conditions": {
    "and": [
      { "field": "merchant_name", "op": "in", "value": ["Whole Foods", "Trader Joe's", "Safeway"] },
      { "not": { "field": "name", "op": "contains", "value": "PREPARED" } }
    ]
  },
  "actions": [
    { "type": "set_category", "category_slug": "food_and_drink_groceries" }
  ],
  "trigger": "on_create",
  "stage": "standard"
}
```

You can freely nest `and`, `or`, and `not` up to 10 levels deep. For most workflows you'll keep it under three.

<Tip>
  If a transaction gets the wrong category anyway, set it manually from the dashboard. That sets <Tooltip headline="category_override" tip="Who last set the category: 'none' (rule-applied or unset), 'agent' (AI-set), or 'user' (manually set — protected from automatic changes).">`category_override = 'user'`</Tooltip>, which the rule engine honors forever — your deliberate choice won't be undone by a later rule.
</Tip>

## Applying rules retroactively

Rules only fire at sync time by default. To run a newly created rule against your full history:

```bash theme={null}
# Apply one rule
curl -X POST \
  -H "X-API-Key: bb_your_key" \
  http://localhost:8080/api/v1/rules/rule_abc123/apply

# Apply every active rule
curl -X POST \
  -H "X-API-Key: bb_your_key" \
  http://localhost:8080/api/v1/rules/apply-all
```

Retroactive apply follows the same pipeline order and the same `category_override` protection as live sync. One caveat: `add_comment` actions are skipped during retroactive apply (they're designed to narrate a specific sync event).

## Where to go next

* [Single Routine Reviewer](/guides/single-routine-reviewer) — put these rules to work with an agent that clears the remaining `needs-review` queue on a schedule.
* [Tracking Subscriptions](/guides/tracking-subscriptions) — a worked rule example for tagging recurring charges.
* [Rules reference](/transactions/rules) and [Rules API](/api/overview) — full DSL, every field and operator, plus the JSON shape of every endpoint.
