Search query language
Documentation: v0.29.0
Every field, operator, preset and limit of MAX message search, with date rules, regular expressions and the JSON answer for scripts and agents.
The reference for queries of max messages search, max messages stats and saved searches. For
everyday examples start with message search.
The language is a strict profile of Apache Lucene's query syntax: words, phrases, AND/OR/NOT, groups, fields, ranges, bounded wildcards and regular expressions. The full reference (in Russian) has the generated tables of fields, operators, presets and limits, and executable examples; the technical specification describes the grammar and the compiler.
Operators
| Operator | Example | Meaning |
|---|---|---|
| words | счёт оплачен | both words |
| phrase | "счёт оплачен" | the words in this order |
AND, && | alpha AND beta | both |
OR, || | alpha OR beta | either |
NOT, !, - | alpha NOT beta | the first without the second |
+ | +alpha OR beta | alpha required, beta optional |
| group | (alpha OR beta) gamma | brackets set the order |
| field group | from:(alice OR bob) | the field applies to each value |
| range | date:[2026-01-01 TO 2026-02-01} | [ ] include, { } exclude, * open |
| comparison | size>10MB, date>=7d | an open range |
| wildcard | квартир*, т?кст | * any characters, ? one |
| regex | text:/pass(port)?/ | a bounded Lucene regular expression |
alpha OR beta gamma means (alpha OR beta) AND gamma; alpha OR beta AND gamma means
alpha OR (beta AND gamma). Use brackets for clarity. Lowercase and, or, not are plain words. A
query with only NOT finds nothing: give a positive condition, for example kind:group NOT preset:secret.
Fuzzy ~, proximity, boosts and intervals are refused with an error, not ignored.
Fields
Field names are case-sensitive. An unknown field, value or combination is an error, never an empty answer and never plain text. A name that the archive does not know is not looked up on MAX.
kind:bot selects a chat with a bot; in:bots selects the archives of max bot. topic: requires exactly one chat in chat: or --chat: topic numbers repeat across chats. filename, mime and size match a message if at least one of its files matches. MAX does not report file types, so mime: finds nothing here: search by extension.
Presets
| Preset | A candidate is |
|---|---|
password | a password label followed by a value |
code | a verification-code label and 4–8 digits |
api-key | an API-key label and a value |
secret | a password, secret, token or API-key label and a value |
card | 13–19 digits with optional spaces or hyphens |
bank | an IBAN-shaped value |
passport | a labelled passport value or a Russian 4+6 digit shape |
phone | a plus-prefixed international phone shape |
email | an email-address shape |
telegram-link | a t.me or telegram.me link |
url | an HTTP(S) link |
contact | a contact attachment, or an email or phone |
location | a location attachment or a geo: link |
A preset reports a candidate by its shape. It does not verify a password, a card or a document, and it can match something harmless. Do not delete or forward messages automatically on its word.
Dates
--timezone takes an IANA zone such as Europe/Madrid; without it, the computer's zone is used and
returned in the answer. A date without a time is a whole calendar day. An inclusive upper day includes
that whole day, an exclusive one excludes it; a day when clocks change can last 23 or 25 hours.
date:today and date:yesterday are calendar days. date:7d means from 7 days ago until now (also
30m, 2h); date>=7d and date:[30d TO 7d} work in comparisons and ranges, counted from the
moment the query runs. An exact time is quoted, with seconds and an offset:
date>="2026-01-01T10:00:00+02:00".
Words, wildcards and regular expressions
Before indexing and searching, text is converted to lower case and loses accents; ё becomes е, and й becomes и. Some different words therefore match: мой also finds «мои». Regex and wildcards on text: are normalized the same way.
text:/счёт/ matches the whole word «счёт», but not «счётом». body:/счёт/ matches only a message whose entire text is «счёт», case-sensitive; to find it anywhere, use body:/.*счёт.*/, and for the start of a sentence, body:/.*[Сс]чёт.*/. This is Lucene regular-expression syntax, without lookaround, backreferences, anchors or JavaScript flags.
On a large archive a short prefix such as к* can expand to more than 10,000 words and is refused;
lengthen it. Long
queries, deep nesting, large patterns and slow scans are refused with query_limit, not cut short:
narrow the chat, the dates or the pattern.
The answer
--json returns { items, page, limit, hasMore, corrections, completeness, wordsReady, query, coverage },
even when nothing matched. --jsonl streams the items only.
query— the language version, the time zone and the order used.coverage— which accounts and chats were searched.lastSyncedAtis the oldest time a chat in scope was fetched bystore fetch,nullif any chat never was.inventoryCompletemeans every account in scope has once listed all its chats; it does not promise a complete history.completeness— per chat: whether its stored history reaches the start and has gaps.wordsReady— whether the word index is complete. When it isfalse, a query with words fails withindex_not_readyand the command that finishes it,max store migrate; a query without words runs.hasMoreis about the page, not about whether MAX holds more.
An error carries the position of the problem in the query and a hint.
In MCP
max_messages_search accepts the query as text or as a versioned syntax tree in ast (not both); language selects lucene or legacy, and timezone sets the calendar time zone. chat accepts an id or stored name; source, newest, context and limit work like the command options. record: false excludes the call from query history. The answer has the same fields as --json. max_messages_stats counts matches of the same queries.
The older modes
max messages search 'from:alice after:7d invoice -draft' --language legacy --json
max messages search --regex 'invoice\s+\d+' --json--language legacy keeps the earlier filters and its correction of typos. --regex is a separate
mode: a JavaScript regular expression, case-insensitive, over the full text, in an isolated worker with
time and size limits. --regex cannot be combined with --language lucene.
| Legacy | Strict |
|---|---|
after:2026-01-01 | date:[2026-01-01 TO *] |
before:2026-02-01 | date:[* TO 2026-02-01} |
after:7d | date:7d |
| automatic prefix and typo correction | квартир* explicitly; typos only in --language legacy |
--thread follows the stored reply graph; in messages context it replaces chronological neighbours. Defaults are
8 hops, 50 messages, 65,536 bytes and one day around each hit. Change them with --thread-hops,
--thread-messages, --thread-bytes, --thread-within. Without a graph it falls back to chronological context;
stale links are marked and not traversed.
Search reads the local archive by default. --sync-first explicitly fetches new messages before searching and
marks nothing read: at most 5 chats, 500 messages and 30 seconds. Change these bounds with --max-chats,
--max-messages, --sync-time. Failed or incomplete refresh retains local results with stale coverage and refresh
details.
MCP uses thread, thread_hops, thread_messages, thread_bytes, thread_within and sync_first. sync_first
is exposed only with messages.sync-first: allow. Ordinary messages_context with offline: true reads stored
messages.