Skip to main content

List Contacts

Retrieve a paginated list of contacts with optional filtering.

Request

GET /contacts

Query Parameters

All parameters are optional and combined with AND.

ParameterTypeDefaultDescription
pageinteger1Page number (min: 1)
per_pageinteger100Items per page (min: 1, max: 100)
searchstring-If valid email: exact match on email. Otherwise: partial match on first_name, last_name, phone, or full name (max 255 characters)
emailstring-Exact email match (case-insensitive). Unique within the account, so this returns at most one contact.
external_idstring-Exact external_id match. Unique within the account, so this returns at most one contact.
country_codestring-Exact ISO 3166-1 alpha-2 country code.
languagestring-Exact ISO 639-1 alpha-2 language code.
timezone_uuidstring-Contacts assigned to the timezone with this UUID. Get the UUID from List Timezones.
list_uuidstring-Members of the list with this UUID. The list must belong to your account, otherwise 422 VALIDATION_ERROR.
not_in_list_uuidstring-Non-members of the list with this UUID. The list must belong to your account, otherwise 422 VALIDATION_ERROR.
statusstring-Subscription status: subscribed, unsubscribed, or suppressed (see below).
created_fromstring (YYYY-MM-DD)-Contacts created on or after this date (inclusive).
created_tostring (YYYY-MM-DD)-Contacts created on or before this date (inclusive). Must be on or after created_from when both are given.
updated_fromstring (YYYY-MM-DD)-Contacts last updated on or after this date (inclusive).
updated_tostring (YYYY-MM-DD)-Contacts last updated on or before this date (inclusive). Must be on or after updated_from when both are given.
engaged_sincestring (YYYY-MM-DD)-Contacts who opened or clicked at least one email on or after this date (inclusive). See below.
not_engaged_sincestring (YYYY-MM-DD)-Contacts with no open or click since this date, including those who have never engaged at all. Cannot be combined with engaged_since. See below.
engagement_typestring-Restrict engaged_since / not_engaged_since to opened or clicked only. Omit to count either.
sortstring-created_atSort order: created_at, updated_at, email, or name. Prefix with - for descending.
custom_field[<key>][<op>]string-Filter by custom-field value. Up to 3 (see below).

Malformed dates, an out-of-order date range, an unknown/foreign list_uuid or not_in_list_uuid, or any invalid custom-field filter return 422 VALIDATION_ERROR.

Ordering: Results default to created_at descending (newest first) unless sort is supplied.

Counting matches: every response includes the filtered total in pagination.total. To get only a count, request per_page=1 with your filters and read pagination.total.

Custom-field filters

Filter by custom-field values with the bracket syntax custom_field[<field_key>][<operator>]=<value>. Up to 3 custom-field filters may be combined. The allowed operators depend on the field's type:

Field typeOperators
text, selectequals, not_equals, contains, not_contains, is_not_empty, is_empty
numberequals, not_equals, greater_than, greater_than_or_equal, less_than, less_than_or_equal, between
date, datetimeequals, not_equals, greater_than, less_than, between
booleanequals, not_equals (true / false)
listcontains, not_contains, is_empty

Reference each field by its field_key, as returned by List Custom Fields.

  • between takes two comma-separated values, e.g. custom_field[age][between]=18,65.
  • is_not_empty / is_empty ignore the value; is_empty matches contacts with no value or an empty value for the field.
  • not_equals / not_contains are the exact complement of equals / contains: they match every contact the positive operator would not, including contacts that have no value for the field. For example, custom_field[plan][not_equals]=pro returns every contact whose plan is not exactly pro, contacts with no plan value included.
  • date and datetime values use YYYY-MM-DD; datetime fields are matched at day granularity (the time component is ignored).

An unrecognised field_key, an operator not allowed for the field's type, a malformed value, or more than 3 custom-field filters return 422 VALIDATION_ERROR.

Subscription status

status filters contacts by whether they can be mailed:

ValueMatches
subscribedContacts that can receive mail — neither unsubscribed nor undeliverable.
unsubscribedContacts who have opted out of all mail.
suppressedUndeliverable contacts (for example, after a permanent bounce), held back from sending regardless of consent.

