Skip to main content
Conversations

Search conversations

Search conversations with a structured filter AST and an optional full-text query.

POST
/v2/conversations/search

The endpoint has two modes that are picked automatically from the body:

  • Filter mode (query set, search omitted): structured filtering against indexed conversation fields. Sorted by lastActivityAt desc by default; set sort to lastActivityAt:asc for the inverse.
  • Search mode (search set, query optional): full-text search across conversation messages, then narrowed by the same query AST so the same filters apply. Results are ranked by relevance by default; set sort to order by lastActivityAt instead (the same query string still gates which conversations appear, only their ordering changes).

Structured query AST

Each clause has the shape { field, operator, value }. You can pass a single clause or wrap up to 15 in a top-level AND group:

{
  "query": {
    "operator": "AND",
    "value": [
      { "field": "state", "operator": "=", "value": "open" },
      { "field": "adminAssigneeId", "operator": "=", "value": "507f1f77bcf86cd799439011" }
    ]
  }
}

Top-level OR groups and nested groups are not supported in this version.

Supported fields and operators

Field names are camelCase, matching the rest of the v2 public API. Snake_case names that this endpoint originally shipped with (admin_assignee_id, team_assignee_id, brand_id, tag_ids, mentioned_admin_ids, user_id, participant_id, company_id, created_at) are still accepted for back-compat but are deprecated — please migrate to camelCase in new code.

FieldTypeOperators
stateenum (open / closed / snoozed)=, !=, IN, NIN
priorityboolean=, !=
adminAssigneeIdid or null (unassigned)=, !=, IN, NIN
teamAssigneeIdid or null (unassigned)=, !=, IN, NIN
brandIdid or null=, !=, IN, NIN
tagIdsid (matches any conversation containing this tag)=, !=, IN, NIN
userId / participantIdid of a conversation participant=, !=, IN, NIN
companyIdcompany id (matches conversations whose participants belong to the company)=, !=, IN, NIN
mentionedAdminIdsadmin id=, !=, IN, NIN
createdAtunix seconds=, >, <
customAttributes.<attributeId>conversation custom attribute (see below)=, !=, IN, NIN; plus >, <, >=, <= for number / date

Filtering by custom attributes

Any non-archived conversation custom attribute can be used as a filter field via customAttributes.<attributeId>, where <attributeId> is the attribute's id from GET /v2/conversation_attributes. The accepted value shape follows the attribute's fieldSubType:

  • select / multi-select — pass an option label (e.g. "Product question") or an option id; both are matched interchangeably, and id-based filters keep working after options are renamed
  • text — string
  • number — number; range operators (>, <, >=, <=) are supported
  • date — ISO 8601 string or unix seconds; range operators are supported
  • checkbox — boolean

Example — all conversations where the "Issue type" select attribute is "Product question":

{
  "query": { "field": "customAttributes.6863d3fca1234b0d5e15c86c", "operator": "=", "value": "Product question" },
  "include": ["customAttributeDefinitions"]
}

Referencing an attribute id that does not exist (or has been archived) returns a field_unknown error. Set include to ["customAttributeDefinitions"] to get each returned conversation's attribute values resolved with names and types alongside the ID-keyed customAttributes map.

For unbounded fields a query consisting only of != or NIN clauses is rejected with query_too_broad to prevent full-org scans. Combine the negation with at least one positive clause (=, IN, >, <) instead. Bounded enum fields (state, priority) are exempt because their negation is naturally bounded.

Inbox visibility

An open-state filter uses the same visibility rule as the Featurebase dashboard inbox. Workflow and outbound-message shells are excluded until a customer or lead interacts with them. As a result, state = "open" (or an IN/NIN filter that resolves exclusively to open) returns the conversations shown in the dashboard's open inbox rather than unreplied campaign or automation shells. Searches without an open-only state filter keep their existing organization-wide semantics.

Pagination

Cursor-based, limit between 1 and 100 (default 10). Pagination cursors are mode-specific - reusing a filter-mode cursor in search mode (or vice versa) returns 400. totalCount is approximate and capped at 1000; totalCountCapped is true when the real count may be higher.

Response

Returns a slim conversation row optimised for inbox / list rendering - just the fields you need to render a row and link to the conversation. Fetch the full conversation via GET /v2/conversations/{id} when the user opens one.

Search-specific additions on each row:

  • conversationUser - the customer / lead the conversation is with. Use this for the row identity.
  • surroundingConversationParts - context window of human messages around the matched message (≈15 before + 15 from the match onwards in search mode, sliding to first / last 30 if the match is near the conversation boundaries; falls back to the latest 30 messages in filter mode). Each previewMarkdown value is truncated server-side; fetch the full conversation for complete history and full bodies.
  • matchingPartAuthor - author of the specific message that matched the search query (search mode only). May be a teammate or bot.
  • matchingPart - reference to the matched message (search mode only).
  • matchingBodyPreviewMarkdown - plain-text / markdown preview of the matched body (search mode only).
  • relevanceScore - aggregate score (search mode only).

Version Availability

This endpoint is only available in API version 2026-01-01.nova and newer.

Authorizationstringheaderrequired

API key as Bearer token. Use: Authorization: Bearer sk_...

Featurebase-Versionenum<string>header

API version for this request. Defaults to your organization's configured API version if not specified.

Available options: 2026-08-19.orbit, 2026-01-01.nova, 2025-12-12.clover
Example: 2026-08-19.orbit

Body

application/json
cursorstring

An opaque cursor for pagination. Use the nextCursor value from a previous response to fetch the next page of results.

Maximum string length: 512
Example: eyJpZCI6IjUwN2YxZjc3YmNmODZjZDc5OTQzOTAxMSJ9
includeenum<string>[]

Optional expansions. Use customAttributeDefinitions to return customAttributeValues with names and types alongside ID-keyed customAttributes.

limitintegerdefault:10

A limit on the number of objects to be returned, between 1 and 100.

Required range: 1 <= x <= 100
Example: 10
queryobject

Structured filter AST. Either a single filter clause or one top-level AND group. Use this for filtering by state, tagIds, adminAssigneeId, customAttributes.<attributeId>, etc. (Legacy snake_case names are accepted for back-compat — see the OpenAPI route description for the full alias map.)

Plain-text full-text search across conversation messages. Server-managed - no operators, no field selection.

Required string length: 1 - 500
Example: refund stripe checkout
sortenum<string>

Result ordering. When omitted, filter mode defaults to lastActivityAt:desc and search mode defaults to relevance ranking. When set, both modes order by lastActivityAt in the requested direction (so you can ask a search query to return its hits in chronological order). Snake_case last_activity_at:* is accepted for back-compat — prefer camelCase in new code.

Available options: lastActivityAt:desc, lastActivityAt:asc, last_activity_at:desc, last_activity_at:asc
Example: lastActivityAt:desc

Response

application/json

Success

dataobject[]required

Array of search results

nextCursorstring | nullrequired

Cursor for fetching the next page (null if no more results)

Example: eyJpZCI6IjEyMzQ1In0=
objectenum<string>required

Object type identifier

Available options: list
Example: list
totalCountinteger

Total number of conversations matching the query, capped at 200. When the actual total is at or above the cap, totalCountCapped is true and the value is exactly the cap.

Example: 42
totalCountCappedboolean

True when totalCount is exactly the cap and the real count is >= totalCount. UI may render as "200+".

Example: false