> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cometly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# List Contacts

> Retrieve a paginated list of contacts filtered by creation date

## Overview

This endpoint allows you to retrieve a paginated list of contacts from Cometly within a specific date range. 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/{id}. Results are cursor-paginated for efficient data retrieval and ordered by ID (newest first).

## Query Parameters

### Required Parameters

<ParamField query="start_date" type="string" required>
  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.

  Example: `2024-01-15 00:00:00`
</ParamField>

<ParamField query="end_date" type="string" required>
  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.

  Example: `2024-01-15 23:59:59`
</ParamField>

### Optional Parameters

<ParamField query="per_page" type="integer" default="200">
  Number of contacts to return per page. Minimum: 1, Maximum: 5000
</ParamField>

<ParamField query="cursor" type="string">
  Pagination cursor from a previous response. Use this to fetch the next page of results.
</ParamField>

<ParamField query="include_comet_tokens" type="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`.
</ParamField>

<ParamField query="include_all_emails" type="boolean" default="0">
  When set to `1`, includes all email addresses associated with the contact (including emails from merged profiles). Accepts `1` or `0`.
</ParamField>

<ParamField query="include_custom_fields" type="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`.
</ParamField>

<ParamField query="use_custom_field_labels" type="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`.
</ParamField>

<ParamField query="custom_field_filters" type="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]=Premium`
</ParamField>

## Response

### Success Response

The response follows Laravel's cursor pagination structure:

<ResponseField name="data" type="array">
  Array of contact objects. Each contact includes:

  * `id` (integer): The unique identifier of the contact
  * `email` (string|null): Primary email address
  * `name` (string|null): Primary name
  * `phone` (string|null): Primary phone number
  * `location` (string|null): Primary location
  * `comet_tokens` (array): Array of comet tokens (only included when `include_comet_tokens=1`)
  * `emails` (array): Array of all email addresses associated with the contact, including emails from merged profiles (only included when `include_all_emails=1`)
  * `profile_field_1` through `profile_field_30`: Custom field values (only included when `include_custom_fields=1`). Fields 1-15 are text (string|null), 16-25 are numeric (number|null), 26-30 are date (string|null)
</ResponseField>

<ResponseField name="path" type="string">
  The base URL path for the endpoint
</ResponseField>

<ResponseField name="per_page" type="integer">
  Number of items per page
</ResponseField>

<ResponseField name="next_cursor" type="string | null">
  Cursor for the next page of results. `null` if there are no more pages.
</ResponseField>

<ResponseField name="next_page_url" type="string | null">
  Full URL for the next page of results. `null` if there are no more pages.
</ResponseField>

<ResponseField name="prev_cursor" type="string | null">
  Cursor for the previous page of results. `null` if on the first page.
</ResponseField>

<ResponseField name="prev_page_url" type="string | null">
  Full URL for the previous page of results. `null` if on the first page.
</ResponseField>

### Error Response

<ResponseField name="message" type="string">
  Error description explaining what went wrong
</ResponseField>

## Example Requests

<CodeGroup>
  ```bash cURL theme={null}
  # 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" \
    -d "start_date=2024-01-15 00:00:00" \
    -d "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" \
    -d "start_date=2024-01-15 00:00:00" \
    -d "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" \
    -d "start_date=2024-01-15 00:00:00" \
    -d "end_date=2024-01-15 23:59:59" \
    -d "include_all_emails=1"

  # 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" \
    -d "start_date=2024-01-15 00:00:00" \
    -d "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" \
    -d "start_date=2024-01-15 00:00:00" \
    -d "end_date=2024-01-15 23:59:59" \
    -d "cursor=eyJpZCI6MTIzNDU..."
  ```

  ```javascript JavaScript theme={null}
  // 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();

  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 PHP theme={null}
  <?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);

  $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);
  }
  ?>
  ```

  ```python Python theme={null}
  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()

  # 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()
  ```
</CodeGroup>

## 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](/introduction/rate-limiting) |

## Notes

* **Rate Limit**: This endpoint has a limit of **15 requests per minute** per Space. See [Rate Limiting](/introduction/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/{id}.
* **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.
* **Comet Tokens**: Use the `include_comet_tokens=1` query parameter to include the last 5 comet tokens for each contact. This parameter is optional and defaults to `0`.
* **All Emails**: Use the `include_all_emails=1` query parameter to include all email addresses for each contact, including emails from merged profiles. This parameter is optional and defaults to `0`.
* **Custom Fields**: Use the `include_custom_fields=1` query 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 to `0`.
* **Custom Field Labels**: Use `use_custom_field_labels=1` alongside `include_custom_fields=1` to 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_filters` to 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.
