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"]
  }
}
FieldTypeDefaultDescription
querystring, max 2000"*"Text for lexical search and, unless explicit variants are supplied, semantic search. It may be omitted when browsing with filters.
semantic_queriesstring or string[]One semantic query or up to 5 variants; the best match per conversation is kept.
modehybrid, lexical, or semantichybridSelects the engines.
filtersobject{}Advanced filters described below.
pageinteger ≥ 11Requested page.
page_sizeinteger 1-10010Results per page.
candidate_limitinteger 25-1000250Fixed candidate window retrieved and fused before pagination. Keep the same value while paging.
semantic_score_thresholdnumber 0-20.8Maximum semantic distance. Results must be below the threshold; a lower value requires greater similarity.
rankingobjectweights 0.5, rrf_k: 60Engine weights and RRF constant.
responseobjectcompact projectionControls fields, results, analytics, diagnostics, and text limits.
export_csvboolean or objectfalseCreates a CSV with a signed URL.

Search modes

ModeEngineWhen to use it
hybridLexical + semanticRecommended. Combines textual and conceptual matches with Reciprocal Rank Fusion (RRF).
lexicalLexicalFor words, names, phone numbers, email addresses, or exact phrases. query: "*" lets you browse by filters and recency only.
semanticSemanticFinds 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.

FieldTypeDescription
start_date, end_dateISO 8601 dateInclusive range. When omitted, searches from 90 days ago through now. A YYYY-MM-DD value covers the whole day.
date_fieldlastUpdate or creationDateDate used by the range. Default: lastUpdate.
tagsstring, string[], or objectA string/list means any. The object supports any, all, and none to include any, require all, or exclude tags.
platformsstring or string[]Platforms such as whatsapp, instagram, or messenger.
topicsstring or string[]Conversation topics.
statusesstring or string[]Searchable statuses: active, finished, or spam.
agentsstring or string[]Agent IDs/paths.
channel_idsstring or string[]Channel IDs.
operationsstring or string[]Operation modes.
owner_idsstring or string[]Owner IDs.
conversation_idsstring or string[]Thread IDs.
client_idsstring or string[]Client IDs.
phone_numbersstring or string[]Exact phone numbers.
has_phonebooleanIncludes or excludes conversations with a phone number.
is_finishedbooleanFilters 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
FieldRangeEffect
include_resultsbooleanWith false, omits results; useful when you only need analytics, cache creation, or an export.
include_analyticsbooleanAdds counts by topic, tag, status, agent, platform, and date. Counts are sampled from the fused candidate window; inspect analytics.scope.
include_engine_metadatabooleanAdds lexical and semantic branch status, candidates, timing, and truncation data.
create_search_cachebooleanCreates a temporary searchId for subsequent flows and returns its expiration.
max_snippet_charsinteger 40-2000Maximum snippet length. Default: 320.
max_content_charsinteger 100-10000Maximum length of the matching passage in messages. It is not the complete history. Default: 2000.

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.

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
}
FieldMeaning
candidateCount, matchCount, totalFoundUnique conversations retrieved within the candidate window after deduplication.
totalRelationexact only for an untruncated lexical search; lower_bound for hybrid, semantic, or truncated results.
truncatedtrue means an engine or the fusion reached its limit; more matches may exist outside the window.
returnedCountItems included in results; it is 0 when include_results is false.
warningsNotices 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.