Pagination and filtering
The live collection endpoints use cursor pagination with a hybrid query model:
GET /v2/contactsGET /v2/companiesGET /v2/usersGET /v2/tagsGET /v2/engagements- Query params:
limit,cursor,searchwhen supported, structuredfilter[...], andorder_by
Query parameters
Response fields
Example
To request the next page:
Engagement filters
GET /v2/engagements supports these engagement-specific filters on top of the shared list query contract:
Engagement filtering follows MCP-style bucket semantics:
company_idsand CRM associations that resolve to companies share one company bucket.contact_idsand CRM associations that resolve to contacts share one participant bucket.- CRM associations that resolve to deals/projects populate a project bucket.
Values within the same bucket use OR semantics. Populated buckets combine with AND semantics.
For readability, docs examples use indexed bracket syntax. One CRM company association looks like:
When using raw filter[...] query params with curl, add --globoff so curl does not interpret [] as URL globbing syntax.
That corresponds to this object:
For GET /v2/engagements, the common CRM object filters are:
- 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" }
Equivalent JSON-array form:
filter[company_ids][in] and filter[contact_ids][in] support up to 25 values each. filter[crm_associations][eq] supports up to 20 objects.
CRM source is inferred from the workspace’s connected CRM state. The API resolves local CRM links first, and unresolved CRM associations do not erase otherwise valid direct company_ids or contact_ids filters in the same bucket.
For a full walkthrough, see Filter engagements by CRM object IDs.
Engagement example
Why cursor pagination
Cursor pagination is the live pattern because it is more stable than offset pagination for large, frequently changing datasets.
For incremental sync, keep the last seen updated_at checkpoint and continue walking pages with next_cursor until has_more is false.