API

Filtering contacts

The where filter DSL for the contacts endpoints — every key, its value shapes, and how conditions combine.

The contacts endpoints accept a structured where object that lets you filter your network by almost any attribute — tags, groups, company, activity dates, custom fields, and more. This page documents every key the where DSL accepts, the value shapes each one takes, and how conditions combine.

Which endpoints use where

Endpointwhere goes inNotes
POST /v1/contacts/filterJSON request bodyRecommended for anything non-trivial. No length limit on the filter.
POST /v2/contacts/filterJSON request bodySame DSL; the response also returns a filterHash fingerprint.
GET /v1/contactsquery stringBest for short, simple filters — long/nested filters hit URL-length limits.
GET /v2/contactsquery stringSame as above.
GET /v1/contacts/count · GET /v2/contacts/countquery stringReturns a count of matching contacts instead of the rows.
On POST /contacts/filter, put where (and the pagination keys take, skip, cursor, plus orderBy, include, select) in the JSON request body. The auto-generated endpoint reference currently lists these under "Query parameters" because the route shares a schema with the list endpoint — but the filter handler reads them from the body. When in doubt, prefer POST /contacts/filter with a JSON body for the full DSL below.

How where works

where is a flat object. Every top-level key is combined with logical AND — a contact must satisfy all of them to be returned. To express OR, use the _or key.

{
  "where": {
    "has_company": "acme",
    "is_starred": true
  }
}

Returns starred contacts whose company contains "acme".

Value-shape conventions

Most keys accept more than one value shape. The shape you pass changes what the key means:

You passMeaning
A bare string on a text field (has_company, has_name, …)Case-insensitive contains (fuzzy) match
{ "contains": "…" }Explicit fuzzy contains
{ "eq": "…" }Exact match (case-sensitive equality)
A boolean on a has_* keyPresence check — true = the field is set, false = it is empty / NULL
An array of ids (tags, groups, sources)Match any of them (OR)
{ "not": … }Exclude — negates the match
{ "gte": "…", "lte": "…" } (ISO 8601)Date range (supply at least one bound)
An array of arrays (has_tags_all, has_groups_all)AND across rows, OR within a row

All dates are ISO 8601 datetimes, e.g. "2026-01-31T00:00:00Z". All ids are UUIDs.

Filter keys

Identity & flags

KeyValue shape(s)Filters by
inuuid · uuid[]Only these contact ids
not_inuuid · uuid[]Everything except these contact ids
is_starredbooleanStarred / not starred
is_archivedbooleanArchived / not archived
ignore_mergebooleanContacts flagged to be ignored by merge

Names & text profile fields

Text fields accept a bare string (fuzzy contains), an explicit { contains } / { eq } matcher, or a boolean presence check. has_name additionally accepts a string array for an exact multi-value match.

KeyValue shape(s)Filters by
has_namestring (contains) · string[] (exact list) · { contains } · { eq }Full name
has_companystring · boolean · { contains } · { eq }Company
has_job_titlestring · boolean · { contains } · { eq }Job title
has_educationstring · boolean · { contains } · { eq }Education
has_descriptionstring · boolean · { contains } · { eq }Description / bio
has_linkedin_companiesstring · boolean · { contains } · { eq }LinkedIn-derived companies
has_linkedin_educationstring · boolean · { contains } · { eq }LinkedIn-derived education
has_locationstring (fuzzy) · booleanLocation text
has_location_exactstringLocation, exact match

Each text field has a negated twin that excludes contacts whose value contains the given string(s):

KeyValue shape(s)
not_has_name · not_has_company · not_has_job_title · not_has_education · not_has_description · not_has_linkedin_companies · not_has_linkedin_educationstring · string[]

Contact info & birthday presence

KeyValue shape(s)Filters by
has_emailbooleanHas at least one email address
has_phone_numberbooleanHas at least one phone number
has_birthdayboolean · ISO datetimetrue = any birthday set; a date matches that day + month

Social handles

Bare strings match the social slug exactly; booleans check presence.

KeyValue shape(s)Filters by
has_linkedinstring (exact slug) · booleanLinkedIn
has_twitterstring · booleanTwitter / X
has_instagramstring · booleanInstagram

Tags

KeyValue shape(s)Filters by
has_taguuidA single tag id
has_tagsuuid[] (OR) · { "not": uuid | uuid[] }Any of the given tag ids, or exclude them
has_tags_alluuid[][]Array of arrays — AND across rows, OR within each row

has_tags_all is how you express "tagged A and B". Each inner array is an OR group; the outer array ANDs the groups together:

{ "has_tags_all": [ ["tagA"], ["tagB", "tagC"] ] }

Has tag A and (tag B or tag C).

Groups

KeyValue shape(s)Filters by
has_groupsuuid · string[] (OR) · boolean (≥ 1 group) · { "not": string | string[] }Group membership
has_groups_alluuid[][]Array of arrays — AND across rows, OR within each row (same shape as has_tags_all)

Source, priority & cadence

