Global search — worked samples
One POST replaces the N parallel per-surface searches a client would otherwise fan out itself: the query is multiplexed against every product pillar in a single request, and the response comes back ranked across all of them. This is the endpoint behind the dashboard’s global search palette — use it the same way when building a custom search widget.Request fields
Multi-pillar query across all seven pillars
200
Reading the response
Each entry inresults[] — and in the per-pillar buckets under
by_pillar — carries:
id— the entity’s own id, so client-render keys stay stable.pillar— one of the seven pillar buckets above; drives the icon and group heading.title— the headline label (a contact name, an agent name, a number).subtitle— an optional muted secondary label (for a contact, the email or phone).href— a locale-relative dashboard path; prepend/${locale}before navigating.score— per-pillar relevance: exact match > prefix match > substring match. Use it as the flat-sort tiebreaker across pillars.
results array or the grouped
by_pillar map (keys are only the pillars that returned at least one
match). counts reports the match tally for every pillar, including zero,
which is what powers a “1 result in Contacts” summary line. partial
names the pillars whose sub-query failed — e.g. a transient database
error on one resolver — so the client can show a discreet “couldn’t search
conversations” hint next to the pillars that did succeed. partial is
empty on a fully successful fan-out.
search_analytics_id is the row this query recorded for search
analytics, or null when the write was de-duped. Save it and report the
row the user picks with
POST /api/v1/search-analytics/click — that click attribution is what
makes the search-analytics click-through panel non-zero. See the Search
Analytics API for the click endpoint
and the reporting reads that back-fill those panels.
Scoped palette query — worked round trip
A palette opened on one surface rarely needs all seven pillars. Scope the fan-out withpillars and tighten per_pillar_limit so the response stays
small enough to render per keystroke:
cURL
200
id, pillar,
title, subtitle, href, score); by_pillar and counts cover only
the pillars you asked for, and partial stays empty on a fully successful
fan-out.
static_match_count and the zero-result trap. The analytics row
recorded for every query stores its result count as the pillar matches
returned plus the static_match_count you sent. In the round trip above
the palette rendered one client-side shortcut and the pillars returned two
hits, so the query records as three results. Leave static_match_count
out and a search that matched only your static shortcuts records zero —
and lands in the Search Analytics zero-result panel even though the user
found what they needed and clicked it. That is exactly the miscount the
field exists to prevent: send the number of shortcuts your client actually
rendered for the query, every time.
The same call through the Python SDK’s generic client, for teams on a SDK
version that does not ship a dedicated search resource yet:
Python
Errors worth branching on
422
details.field: "query"; an unrecognized pillar name lands with
details.field: "pillars". There is no dedicated query_too_long code to
match against: branch on the 422 plus error.details.field. And because the
endpoint takes free text with no operator grammar, there is likewise no
operator_not_supported rejection to handle — special characters are
matched literally, never rejected.
401