Filter contacts with a predicate

Returns a paginated list of the contacts matching an arbitrary predicate tree — the same engine that
powers segments, but evaluated on the fly without having to create a segment first.

Use GET /contacts when simple keyword/role/segment filtering is enough. Use this endpoint when you need
to combine several conditions with and/or, or to filter on attributes, tags, events or campaigns.

Pagination

This endpoint uses offset-based pagination:

  1. First request: POST /contacts/filter with { "per_page": 20 }
  2. The response returns a cursor (the offset of the next page, e.g. 20)
  3. Next page: repeat the same body adding "cursor": 20
  4. Continue until cursor is null (no more contacts)

cursor is an offset, not a contact ID. Keep filters, sort_by and order identical across pages,
otherwise the offset points at a different result set.

Sorting

  • sort_by: name or email. If omitted, contacts are sorted by internal ID (creation order).
  • order: ascending or descending (default: descending).

Counting before listing

To know how many contacts match a predicate — without paging through them — call
POST /contacts/filter/count with the same filters payload.

The filters field — Predicate Structure

filters is a predicate tree. Omit it (or send null) to match every contact.

Root object:

{
  "audience": "<audience_value>",
  "operation": { ... }
}

audience (required) — restricts the contact type:

  • "is_any_contact" (or "is_any") — all contacts, users and leads
  • "is_user" — only registered users
  • "is_lead" — only leads
  • "is_not_user" — equivalent to "is_lead", and "is_not_lead" to "is_user": a stored contact is
    always either a user or a lead
  • "is_visitor" — always matches nothing here: visitors are not stored contacts, they only exist while
    the messenger evaluates a predicate in real time

operation (optional) — a predicate node, either an operation or a condition:

Operation node (combines multiple predicates):

{
  "type": "operation",
  "operator": "and",
  "predicates": [ ... ]
}
  • operator: "and" or "or" (lowercase)
  • predicates: array of condition or operation nodes (they can be nested at any depth)

Condition node (single filter rule):

{
  "type": "condition",
  "target": "<target>",
  "operation": "<operation>",
  "value": "<value>"
}

Condition Targets and Operations

state — Contact attribute. Requires name (the attribute key, e.g. "plan", or a default contact
field such as "email", "name", "created_at"). The operation set depends on the value type:

  • string: is, is_not, starts_with, ends_with, contains, does_not_contain
  • number: greater_than, less_than, equal_to, not_equal
  • boolean: is_true, is_false
  • date: date_on, date_before, date_after, date_exactly, date_more_than, date_less_than,
    date_more_than_hours, date_less_than_hours, date_more_than_ahead, date_less_than_ahead,
    date_more_than_hours_ahead, date_less_than_hours_ahead
  • any type: has_any_value, is_unknown (no value needed)

For date_on, date_before and date_after, value is a date (timestamp or parsable date string). For
the other date operations, value is a number of days (or hours, for the _hours variants).

tag — Contact tag:

  • operation: is, is_not
  • value: tag name (e.g., "vip")

event — Custom event tracking. Requires name (the event name, e.g. "purchase"):

  • count: count_greater_than, count_less_than, count_is, count_is_not, count_has_any_value, count_is_unknown
  • first occurrence: first_on, first_before, first_after, first_exactly, first_more_than,
    first_less_than, first_more_than_hours, first_less_than_hours
  • last occurrence: same as first_* but with the last_* prefix
  • value: a number for the count operations, a date or a number of days/hours for the others
  • window (optional, count operations only): { "operation": "...", "value": ... } to restrict the count
    to a time window

segment — Membership in a contacts segment:

  • operation: is, is_not
  • value: segment_id (integer)

company_state — Attribute of a company the contact belongs to. Same name/operation/value
semantics as state.

campaign — Campaign engagement:

  • name: the campaign ID (integer)
  • operation: sent, not_sent, seen, not_seen, not_sent_or_not_seen, clicked, not_clicked,
    replied, not_replied
  • value (optional, clicked/not_clicked only): the clicked URL to match

campaigns_multiple — Same engagement operations as campaign, but across several campaigns at once:

  • names: array of campaign IDs (integers)
  • value (optional): the clicked URL to match

Unsupported targets

The predicate language also has targets that cannot be evaluated against stored contacts: the real-time
messenger ones (page, stats, platform, office_hours, incoming_message), the company-scoped ones
(company_type, companies_segment) and every conversation_* target. Using them here returns
422 contact_invalid_filters.

Examples

Example 1 — First page of registered users tagged vip, sorted by name:

{
  "per_page": 50,
  "sort_by": "name",
  "order": "ascending",
  "filters": {
    "audience": "is_user",
    "operation": {
      "type": "condition",
      "target": "tag",
      "operation": "is",
      "value": "vip"
    }
  }
}

Example 2 — Contacts on the premium plan who purchased at least once:

{
  "filters": {
    "audience": "is_any_contact",
    "operation": {
      "type": "operation",
      "operator": "and",
      "predicates": [
        { "type": "condition", "target": "state", "name": "plan", "operation": "is", "value": "premium" },
        { "type": "condition", "target": "event", "name": "purchase", "operation": "count_greater_than", "value": 0 }
      ]
    }
  }
}

Example 3 — Contacts in segment 10 but not in segment 20, second page:

{
  "per_page": 20,
  "cursor": 20,
  "filters": {
    "audience": "is_any_contact",
    "operation": {
      "type": "operation",
      "operator": "and",
      "predicates": [
        { "type": "condition", "target": "segment", "operation": "is", "value": 10 },
        { "type": "condition", "target": "segment", "operation": "is_not", "value": 20 }
      ]
    }
  }
}

Example 4 — Contacts created less than 30 days ago who never opened a campaign:

{
  "filters": {
    "audience": "is_any_contact",
    "operation": {
      "type": "operation",
      "operator": "and",
      "predicates": [
        { "type": "condition", "target": "state", "name": "created_at", "operation": "date_less_than", "value": 30 },
        { "type": "condition", "target": "campaign", "name": 1234, "operation": "not_seen" }
      ]
    }
  }
}
Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…
Body Params
filters
object

Predicate tree defining which contacts to return. Omit to match every contact. See the endpoint description for the full schema.

integer
1 to 100
Defaults to 20

Number of contacts per page (1-100)

integer

Offset of the page to return, taken from the previous response's cursor. Omit for the first page.

string
enum

Field to sort results by. If omitted, contacts are sorted by internal ID.

Allowed:
string
enum
Defaults to descending

Sort direction

Allowed:
Responses

401

Authentication credentials are missing or invalid

403

You don't have permission to access contacts

422

Validation error: malformed or unsupported filters, or invalid pagination/sorting parameters

Language
Credentials
Bearer
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json