Searching engagements

Common patterns for filtering engagements by CRM associations, contacts, companies, and transcript data
View as Markdown

GET /v2/engagements is the main search surface for engagement discovery.

Use it when you need to:

  • Find engagements linked to CRM records such as deals, companies, or contacts
  • Find engagements for known AskElephant contact or company IDs
  • Narrow results by time window, processing state, or engagement type
  • Load related engagement details before fetching transcript timelines

If you specifically want CRM company, contact, or deal examples, see Filter engagements by CRM object IDs. Every engagement object now includes is_private so your integration can see the privacy state of the engagements it is allowed to read.

What the endpoint supports

The most common engagement search parameters are:

ParameterWhat it does
filter[crm_associations][eq]Matches engagements linked to one or more CRM objects.
filter[contact_ids][eq|in]Matches engagements linked to AskElephant contact IDs.
filter[company_ids][eq|in]Matches engagements linked to AskElephant company IDs.
filter[start_at][gt|gte|lt|lte]Narrows by engagement start time.
filter[updated_at][gt|gte|lt|lte]Narrows by last update time.
filter[processing_status][eq|in]Filters by transcript or analysis processing state.
filter[engagement_type][eq|in]Filters by engagement type such as MEETING or CALL.
expandIncludes related fields like companies, contacts, owner, signals, transcript, transcript_timeline, media_url, or tags.
order_bySorts by updated_at:asc or updated_at:desc.

Use the canonical filter names exactly as shown in the table. Legacy top-level aliases such as company_id, top-level processing_status, sort, and order are rejected with 400 responses.

Important behavior

For readability, docs examples use indexed bracket syntax, but the equivalent object shape is:

[
{ "id": "12345", "object_type": "company" },
{ "id": "67890", "object_type": "contact" },
{ "id": "deal_abc123", "object_type": "deal" }
]

Single-object examples:

  • CRM company ID: { "id": "12345", "object_type": "company" }
  • CRM contact ID: { "id": "67890", "object_type": "contact" }
  • CRM deal ID: { "id": "deal_abc123", "object_type": "deal" }

The CRM source is inferred from the workspace’s connected CRM. You do not pass a separate source query parameter.

Engagement filters combine like this:

  • Values in the same bucket use OR semantics
  • Populated buckets combine with AND semantics
  • AskElephant company_ids and CRM company associations share the company bucket
  • AskElephant contact_ids and CRM contact associations share the participant bucket
  • CRM deal associations populate a deal bucket

That means you can search for:

  • Any engagement related to one of several contacts
  • Any engagement related to one of several CRM deals
  • Engagements that match both a company bucket and a participant bucket

Search by CRM object ID

Use filter[crm_associations][eq] when your system already knows the CRM record IDs and you want the related engagements.

When using raw filter[...] query params with curl, add --globoff so curl does not interpret [] as URL globbing syntax.

Example: find engagements linked to a CRM deal:

curl --request GET \
--globoff \
--url 'https://app.askelephant.ai/api/v2/engagements?filter[crm_associations][eq][0][id]=deal_123&filter[crm_associations][eq][0][object_type]=deal&order_by=updated_at:desc&limit=25' \
--header 'Authorization: sk-apik_<id>.<secret>' \
--header 'Accept: application/json'

Example: search by multiple CRM objects in one request:

curl --request GET \
--globoff \
--url 'https://app.askelephant.ai/api/v2/engagements?filter[crm_associations][eq][0][id]=deal_123&filter[crm_associations][eq][0][object_type]=deal&filter[crm_associations][eq][1][id]=contact_456&filter[crm_associations][eq][1][object_type]=contact&order_by=updated_at:desc&limit=25' \
--header 'Authorization: sk-apik_<id>.<secret>' \
--header 'Accept: application/json'

Use this pattern when your integration starts from Salesforce, HubSpot, or another CRM and you need to map back to AskElephant engagements.

Search by AskElephant company IDs

Use filter[company_ids][eq] or filter[company_ids][in] when you already have AskElephant company IDs.

Example:

curl --request GET \
--globoff \
--url 'https://app.askelephant.ai/api/v2/engagements?filter[company_ids][in]=cmp_01HQXVB4Y9Y0Q6P0QH5GX2CT7T,cmp_01HQXVB4Y9Y0Q6P0QH5GX2CT7U&expand=companies,owner&order_by=updated_at:desc&limit=25' \
--header 'Authorization: sk-apik_<id>.<secret>' \
--header 'Accept: application/json'

This is useful when your app stores AskElephant company IDs directly and wants all engagement activity around one or more companies.

Search by AskElephant contact IDs

Use filter[contact_ids][eq] or filter[contact_ids][in] when you know the AskElephant contact IDs.

Example:

curl --request GET \
--globoff \
--url 'https://app.askelephant.ai/api/v2/engagements?filter[contact_ids][eq]=cnt_01HQY3JMS2QAXJGX6X7CH7CM6X&expand=contacts,owner&order_by=updated_at:desc&limit=25' \
--header 'Authorization: sk-apik_<id>.<secret>' \
--header 'Accept: application/json'

This is the cleanest path when a user is already looking at a known AskElephant contact record and wants the related meetings, calls, emails, or notes.