KeyValue shape(s)Filters by
has_sourcestring · string[]Contact source (import origin)
has_priority_tiernumber (≥ 1) · number[] (OR) · booleanRelationship priority tier; true = any tier set, false = none
has_frequencystring · booleanKeep-in-touch cadence
has_never_keep_in_touchbooleanMarked "never keep in touch"

Activity & date ranges

Each of these accepts a { gte, lte } ISO datetime range (supply at least one bound). The has_last_seen_at, has_interaction, has_next_reminder, and has_linkedin_last_message_at keys also accept a boolean presence check.

KeyValue shape(s)Filters by
has_created_at{ gte, lte }When the contact was created
has_updated_at{ gte, lte }When the contact was last updated
has_last_seen_atboolean · { gte, lte }Last interaction time (alias of has_interaction)
has_interactionboolean · { gte, lte }Interaction recency
has_next_reminderboolean · { gte, lte }Upcoming reminder date
has_linkedin_last_message_atboolean · { gte, lte }Last LinkedIn message

Geographic distance

KeyValue shape(s)Filters by
has_distance{ "lat": number, "lon": number, "meters": number }Contacts within meters of the point

Custom fields

Recently added.custom_fields filtering is rolling out across the API. Use the latest API host and confirm behavior on a small query before relying on it in production.

custom_fields filters by your user-defined fields without inflating the schema with one key per field. It takes an array of clauses (at least one); each clause is { field_id, operator, value? } and all clauses AND together.

operatorvalue shapeMeaning
containsstringFuzzy contains (text fields)
eqstringExact match
instring[]Value is any of these
not_instring[]Value is none of these
present(omit)The field has a value
absent(omit)The field is empty
gteISO datetime stringOn or after (date fields)
lteISO datetime stringOn or before (date fields)
{
  "custom_fields": [
    { "field_id": "", "operator": "eq", "value": "VIP" },
    { "field_id": "", "operator": "present" }
  ]
}

field_id is the id of the custom field — fetch it from the custom fields endpoints.

Compound OR

Recently added. The _or compound-OR key is rolling out across the API.

By default every top-level key is ANDed. Use _or when you genuinely need (A AND B) OR (C AND D). It takes an array of filter sets (at least one), and a contact matches if it satisfies any branch. Each branch is a full filter set, but _or cannot nest inside a branch — one level deep only. Deeper logic can always be flattened to distributive form: (A OR B) AND C becomes _or: [ {A, C}, {B, C} ].

{
  "where": {
    "_or": [
      { "is_starred": true },
      { "has_tags": ["…tag-id…"] }
    ]
  }
}

Starred or tagged with the given tag.

Examples

All examples authenticate with an API key as a Bearer token. See Authentication for where to find your key. The base URL is https://api.prod.getdex.com.

Tag AND-composition

Contacts that have (tag A or tag B) and tag C:

curl https://api.prod.getdex.com/v1/contacts/filter \
  --request POST \
  --header 'Authorization: Bearer dex_your_api_key' \
  --header 'Content-Type: application/json' \
  --data '{
    "where": {
      "has_tags_all": [
        ["11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222"],
        ["33333333-3333-3333-3333-333333333333"]
      ]
    },
    "take": 100
  }'

Company + last-seen date range

Contacts at a company matching "acme" whom you last interacted with in H1 2026:

curl https://api.prod.getdex.com/v1/contacts/filter \
  --request POST \
  --header 'Authorization: Bearer dex_your_api_key' \
  --header 'Content-Type: application/json' \
  --data '{
    "where": {
      "has_company": { "contains": "acme" },
      "has_last_seen_at": {
        "gte": "2026-01-01T00:00:00Z",
        "lte": "2026-06-30T23:59:59Z"
      }
    }
  }'

Custom-field filter

Contacts whose "Tier" custom field equals "VIP" and whose "Renewal" date is on or after Jan 1 2026:

curl https://api.prod.getdex.com/v1/contacts/filter \
  --request POST \
  --header 'Authorization: Bearer dex_your_api_key' \
  --header 'Content-Type: application/json' \
  --data '{
    "where": {
      "custom_fields": [
        { "field_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "operator": "eq", "value": "VIP" },
        { "field_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "operator": "gte", "value": "2026-01-01T00:00:00Z" }
      ]
    }
  }'

Compound OR

Contacts that are starred or are in a given group and have an email on file:

curl https://api.prod.getdex.com/v1/contacts/filter \
  --request POST \
  --header 'Authorization: Bearer dex_your_api_key' \
  --header 'Content-Type: application/json' \
  --data '{
    "where": {
      "_or": [
        { "is_starred": true },
        {
          "has_groups": ["44444444-4444-4444-4444-444444444444"],
          "has_email": true
        }
      ]
    }
  }'

Response

The filter and list endpoints return the standard envelope with a data.items array, a data.count, and a data.nextCursor for pagination:

{
  "error": false,
  "data": {
    "items": [ { "id": "", "full_name": "" } ],
    "count": 1,
    "nextCursor": null
  }
}

For the full response shape and all available fields, see the endpoint reference: Filter contacts (v1) · List contacts (v2).

Copyright © 2026