Search conversations
Search workspace conversations by textual match, semantic similarity, or both. Hybrid mode fuses the two signals, removes duplicate chunks from the same conversation, and lets you control exactly which fields are returned.
POST https://api.platica.mx/v1/search/conversations Request body
{
"query": "the customer wants to cancel because they were charged twice",
"mode": "hybrid",
"filters": {
"start_date": "2026-07-01",
"tags": { "any": ["cancellation", "billing"], "none": ["test"] },
"platforms": ["whatsapp", "instagram"],
"is_finished": true
},
"page_size": 20,
"response": {
"fields": ["id", "contactName", "topic", "tags", "snippet", "score", "sources"]
}
} | Field | Type | Default | Description |
|---|---|---|---|
query | string, max 2000 | "*" | Text for lexical search and, unless explicit variants are supplied, semantic search. It may be omitted when browsing with filters. |
semantic_queries | string or string[] | — | One semantic query or up to 5 variants; the best match per conversation is kept. |
mode | hybrid, lexical, or semantic | hybrid | Selects the engines. |
filters | object | {} | Advanced filters described below. |
page | integer ≥ 1 | 1 | Requested page. |
page_size | integer 1-100 | 10 | Results per page. |
candidate_limit | integer 25-1000 | 250 | Fixed candidate window retrieved and fused before pagination. Keep the same value while paging. |
semantic_score_threshold | number 0-2 | 0.8 | Maximum semantic distance. Results must be below the threshold; a lower value requires greater similarity. |
ranking | object | weights 0.5, rrf_k: 60 | Engine weights and RRF constant. |
response | object | compact projection | Controls fields, results, analytics, diagnostics, and text limits. |
export_csv | boolean or object | false | Creates a CSV with a signed URL. |
Search modes
| Mode | Engine | When to use it |
|---|---|---|
hybrid | Lexical + semantic | Recommended. Combines textual and conceptual matches with Reciprocal Rank Fusion (RRF). |
lexical | Lexical | For words, names, phone numbers, email addresses, or exact phrases. query: "*" lets you browse by filters and recency only. |
semantic | Semantic | Finds conversations with the same meaning even when they use different words. Requires a query other than "*" or semantic_queries. |
In hybrid mode, query is also used as the semantic query when semantic_queries is omitted. If
there is no semantic query, the search continues with the lexical branch only and reports this in warnings. If you provide semantic_queries without query, the lexical branch is skipped so
semantic matches are not mixed with a general recency listing.
Filters
Every text filter accepts one string or an array of up to 50 strings. Filters apply to the results from both engines.
| Field | Type | Description |
|---|---|---|
start_date, end_date | ISO 8601 date | Inclusive range. When omitted, searches from 90 days ago through now. A YYYY-MM-DD value covers the whole day. |
date_field | lastUpdate or creationDate | Date used by the range. Default: lastUpdate. |
tags | string, string[], or object | A string/list means any. The object supports any, all, and none to include any, require all, or exclude tags. |
platforms | string or string[] | Platforms such as whatsapp, instagram, or messenger. |
topics | string or string[] | Conversation topics. |
statuses | string or string[] | Searchable statuses: active, finished, or spam. |
agents | string or string[] | Agent IDs/paths. |
channel_ids | string or string[] | Channel IDs. |
operations | string or string[] | Operation modes. |
owner_ids | string or string[] | Owner IDs. |
conversation_ids | string or string[] | Thread IDs. |
client_ids | string or string[] | Client IDs. |
phone_numbers | string or string[] | Exact phone numbers. |
has_phone | boolean | Includes or excludes conversations with a phone number. |
is_finished | boolean | Filters by finished state. |
Hybrid ranking
{
"ranking": {
"lexical_weight": 0.65,
"semantic_weight": 0.35,
"rrf_k": 60
}
} Each weight accepts values from 0 to 1; at least one must be greater than zero in hybrid mode.
RRF combines each conversation's position in both branches instead of directly comparing their
relevance scales. Semantic chunks are deduplicated by conversation id before ranking.
The final score is normalized from 0 to 1. Request sources to see which engines found a
result and scores to inspect lexicalRank, lexicalTextMatch, semanticRank, and semanticDistance.
Control response size
The default projection excludes messages, phone numbers, and email addresses:
{
"response": {
"fields": [
"id", "contactName", "platform", "topic", "tags",
"lastUpdate", "snippet", "conversationUrl", "score", "sources"
],
"include_results": true,
"include_analytics": false,
"include_engine_metadata": false,
"create_search_cache": false,
"max_snippet_chars": 320,
"max_content_chars": 2000
}
} fields accepts:
id, conversationId, contactName, phoneNumber, email, platform, topic, tags,
status, agent, channelId, clientId, operation, owners, isFinished, creationDate,
lastUpdate, snippet, messages, conversationUrl, contactUrl, score, sources, scores | Field | Range | Effect |
|---|---|---|
include_results | boolean | With false, omits results; useful when you only need analytics, cache creation, or an export. |
include_analytics | boolean | Adds counts by topic, tag, status, agent, platform, and date. Counts are sampled from the fused candidate window; inspect analytics.scope. |
include_engine_metadata | boolean | Adds lexical and semantic branch status, candidates, timing, and truncation data. |
create_search_cache | boolean | Creates a temporary searchId for subsequent flows and returns its expiration. |
max_snippet_chars | integer 40-2000 | Maximum snippet length. Default: 320. |
max_content_chars | integer 100-10000 | Maximum length of the matching passage in messages. It is not the complete history. Default: 2000. |
For agents and automations, request only the fields you need. Use snippet to check relevance and
request messages only when you need the available matching passage. For the complete history, use Get conversation .
Export CSV
Send "export_csv": true to export matches from the candidate window with the default column
selection, or control both options:
{
"query": "cancellation request",
"response": { "include_results": false },
"export_csv": {
"fields": ["id", "contactName", "phoneNumber", "topic", "lastUpdate", "conversationUrl"],
"max_rows": 500
}
} max_rows limits exported rows but does not expand candidate_limit. Exports that include snippet or messages are limited to 100 rows; without those fields they can include up to 1000.
The response includes export.url, filename, rowCount, sizeBytes, truncated, and expiresAt. downloadUrl contains the same URL for compatibility. The file is private, the signed
URL expires after 7 days, and cells are protected against spreadsheet formulas.
The default export does include phoneNumber and email. Treat the signed URL as a temporary
secret and use export_csv.fields to exclude personal data you do not need.
Response
{
"success": true,
"mode": "hybrid",
"matchCount": 42,
"candidateCount": 42,
"totalFound": 42,
"totalRelation": "lower_bound",
"returnedCount": 2,
"truncated": false,
"fields": ["id", "contactName", "topic", "snippet", "score", "sources"],
"results": [
{
"id": "conv_7a19",
"contactName": "Maria Lopez",
"topic": "Billing",
"snippet": "...I would like to cancel because I was charged twice...",
"score": 0.967213,
"sources": ["lexical", "semantic"]
}
],
"pagination": {
"currentPage": 1,
"totalPages": 5,
"pageSize": 10,
"hasNextPage": true,
"hasPreviousPage": false,
"candidateWindow": 250
},
"tookMs": 183
} | Field | Meaning |
|---|---|
candidateCount, matchCount, totalFound | Unique conversations retrieved within the candidate window after deduplication. |
totalRelation | exact only for an untruncated lexical search; lower_bound for hybrid, semantic, or truncated results. |
truncated | true means an engine or the fusion reached its limit; more matches may exist outside the window. |
returnedCount | Items included in results; it is 0 when include_results is false. |
warnings | Notices about degraded search or result limits. |
The requested page must fit inside candidate_limit. The maximum window is 1000 conversations and
stays fixed to preserve ordering across pages.
Engine availability
If one requested branch is unavailable and the other is available, the endpoint continues with the
available branch, readjusts ranking, and adds a warnings entry. Enable response.include_engine_metadata when diagnosing integrations; leave it disabled by default to
minimize context.