subscribed is the audience that would actually receive a send. It does not take list membership into account — combine it with list_uuid to get the mailable members of a specific list: status=subscribed&list_uuid=70dbd819-fac2-4dcf-b655-fb925e24410b.

These values are not mutually exclusive — a contact can be both unsubscribed and suppressed — so don't sum the pagination.total of separate status queries to get a grand total.

An unknown status value returns 422 VALIDATION_ERROR.

Engagement

engaged_since and not_engaged_since filter contacts by when they last opened or clicked an email.

ParameterMatches
engaged_sinceContacts who opened or clicked at least one email on or after the given date (inclusive).
not_engaged_sinceContacts who have not opened or clicked any email since the given date — including contacts who have never opened or clicked at all.

Both take a YYYY-MM-DD date and cannot be combined; supplying both returns 422 VALIDATION_ERROR.

By default both opens and clicks count toward engagement. Add engagement_type=opened or engagement_type=clicked to count only one signal — for example, engaged_since=2026-06-01&engagement_type=clicked returns contacts who clicked on or after June 1. An unknown engagement_type returns 422 VALIDATION_ERROR.

To retrieve a contact's list memberships and custom-field values, use Get Contact, which returns both — along with the contact's subscription, deliverability, and engagement details.

Example Requests

Basic search:

curl -X GET "https://email.easy.tools/api/v1/contacts?page=1&per_page=50&search=john" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"

Exact lookup by your own identifier:

curl -X GET "https://email.easy.tools/api/v1/contacts?external_id=crm-4821" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"

Contacts updated in a date range, newest-updated first:

curl -X GET "https://email.easy.tools/api/v1/contacts?updated_from=2026-01-01&updated_to=2026-03-31&sort=-updated_at" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"

Filter by custom fields (VIP customers aged 18+):

curl -X GET "https://email.easy.tools/api/v1/contacts?custom_field[customer-type][equals]=vip&custom_field[age][greater_than_or_equal]=18" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"

Count contacts not in a list:

curl -X GET "https://email.easy.tools/api/v1/contacts?not_in_list_uuid=a3d8f1c6-2b9e-4d7a-8c5f-1e6b4a9d2c30&per_page=1" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"

Mailable members of a specific list:

curl -X GET "https://email.easy.tools/api/v1/contacts?status=subscribed&list_uuid=70dbd819-fac2-4dcf-b655-fb925e24410b" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"

Contacts who clicked since a given date:

curl -X GET "https://email.easy.tools/api/v1/contacts?engaged_since=2026-06-01&engagement_type=clicked" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"

Count contacts with no opens or clicks since a date, still subscribed:

curl -X GET "https://email.easy.tools/api/v1/contacts?not_engaged_since=2026-03-01&status=subscribed&per_page=1" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"

Response

Success Response (200 OK)

{
"data": [
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"phone": "+48123456789",
"external_id": "customer_123",
"language": "en",
"country_code": "US",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
},
{
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"email": "john.smith@example.com",
"first_name": "John",
"last_name": "Smith",
"phone": null,
"external_id": null,
"language": "pl",
"country_code": null,
"created_at": "2025-01-14T09:00:00Z",
"updated_at": "2025-01-14T09:00:00Z"
}
],
"pagination": {
"current_page": 1,
"per_page": 50,
"total": 2
}
}

Response Fields

FieldTypeDescription
uuidstringContact's unique identifier
emailstringContact's email address
first_namestring|nullContact's first name
last_namestring|nullContact's last name
phonestring|nullContact's phone number
external_idstring|nullExternal identifier for integration purposes
languagestring|nullISO 639-1 language code (2 characters)
country_codestring|nullISO 3166-1 alpha-2 country code (2 characters)
created_atstringISO 8601 timestamp (UTC)
updated_atstringISO 8601 timestamp (UTC)

Pagination Object

FieldTypeDescription
current_pageintegerCurrent page number
per_pageintegerNumber of items per page
totalintegerTotal number of contacts

Error Responses

Validation Error (422 Unprocessable Entity)

{
"error": {
"code": "VALIDATION_ERROR",
"message": "The given data was invalid",
"details": {
"per_page": ["The per page field must not be greater than 100."],
"list_uuid": ["The list uuid field must be a valid UUID."]
}
}
}