List Contacts
curl --request GET \
--url https://app.cometly.com/public-api/v1/contacts \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.cometly.com/public-api/v1/contacts"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.cometly.com/public-api/v1/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.cometly.com/public-api/v1/contacts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.cometly.com/public-api/v1/contacts"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.cometly.com/public-api/v1/contacts")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.cometly.com/public-api/v1/contacts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": [
{}
],
"path": "<string>",
"per_page": 123,
"next_cursor": {},
"next_page_url": {},
"prev_cursor": {},
"prev_page_url": {},
"message": "<string>"
}Contacts
List Contacts
Retrieve a paginated list of contacts filtered by creation date or by email address
GET
/
public-api
/
v1
/
contacts
List Contacts
curl --request GET \
--url https://app.cometly.com/public-api/v1/contacts \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.cometly.com/public-api/v1/contacts"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.cometly.com/public-api/v1/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.cometly.com/public-api/v1/contacts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.cometly.com/public-api/v1/contacts"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.cometly.com/public-api/v1/contacts")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.cometly.com/public-api/v1/contacts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": [
{}
],
"path": "<string>",
"per_page": 123,
"next_cursor": {},
"next_page_url": {},
"prev_cursor": {},
"prev_page_url": {},
"message": "<string>"
}Overview
This endpoint allows you to retrieve a paginated list of contacts from Cometly within a specific date range, by email address, or both. Each contact includes primary data from the profile (email, name, phone, location). For complete contact information including all associated emails, phones, names, and locations from merged profiles, use the individual contact endpoint GET /contacts/. Results are cursor-paginated for efficient data retrieval and ordered by ID (newest first).Query Parameters
Filter Parameters
string[]
Filter contacts by email address. Accepts a single address (
email=jane@acme.com), a comma-separated list (email=a@x.com,b@y.com), or a repeated array parameter (email[]=a@x.com&email[]=b@y.com). Maximum 100 addresses per request.Matching is exact (not partial or fuzzy) and case-insensitive; surrounding whitespace is trimmed. It matches both the contact’s primary address and any of its alternate addresses, including addresses captured on profiles that were later merged into the contact — so an address that only ever appeared on a merged-away profile still finds the surviving contact. Several distinct contacts can legitimately share an address; all of them are returned.You can combine email with start_date / end_date — when both are present, a contact must match the email and fall inside the creation window.Mapping results back to what you asked for: a contact matched on one of its alternate addresses is returned with its primary address in email, which is a different value from the one you queried. Without include_all_emails=1 the address you searched for appears nowhere in the row, so batched lookups cannot be joined back to your input list. Pass include_all_emails=1 whenever you send more than one address.string
Start date and time for filtering contacts by creation date in
YYYY-MM-DD HH:MM:SS format. The date is interpreted in your space’s timezone.Required unless email is supplied. When present, end_date must also be present — sending only one returns a 422.Example: 2024-01-15 00:00:00string
End date and time for filtering contacts by creation date in
YYYY-MM-DD HH:MM:SS format. Must be after start_date. The date is interpreted in your space’s timezone.Required unless email is supplied. When present, start_date must also be present — sending only one returns a 422.Example: 2024-01-15 23:59:59Optional Parameters
integer
default:"200"
Number of contacts to return per page. Minimum: 1, Maximum: 5000
string
Pagination cursor from a previous response. Use this to fetch the next page of results.
boolean
default:"0"
When set to
1, includes the last 5 comet tokens for each contact, ordered by most recent first. Accepts 1 or 0.boolean
default:"0"
When set to
1, includes all email addresses associated with the contact (including emails from merged profiles). Accepts 1 or 0.boolean
default:"0"
When set to
1, includes all 30 custom field columns for each contact. Fields 1-15 are text, 16-25 are numeric, 26-30 are date. Accepts 1 or 0.boolean
default:"0"
When set to
1, custom field keys in the response will use the user-defined labels (e.g. "Customer Age") instead of the raw column names (e.g. "profile_field_1"). Only applies when include_custom_fields=1. Fields without a configured label will keep their raw column name. Accepts 1 or 0.array
Filter contacts by their custom profile field values. Array of AND-joined conditions. Each condition is
{ "field": "profile_field_N", "operator": "<op>", "value": <val> }, or for ranges { "field": "profile_field_N", "operator": "between", "start": <a>, "end": <b> }. The field must reference a custom field slot that has been configured with a label in your space (e.g. profile_field_5). Maximum 10 conditions per request.Operator vocabulary by field type:| Slot range | Type | Valid operators |
|---|---|---|
profile_field_1 – profile_field_15 | text | equal_to, not_equal_to, contains, not_contains, starts_with, ends_with, contains_word, any, unknown |
profile_field_16 – profile_field_25 | number | equal_to, not_equal_to, greater_than, greater_than_or_equal_to, less_than, less_than_or_equal_to, any, unknown |
profile_field_26 – profile_field_30 | date | equal_to, between, greater_than, less_than, age_greater_than, age_equal_to, age_less_than, any, unknown |
any matches contacts where the field has a value; unknown matches contacts where the field is empty or unset. For numeric fields, any requires a value greater than 0 — unknown matches 0 or unset. Date comparisons (equal_to, greater_than, less_than, between) are evaluated in your space’s configured timezone; equal_to matches the full calendar day. not_equal_to and not_contains do not include contacts where the field is unset — add a separate unknown condition if you need those.Example query string: custom_field_filters[0][field]=profile_field_5&custom_field_filters[0][operator]=equal_to&custom_field_filters[0][value]=PremiumResponse
Success Response
The response follows Laravel’s cursor pagination structure:array
Array of contact objects. Each contact includes:
id(integer): The unique identifier of the contactemail(string|null): Primary email addressname(string|null): Primary namephone(string|null): Primary phone numberlocation(string|null): Primary locationcomet_tokens(array): Array of comet tokens (only included wheninclude_comet_tokens=1)emails(array): Array of all email addresses associated with the contact, including emails from merged profiles (only included wheninclude_all_emails=1)profile_field_1throughprofile_field_30: Custom field values (only included wheninclude_custom_fields=1). Fields 1-15 are text (string|null), 16-25 are numeric (number|null), 26-30 are date (string|null)
string
The base URL path for the endpoint
integer
Number of items per page
string | null
Cursor for the next page of results.
null if there are no more pages.string | null
Full URL for the next page of results.
null if there are no more pages.string | null
Cursor for the previous page of results.
null if on the first page.string | null
Full URL for the previous page of results.
null if on the first page.Error Response
string
Error description explaining what went wrong
Example Requests
# Basic request
curl -G "https://app.cometly.com/public-api/v1/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data-urlencode "start_date=2024-01-15 00:00:00" \
--data-urlencode "end_date=2024-01-15 23:59:59"
# Include comet tokens
curl -G "https://app.cometly.com/public-api/v1/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data-urlencode "start_date=2024-01-15 00:00:00" \
--data-urlencode "end_date=2024-01-15 23:59:59" \
-d "include_comet_tokens=1"
# Include all emails
curl -G "https://app.cometly.com/public-api/v1/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data-urlencode "start_date=2024-01-15 00:00:00" \
--data-urlencode "end_date=2024-01-15 23:59:59" \
-d "include_all_emails=1"
# Look up contacts by email (no date range required; batch up to 100 addresses)
curl -G "https://app.cometly.com/public-api/v1/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d "email=jane@acme.com,john@acme.com"
# Filter by custom field value
curl -G "https://app.cometly.com/public-api/v1/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data-urlencode "start_date=2024-01-15 00:00:00" \
--data-urlencode "end_date=2024-01-15 23:59:59" \
-d "custom_field_filters[0][field]=profile_field_5" \
-d "custom_field_filters[0][operator]=equal_to" \
-d "custom_field_filters[0][value]=Premium"
# Pagination request using cursor
curl -G "https://app.cometly.com/public-api/v1/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data-urlencode "start_date=2024-01-15 00:00:00" \
--data-urlencode "end_date=2024-01-15 23:59:59" \
-d "cursor=eyJpZCI6MTIzNDU..."
// Basic request
const params = new URLSearchParams({
start_date: '2024-01-15 00:00:00',
end_date: '2024-01-15 23:59:59'
});
const response = await fetch(`https://app.cometly.com/public-api/v1/contacts?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const data = await response.json();
// Include comet tokens
const paramsWithTokens = new URLSearchParams({
start_date: '2024-01-15 00:00:00',
end_date: '2024-01-15 23:59:59',
include_comet_tokens: 1
});
// Include all emails
const paramsWithEmails = new URLSearchParams({
start_date: '2024-01-15 00:00:00',
end_date: '2024-01-15 23:59:59',
include_all_emails: 1
});
const responseWithEmails = await fetch(`https://app.cometly.com/public-api/v1/contacts?${paramsWithEmails}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const dataWithEmails = await responseWithEmails.json();
// Look up contacts by email (no date range required; batch up to 100 addresses)
const paramsWithEmailFilter = new URLSearchParams({
email: 'jane@acme.com,john@acme.com'
});
const responseWithEmailFilter = await fetch(`https://app.cometly.com/public-api/v1/contacts?${paramsWithEmailFilter}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const dataWithEmailFilter = await responseWithEmailFilter.json();
const responseWithTokens = await fetch(`https://app.cometly.com/public-api/v1/contacts?${paramsWithTokens}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const dataWithTokens = await responseWithTokens.json();
// Filter by custom field value
const paramsWithFilters = new URLSearchParams();
paramsWithFilters.append('start_date', '2024-01-15 00:00:00');
paramsWithFilters.append('end_date', '2024-01-15 23:59:59');
paramsWithFilters.append('custom_field_filters[0][field]', 'profile_field_5');
paramsWithFilters.append('custom_field_filters[0][operator]', 'equal_to');
paramsWithFilters.append('custom_field_filters[0][value]', 'Premium');
const responseWithFilters = await fetch(`https://app.cometly.com/public-api/v1/contacts?${paramsWithFilters}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const dataWithFilters = await responseWithFilters.json();
// Pagination using cursor from previous response
if (data.next_cursor) {
const nextParams = new URLSearchParams({
start_date: '2024-01-15 00:00:00',
end_date: '2024-01-15 23:59:59',
cursor: data.next_cursor
});
const nextPage = await fetch(`https://app.cometly.com/public-api/v1/contacts?${nextParams}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
}
<?php
// Basic request
$baseUrl = 'https://app.cometly.com/public-api/v1/contacts';
$headers = [
'Authorization: Bearer YOUR_API_KEY',
'Accept: application/json',
'Content-Type: application/json'
];
$params = http_build_query([
'start_date' => '2024-01-15 00:00:00',
'end_date' => '2024-01-15 23:59:59'
]);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '?' . $params);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$data = json_decode($response, true);
curl_close($ch);
// Include comet tokens
$paramsWithTokens = http_build_query([
'start_date' => '2024-01-15 00:00:00',
'end_date' => '2024-01-15 23:59:59',
'include_comet_tokens' => 1
]);
// Include all emails
$paramsWithEmails = http_build_query([
'start_date' => '2024-01-15 00:00:00',
'end_date' => '2024-01-15 23:59:59',
'include_all_emails' => 1
]);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '?' . $paramsWithEmails);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$responseWithEmails = curl_exec($ch);
$dataWithEmails = json_decode($responseWithEmails, true);
curl_close($ch);
// Look up contacts by email (no date range required; batch up to 100 addresses)
$paramsWithEmailFilter = http_build_query([
'email' => 'jane@acme.com,john@acme.com'
]);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '?' . $paramsWithEmailFilter);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$responseWithEmailFilter = curl_exec($ch);
$dataWithEmailFilter = json_decode($responseWithEmailFilter, true);
curl_close($ch);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '?' . $paramsWithTokens);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$responseWithTokens = curl_exec($ch);
$dataWithTokens = json_decode($responseWithTokens, true);
curl_close($ch);
// Filter by custom field value
$paramsWithFilters = http_build_query([
'start_date' => '2024-01-15 00:00:00',
'end_date' => '2024-01-15 23:59:59',
'custom_field_filters' => [
[
'field' => 'profile_field_5',
'operator' => 'equal_to',
'value' => 'Premium',
],
],
]);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '?' . $paramsWithFilters);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$responseWithFilters = curl_exec($ch);
$dataWithFilters = json_decode($responseWithFilters, true);
curl_close($ch);
// Pagination using cursor
if (!empty($data['next_cursor'])) {
$nextParams = http_build_query([
'start_date' => '2024-01-15 00:00:00',
'end_date' => '2024-01-15 23:59:59',
'cursor' => $data['next_cursor']
]);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '?' . $nextParams);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$nextResponse = curl_exec($ch);
curl_close($ch);
}
?>
import requests
# Basic request
base_url = 'https://app.cometly.com/public-api/v1/contacts'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
params = {
'start_date': '2024-01-15 00:00:00',
'end_date': '2024-01-15 23:59:59'
}
response = requests.get(base_url, headers=headers, params=params)
data = response.json()
# Include comet tokens
params_with_tokens = {
'start_date': '2024-01-15 00:00:00',
'end_date': '2024-01-15 23:59:59',
'include_comet_tokens': 1
}
# Include all emails
params_with_emails = {
'start_date': '2024-01-15 00:00:00',
'end_date': '2024-01-15 23:59:59',
'include_all_emails': 1
}
response_with_emails = requests.get(base_url, headers=headers, params=params_with_emails)
data_with_emails = response_with_emails.json()
response_with_tokens = requests.get(base_url, headers=headers, params=params_with_tokens)
data_with_tokens = response_with_tokens.json()
# Look up contacts by email (no date range required; batch up to 100 addresses)
params_with_email_filter = {
'email': 'jane@acme.com,john@acme.com'
}
response_with_email_filter = requests.get(base_url, headers=headers, params=params_with_email_filter)
data_with_email_filter = response_with_email_filter.json()
# Filter by custom field value
params_with_filters = {
'start_date': '2024-01-15 00:00:00',
'end_date': '2024-01-15 23:59:59',
'custom_field_filters[0][field]': 'profile_field_5',
'custom_field_filters[0][operator]': 'equal_to',
'custom_field_filters[0][value]': 'Premium',
}
response_with_filters = requests.get(base_url, headers=headers, params=params_with_filters)
data_with_filters = response_with_filters.json()
# Pagination using cursor from previous response
if data.get('next_cursor'):
next_params = {
'start_date': '2024-01-15 00:00:00',
'end_date': '2024-01-15 23:59:59',
'cursor': data['next_cursor']
}
next_response = requests.get(base_url, headers=headers, params=next_params)
next_data = next_response.json()
Status Codes
| Status Code | Description |
|---|---|
| 200 | Contacts successfully retrieved |
| 401 | Missing or invalid API key |
| 403 | API key doesn’t have permission or subscription is inactive |
| 422 | Invalid parameters provided (check error message for details) |
| 429 | Too many requests - rate limit exceeded. See Rate Limiting |
Notes
- Rate Limit: This endpoint has a limit of 15 requests per minute per Space. See Rate Limiting for details.
- Primary Data Only: This endpoint returns primary contact data (email, name, phone, location) from the profiles table for efficient browsing.
- Complete Contact Data: For full contact information including all associated emails, phones, names, and locations from merged profiles, use GET /contacts/.
- Performance: This endpoint uses a single efficient query, making it ideal for listing and browsing large numbers of contacts.
- Ordering: Results are ordered by ID in descending order (newest first).
- Pagination: Cursor pagination is used for efficient traversal of large result sets.
next_page_urlpreserves whicheveremailform (single address, comma-separated list, oremail[]array) you used on the original request. - Comet Tokens: Use the
include_comet_tokens=1query parameter to include the last 5 comet tokens for each contact. This parameter is optional and defaults to0. - All Emails: Use the
include_all_emails=1query parameter to include all email addresses for each contact, including emails from merged profiles. This parameter is optional and defaults to0. - Custom Fields: Use the
include_custom_fields=1query parameter to include all 30 custom fields in the response. Fields 1-15 are text, 16-25 are numeric, 26-30 are date. This parameter is optional and defaults to0. - Custom Field Labels: Use
use_custom_field_labels=1alongsideinclude_custom_fields=1to replace raw column names (profile_field_1) with user-defined labels (e.g.Customer Age). Fields without a configured label will retain their raw column name. - Custom Field Filters: Use
custom_field_filtersto narrow results to contacts whose custom profile fields match specified values. Conditions AND together; up to 10 per request. Slots that haven’t been configured with a label in your space cannot be filtered against and will be rejected with a 422. Age operators (age_greater_than,age_equal_to,age_less_than) accept a whole-number day count and are supported on date fields (profile_field_26–profile_field_30). The_and\characters in text filter values are treated as literals, not LIKE wildcards. - Email Lookup: Use the
emailquery parameter to look up contacts by address instead of, or in addition to, a creation-date range. Matching is exact and case-insensitive (with surrounding whitespace trimmed) — not partial or fuzzy. It matches both the contact’s primary address and any of its alternate addresses, including addresses captured on profiles that were later merged into the contact, so an address that only ever appeared on a merged-away profile still finds the surviving contact. - Shared Addresses: More than one distinct contact can legitimately share the same email address;
emailreturns all of them. - Expecting a single contact? If you expect exactly one contact for an address and want a
404/409instead of inspecting an array, use Get Contact by Email (GET /contacts/by-email/{email}orGET /contacts/by-email?email=) instead. - No Match Is Still a 200: A request with no matching contacts (whether filtering by
email, a date range, or both) returns a normal200with an emptydataarray — never a404, since this is a list endpoint. - Batch Email Lookups: Because this endpoint is rate-limited to 15 requests per minute per space, batch up to 100 addresses into a single
emailrequest rather than issuing one call per address when reconciling a list of emails in bulk. Pair batched lookups withinclude_all_emails=1: a contact matched on an alternate address is returned with its primary address inemail, so without theemailsarray there is no way to tell which of your input addresses produced a given row.