Skip to main content
Find contacts by email address, custom field values, or other criteria using the search API.

Goal

By the end of this guide you will be able to search for contacts by email (useful for dedup checks), filter by custom field values, and paginate through large result sets.

Prerequisites

  • An API key with contacts:read scope
  • Your API base URL (found on the Settings > API Keys page)
  • A contact structure ID — search operates within a single contact structure. Retrieve yours with GET /api/contact-structure (see Manage Custom Fields)
Scope note: Contact search uses POST but is semantically a read operation. It requires only contacts:read scope (not contacts:write).

How search requests work

Every search request has two main parts: a filter that decides which contacts match, and a source array that decides which fields to return for each matching contact.

The source array

source is a required projection that tells the API which fields to return in each contact. Pass an array containing one or more of these case-sensitive values:
  • _id: the contact’s unique identifier
  • key: the contact’s email address
  • fields: custom field values
  • tags: tag assignments
  • lists: list memberships
  • contactStatus: primary status (Active, Inactive, etc.)
  • contactSubStatus: secondary status
  • createdAt: creation timestamp
  • updatedAt: last-update timestamp
Rules:
  • source is required and must contain at least one value.
  • Values are case-sensitive. _id works; _ID returns a validation error.
  • _id is always returned, even when you don’t include it in source.
  • Any unknown value rejects the entire request. There’s no partial success.

Steps

1. Search by email address

This is the most common search pattern, useful for deduplication before creating a contact.
curl -X POST "<your-api-base-url>/api/contact/search" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v" \
  -H "Content-Type: application/json" \
  -d '{
    "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
    "page": 1,
    "pageSize": 10,
    "source": ["_id", "key", "fields", "createdAt", "updatedAt"],
    "contactSpecification": {
      "filters": [
        {
          "criterias": [
            {
              "columnToFilter": "KEY",
              "operator": "EQ",
              "values": ["jane.smith@example.com"]
            }
          ]
        }
      ]
    }
  }'
Response (200 OK):
{
  "contacts": [
    {
      "_id": "66f1a6e4698f1bca60424901",
      "key": "jane.smith@example.com",
      "fields": [
        { "_id": "64a1b2c3d4e5f6a7b8c9d100", "value": "Jane" },
        { "_id": "64a1b2c3d4e5f6a7b8c9d101", "value": "Smith" }
      ],
      "createdAt": "2026-03-28T14:30:00.000Z",
      "updatedAt": "2026-03-28T14:30:00.000Z",
      "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1"
    }
  ],
  "totalRecords": 1
}
Dedup pattern: Before creating a contact, search by email using "operator": "EQ". If totalRecords is 0, the email is not in use and you can safely create the contact.

2. Search by email domain

Find all contacts from a specific email domain.
curl -X POST "<your-api-base-url>/api/contact/search" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v" \
  -H "Content-Type: application/json" \
  -d '{
    "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
    "page": 1,
    "pageSize": 25,
    "source": ["_id", "key", "fields"],
    "contactSpecification": {
      "filters": [
        {
          "criterias": [
            {
              "columnToFilter": "EMAIL_DOMAIN",
              "operator": "EQ",
              "values": ["example.com"]
            }
          ]
        }
      ]
    }
  }'

3. Search by custom field values

Filter contacts based on custom field values. Use the field’s _id from your contact structure.
curl -X POST "<your-api-base-url>/api/contact/search" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v" \
  -H "Content-Type: application/json" \
  -d '{
    "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
    "page": 1,
    "pageSize": 25,
    "source": ["_id", "key", "fields", "tags"],
    "contactSpecification": {
      "filters": [
        {
          "criterias": [
            {
              "columnToFilter": "FIELD_ID",
              "id": "64a1b2c3d4e5f6a7b8c9d102",
              "operator": "EQ",
              "values": ["Enterprise"]
            }
          ]
        }
      ]
    }
  }'
Response (200 OK):
{
  "contacts": [
    {
      "_id": "66f1a6e4698f1bca60424905",
      "key": "alice@enterprise-corp.com",
      "fields": [
        { "_id": "64a1b2c3d4e5f6a7b8c9d100", "value": "Alice" },
        { "_id": "64a1b2c3d4e5f6a7b8c9d101", "value": "Chen" },
        { "_id": "64a1b2c3d4e5f6a7b8c9d102", "value": "Enterprise" }
      ],
      "tags": [
        { "_id": "64a1b2c3d4e5f6a7b8c9d300" }
      ],
      "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1"
    }
  ],
  "totalRecords": 1
}

4. Combine multiple criteria

