---
title: "List contacts"
method: GET
path: "/v1/sites/{site_id}/contacts"
tags: ["Contacts"]
---

# List contacts

`GET /v1/sites/{site_id}/contacts`

## Request

- Base URL: `https://api.getsitecontrol.com`
- URL: `https://api.getsitecontrol.com/v1/sites/{site_id}/contacts`
- Auth: HTTP bearer

Contacts of the site in ascending order of id (`order=newest` for the latest
first), each with its e-mail, status,
names, custom `fields`, `tags`, `source` and `email_stats`. Call it to find
contacts by filters, to export them or to keep a copy in sync. One contact by
id is `get_contact`; counts without the contacts are `get_contacts_stats`.
Filters combine: every one given must match. Deleted contacts are not listed.

A page holds up to `limit` contacts; while `next_cursor` is set, pass it as
`cursor` for the next page.

`created_at` is the date the contact was added at its source: imports and
Shopify keep the original date, so `created_at` does not grow with the id.

To keep a copy in sync, walk the whole list with the cursor and no filters
once. After that, pass `updated_since` set to the start of the previous walk
minus a margin of a few minutes, and merge by id. The margin is required:
a change may be stamped slightly earlier than it becomes visible. Deletions
are not visible: a deleted contact simply disappears, so repeat the full walk
now and then to notice them.

`updated_since` covers changes of the contact itself: its fields, status,
tags or e-mail, and a restore. It misses the rest. A deletion is not listed,
the contact disappears. Sending and opening e-mails, the e-mail quality check
and segment tracking do not move `updated_at`, so `email_stats` changes
without it. A contact not changed since changes began to be recorded,
created in the admin, or the account owner's contact added with the site has
`updated_at` equal to `created_at` and is not found by `updated_since`, even
when its `created_at` is newer; the full walk lists such contacts.

`segment_id` applies the conditions of the segment as they are now.

## Path parameters

- `site_id` integer, required

## Query parameters

- `cursor` string: `next_cursor` of the previous page, sent with the same `order`; omit for the first
- `limit` integer: Items per page
- `fields` string: Comma-separated fields to keep in each item, for example `id,name`; each item then carries only those fields and `id`. Omit for every field
- `order` 'oldest' | 'newest': Order of a list. oldest: the oldest item first; newest: the newest item first, for the latest items without paging through the history.
- `status` string[]: Any of these statuses; repeat for several. `subscribed`: subscribed, with double opt-in confirmed or not needed; `unsubscribed`: unsubscribed, never subscribed or awaiting double opt-in; `bounced`: the address bounced or failed the e-mail check
- `tag_ids` integer[]: Tagged with any of these tag ids from `list_tags`; repeat for several
- `country` string[]: In any of these countries, ISO 3166-1 alpha-2 codes such as `US`; repeat for several
- `email` string[]: With any of these whole addresses, without case; repeat for several
- `search` string: Part of the e-mail, phone, first or last name, without case; % and _ are plain characters. Digits also match the id. To find a contact by its address, use `email`
- `created_since` string, date-time: `created_at` at or after this moment; without an offset, taken as UTC; a plain date means its start
- `created_before` string, date-time: `created_at` before this moment; without an offset, taken as UTC; a plain date means its start
- `updated_since` string, date-time: `updated_at` at or after this moment; without an offset, taken as UTC; a plain date means its start. Only contacts with a recorded change match
- `segment_id` integer: Matching the conditions of this segment, a segment id from `list_segments`
- `conditions` string: Matching a tree of conditions, as JSON: the language of `create_segment` `conditions`, without saving a segment; for example contacts without a first name: {"op": "and", "args": [{"op": "undefined", "args": ["{{$contact.firstName}}"]}]}

## Response `200`

Successful Response

- ContactPage: One page of a list; `next_cursor` is null on the last page.
  - `items` Contact[], required
    - `id` integer, required
    - `email` string, required
    - `status` 'subscribed' | 'unsubscribed' | 'bounced', required: subscribed: subscribed to e-mails. unsubscribed: opted out, never subscribed, or has not confirmed the subscription yet. bounced: e-mails to the address bounced, or it was cleaned from the list.
    - `first_name` string, nullable
    - `last_name` string, nullable
    - `phone` string, nullable
    - `country` string, nullable: ISO 3166-1 alpha-2
    - `timezone` string, nullable: IANA time zone name
    - `language` string, nullable: ISO 639-1 code
    - `fields` object, required: Set values of custom fields by field id, typed by the field: a date as YYYY-MM-DD, a datetime in ISO 8601
    - `tags` ContactTag[], required
      - `id` integer, required
      - `name` string, required
    - `source` ContactSource, required
      - `type` 'widget' | 'automation' | 'manual' | 'import' | 'shopify' | 'mailchimp' | 'klaviyo' | 'api' | 'other', required: Where the contact came from; widget, automation, import, shopify, mailchimp, klaviyo name the source. api: added through the public API or an AI assistant (MCP). manual: added by hand in the admin. other: a source not listed here.
      - `id` integer, nullable: Depends on the type: the widget, the automation, the admin user, the import task, the Shopify customer, the Mailchimp id; for api the API key, or the user whose access added it when there is no key
    - `email_stats` ContactEmailStats, required: E-mails to the contact, the keys of `EmailMetrics`; `send` includes e-mails rejected on sending, also counted in `bounce`.
      - `send` integer, required
      - `deliver` integer, required
      - `bounce` integer, required
      - `open` integer, required
      - `click` integer, required
      - `unsubscribe` integer, required
      - `complaint` integer, required
    - `created_at` string, date-time, required: When the contact was added at its source; imports keep the original date
    - `updated_at` string, date-time, required: Last change; equals created_at when none was recorded
    - `warnings` string[], required: Empty unless the contact could not be read in full; wording is not stable, do not parse
  - `next_cursor` string, nullable

## Other responses

- `400`: Unsupported GSC-Version
- `401`: Missing or invalid API key or access token
- `403`: Access denied
- `404`: `segment_not_found`: no segment `segment_id` on the site
- `422`: `segment_conditions_invalid`: the conditions cannot be applied, the ones in `conditions` or the saved ones of the segment, which are fixed in the admin. `validation_failed`: a parameter is invalid. `invalid_cursor`: `cursor` was not issued here or was issued for another `order`
- `429`: `rate_limited`: a request limit is spent; a request with `search` or `segment_id` also spends the heavy request limit
- `503`: `temporarily_unavailable`: the request took too long, or limits or idempotency keys cannot be enforced; narrow the filters or retry later

---

[API](https://skmtc.dev/getsitecontrol/apis/getsitecontrol-public-api.md) - [All operations](https://skmtc.dev/getsitecontrol/apis/getsitecontrol-public-api/llms.txt) - [OpenAPI document](https://skmtc.dev/getsitecontrol/apis/getsitecontrol-public-api/revisions/084fcad80e75?raw)
