Query language
The filter DSL the API and MCP tools accept, clause by clause.
The REST API and the MCP tools accept a small filter language on their read endpoints. It's deliberately small — enough to ask a precise question, not enough to need its own manual.
The app's own filter bar builds the same queries under the hood, and the same evaluator runs on both sides, so a filter means exactly one thing whether the browser or the server answers it.
Shape
Clauses of the form field op value, joined by and. There is no or, and no parentheses.
category eq 'Groceries' and debit ge 50 and transactionDate ge 20240101
String values are single-quoted. A literal apostrophe inside one is written by doubling it: description contains 'Trader Joe''s'. Numbers are unquoted.
Fields
| Field | Operators | Value |
|---|---|---|
description |
eq, contains, startswith, endswith |
quoted string |
source |
eq |
quoted string, e.g. 'Chase' |
category |
eq, startswith, in |
quoted string, or a parenthesized list for in |
effectiveCategorySource |
eq |
'override', 'rules', or 'none' |
rules.status |
eq |
'matched', 'none', or 'conflict' |
transactionDate |
eq, ne, gt, ge, lt, le |
YYYYMMDD, unquoted |
debit |
eq, ne, gt, ge, lt, le |
number, unquoted |
credit |
eq, ne, gt, ge, lt, le |
number, unquoted |
category means the transaction's effective category — the override if one is set, otherwise whatever the rules concluded. An uncategorized transaction has the empty category, so category eq '' finds them.
balance is not a queryable field.
Two rules to remember
At most one category clause per query. Two of them would either contradict each other or be redundant, so the parser rejects it rather than guessing. Use in for a set:
category in ('Groceries','Restaurants','Transportation')
There's no size limit on that list — a long one is simply evaluated differently behind the scenes.
Results are capped. A response carrying truncated: true means the cap was reached and there is more matching data than you were given. Narrow the query — usually with a date range — rather than assuming the result is complete.
Examples
Everything you have hand-corrected, which is the list of merchants that probably deserve a rule:
effectiveCategorySource eq 'override'
Transactions no rule covers:
rules.status eq 'none'
Transactions where rules disagree and nothing could be applied:
rules.status eq 'conflict'
Large restaurant charges last year:
category eq 'Restaurants' and debit ge 100 and transactionDate ge 20240101 and transactionDate le 20241231
Everything from one card in one month:
source eq 'Chase' and transactionDate ge 20250301 and transactionDate le 20250331
Anything Amazon-shaped, however it's categorized:
description contains 'AMZN'
An older name
categoryStatus was renamed to rules.status. The old name is rejected with a message pointing at the new one rather than silently ignored.