Search stored messages
GET /api/search searches stored message text across sessions. It requires an operator or admin key and is enabled by default with the built-in database full-text provider. A viewer key gets 403. Set SEARCH_ENABLED=false to remove the route, or SEARCH_PROVIDER=builtin-fts to keep the built-in provider active when search plugins are installed.
curl --get http://localhost:2785/api/search \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode "q=invoice" \
--data-urlencode "sessionId=8f3c2b1a-9d4e-4c7a-8b2f-1e6d5a4c3b2a" \
--data-urlencode "chatId=628123456789@c.us" \
--data-urlencode "direction=incoming" \
--data-urlencode "limit=20"
Filters and pagination
| Parameter | Meaning |
|---|---|
q | Required, non-blank text. At most 1000 characters and 32 search words. Punctuation separates words; a standalone symbol or emoji counts as a word. Repeated whitespace-separated words count once. |
sessionId, chatId | Narrow the search to one session or chat. These filters cannot expand the key's permissions. |
direction | incoming or outgoing. |
type | Stored message type, for example text, image or poll. |
from | Sender filter. |
dateFrom, dateTo | Inclusive bounds in Unix epoch milliseconds. |
limit, offset | Page size (default 50) and offset from 0 to 100000. limit is capped by SEARCH_LIMIT_MAX (default 100). |
The response has hits, total, tookMs and provider. A hit includes its stored messageId, WhatsApp waMessageId, sessionId, chatId, body, snippet, timestamp, type, direction, from and optional score. Use offset to request subsequent pages.
This endpoint searches indexed text. To filter message rows by message time, type, direction or message reference without a full-text query, use the stored message list.
API-key restrictions
The gateway applies allowedSessions and, since v0.25.0, allowedChats before the built-in provider computes hits, pagination and totals. A chat-scoped key can omit chatId to search its allowed chats; an explicit disallowed chat gets 403. Search through a plugin provider gets 403 for a chat-scoped key, so select SEARCH_PROVIDER=builtin-fts for this use case.
On SQLite, hits returned to a session-scoped or chat-scoped key omit score, because the full-index score would reflect messages outside its scope. They remain in relevance order. See Authentication for creating restricted keys.
Query syntax and snippets
PostgreSQL uses websearch_to_tsquery, supporting quoted phrases, OR and exclusion. SQLite treats each whitespace-separated token literally and combines tokens with AND; its FTS query operators are not exposed.
Snippets use literal <mark> and </mark> markers around matches. The rest of the body is not HTML-escaped. Split on those markers and render the segments as text; never assign a raw snippet to innerHTML.
| Status | Cause |
|---|---|
400 | Blank or oversized q, more than 32 words, or an invalid numeric parameter. |
401 | Missing, unknown, revoked or expired API key. |
403 | Insufficient role, IP/session/chat scope denial, or chat-scoped search through a plugin. |
501 | No provider is available, including a custom SQLite build without FTS5. |
502 | A plugin returned a malformed result. |
503 | The active search plugin failed or timed out. |
See the API reference for the full response schema and Configuration for search settings.