Use multiple criteria objects to build complex filters. Criteria within the same group are AND-ed; separate groups are OR-ed.
curl -X POST "<your-api-base-url>/api/contact/search" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v" \
  -H "Content-Type: application/json" \
  -d '{
    "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
    "page": 1,
    "pageSize": 25,
    "source": ["_id", "key", "fields", "createdAt"],
    "contactSpecification": {
      "filters": [
        {
          "criterias": [
            {
              "columnToFilter": "FIELD_ID",
              "id": "64a1b2c3d4e5f6a7b8c9d102",
              "operator": "EQ",
              "values": ["Enterprise"]
            },
            {
              "columnToFilter": "CREATED_AT",
              "operator": "GT",
              "values": ["2026-01-01T00:00:00.000Z"]
            }
          ]
        }
      ]
    }
  }'
This searches for contacts where the custom field equals “Enterprise” AND the contact was created after January 1, 2026.

5. Search within a specific list

Filter contacts that belong to a specific list.
curl -X POST "<your-api-base-url>/api/contact/search" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v" \
  -H "Content-Type: application/json" \
  -d '{
    "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
    "page": 1,
    "pageSize": 25,
    "source": ["_id", "key", "fields"],
    "contactSpecification": {
      "filters": [
        {
          "criterias": [
            {
              "columnToFilter": "LIST_ID",
              "operator": "IN",
              "values": ["66f1a6e4698f1bca60425001"]
            }
          ]
        }
      ]
    }
  }'

6. Paginate through results

For large result sets, increment the page parameter to retrieve subsequent pages. Pages are 1-indexed.
# Page 1 (first 50 results)
curl -X POST "<your-api-base-url>/api/contact/search" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v" \
  -H "Content-Type: application/json" \
  -d '{
    "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
    "page": 1,
    "pageSize": 50,
    "source": ["_id", "key"],
    "contactSpecification": {
      "filters": [
        {
          "criterias": [
            {
              "columnToFilter": "CONTACT_STATUS",
              "operator": "EQ",
              "values": ["active"]
            }
          ]
        }
      ]
    },
    "sortField": [
      {
        "column": "CREATED_AT",
        "order": "desc"
      }
    ]
  }'
Response (200 OK):
{
  "contacts": [
    { "_id": "66f1a6e4698f1bca60424950", "key": "newest@example.com" },
    { "_id": "66f1a6e4698f1bca60424949", "key": "second@example.com" }
  ],
  "totalRecords": 3847
}
Use totalRecords to calculate the total number of pages: Math.ceil(totalRecords / pageSize). Then loop through pages 1 to N.

Request body reference

FieldRequiredDescription
contactStructureIdYesThe contact structure to search within
pageYesPage number (1-indexed)
pageSizeYesResults per page
sourceYesField projection. Pass an array of one or more case-sensitive values from: _id, key, fields, tags, lists, contactStatus, contactSubStatus, createdAt, updatedAt.
contactSpecificationYesObject with a filters array of filter groups (see operators below)
sortFieldNoArray of sort criteria
showFieldIdsNoLimit which field IDs appear in fields array

Available filter operators

OperatorDescriptionExample use
EQEqualsExact email match
NEQNot equalsExclude a specific value
SWStarts withEmail prefix search
CONTAINSContains substringPartial name match
GTGreater thanCreated after a date
LTLess thanCreated before a date
BETWEENBetween two valuesDate range
NOT_BETWEENNot between two valuesExclude a date range
IS_EMPTYField is emptyFind contacts with missing data
NOT_EMPTYField is not emptyFind contacts with data present
INIn a set of valuesMatch any of several list IDs
NINNot in a setExclude specific values

Available filter columns

ColumnDescription
KEYEmail address
EMAIL_DOMAINEmail domain (e.g., example.com)
FIELD_IDCustom field value (requires id parameter with the field’s _id)
TAG_IDTag assignment
LIST_IDList membership
CONTACT_STATUSPrimary status (active, inactive, etc.)
CONTACT_SUB_STATUSSecondary status
CREATED_ATCreation timestamp
UPDATED_ATLast update timestamp

Common errors

StatusErrorCauseFix
401UnauthorizedErrorInvalid or inactive API keyVerify your key in Settings > API Keys
403ForbiddenErrorKey lacks contacts:read scopeSearch requires contacts:read scope
400RequiredFieldErrorA required body field is missingEnsure contactStructureId, page, pageSize, source, and contactSpecification are all in the request body
400ModelValidationErrorA field’s value fails schema validationCheck the error’s field and message. For source, confirm it’s a non-empty array of valid, case-sensitive values
400ValidationErrorOther validation failure (e.g., bad filter operator)Read the message for specifics
429TooManyRequestsErrorRate limit exceededWait for the Retry-After period. See Rate Limits

Next steps