Combine company, contact, and CRM filters

You can combine filters to narrow results further.

Example: find engagements that are tied to a specific company, a specific contact, and a CRM deal:

curl --request GET \
--globoff \
--url 'https://app.askelephant.ai/api/v2/engagements?filter[company_ids][eq]=cmp_01HQXVB4Y9Y0Q6P0QH5GX2CT7T&filter[contact_ids][eq]=cnt_01HQY3JMS2QAXJGX6X7CH7CM6X&filter[crm_associations][eq][0][id]=deal_123&filter[crm_associations][eq][0][object_type]=deal&filter[start_at][gte]=2026-03-01T00:00:00.000Z&order_by=updated_at:desc&limit=25' \
--header 'Authorization: sk-apik_<id>.<secret>' \
--header 'Accept: application/json'

This is useful for account-level timeline views where you want only the subset of engagements that match a particular relationship context.

There are three distinct patterns:

NeedRecommended call
Convenience aggregated engagement text in the engagement payloadGET /v2/engagements?...&expand=transcript
Bulk-fetch structured timelines for many engagements (up to 25 per page)GET /v2/engagements?...&expand=transcript_timeline&limit=25
Retrieve one structured timeline by engagement IDGET /v2/engagements/{engagement_id}/transcript_timeline

expand=transcript returns an aggregated engagement text string (or null when no text is available). Depending on the engagement, it can include Markdown or HTML plus extracted text from completed attachments, so it is not guaranteed to contain only spoken transcript. It does not include speaker identity, timing, or sentence boundaries. Use it only for quick previews or full-text search over a single engagement.

For speaker-aware ingest — joining transcript text to workspace users or contacts, building per-speaker timelines, or exporting timed entries — use expand=transcript_timeline for paginated bulk reads or GET /v2/engagements/{engagement_id}/transcript_timeline for one engagement. Do not use expand=transcript as your primary ingest path.

expand=transcript_timeline returns the same structured transcript_timeline object as the per-engagement endpoint, embedded on each engagement in the list response. Engagements without a primary timeline return transcript_timeline: null. This expand requires transcript_timelines:read in addition to engagements:read. Each list request counts as one read against workspace rate limits. Pages with this expand are capped at 25 engagements, even when a larger limit is supplied.

For large backfills, paginate engagements with expand=transcript_timeline and limit=25 instead of issuing one timeline GET per engagement. See Bulk exports and incremental syncs for checkpoint and pagination guidance.

Example: search engagements and include plaintext transcript expansion:

curl --request GET \
--globoff \
--url 'https://app.askelephant.ai/api/v2/engagements?filter[contact_ids][eq]=cnt_01HQY3JMS2QAXJGX6X7CH7CM6X&filter[processing_status][eq]=COMPLETED&expand=transcript,contacts,companies&order_by=updated_at:desc&limit=10' \
--header 'Authorization: sk-apik_<id>.<secret>' \
--header 'Accept: application/json'

Example: bulk-fetch structured timelines for a page of engagements:

curl --request GET \
--globoff \
--url 'https://app.askelephant.ai/api/v2/engagements?filter[processing_status][eq]=COMPLETED&expand=transcript_timeline&order_by=start_at:asc&limit=25' \
--header 'Authorization: sk-apik_<id>.<secret>' \
--header 'Accept: application/json'

To fetch a single engagement’s timeline without listing, use the dedicated endpoint:

curl --request GET \
--url 'https://app.askelephant.ai/api/v2/engagements/ngmt_01JWAS.../transcript_timeline' \
--header 'Authorization: sk-apik_<id>.<secret>' \
--header 'Accept: application/json'

Structured timeline reads require the transcript_timelines:read scope. If the engagement is private, they also require engagements:read and engagements:private:read.

CRM-first integration

  1. Start with filter[crm_associations][eq]
  2. Add filter[start_at][gte] or filter[updated_at][gte] if you are narrowing to recent activity
  3. Use expand=contacts,companies,owner if you want richer context in the search response
  4. Use expand=transcript_timeline when you need structured timelines for many engagements, or fetch /transcript_timeline for individual engagement IDs

Contact activity timeline

  1. Use filter[contact_ids][eq]
  2. Add filter[processing_status][eq]=COMPLETED if you only want engagements with completed processing
  3. Sort with order_by=updated_at:desc
  4. Use expand=transcript_timeline to load structured timelines for the selected meetings or calls

Company account review

  1. Use filter[company_ids][eq] or filter[company_ids][in]
  2. Add filter[engagement_type][in]=MEETING,CALL
  3. Expand companies,contacts,owner,tags
  4. Use expand=transcript_timeline to load structured timelines for the specific engagements that matter

Practical notes

  • search only matches engagement titles. It does not search transcript text, contact names, company names, descriptions, or CRM fields.
  • filter[crm_associations][eq] supports up to 20 CRM association objects.
  • filter[company_ids][in] and filter[contact_ids][in] support up to 25 values each.
  • expand=contacts additionally requires contacts:read.
  • expand=owner additionally requires users:read.
  • engagements:read returns only non-private engagements; include engagements:private:read if your integration must read private engagements too.
  • Use next_cursor to continue large result sets.

See also: Pagination and filtering