---
title: "Get contacts"
method: POST
path: "/v1/contacts/search"
tags: ["Contacts"]
---

# Get contacts

`POST /v1/contacts/search`

The single "Get Contacts" read. Structured search over the brand’s contacts: free-text `search`, typed `filters` (`{ field, operator, value }` combined with `logic: "and" | "or"`), `sort` + `order`, and cursor pagination. Returns `{ data, pagination }`.

Folds the former `GET /v1/contacts` (omit all filters to list everything) and `GET /v1/contacts/{email}` (use `filters: [{ field: "email", operator: "equals", value: "…" }]`).

Pass an optional `audienceId` to scope the search to a saved audience’s members — its stored filter is ANDed with `filters` (an unknown / cross-brand id → `400`).

Set `count: true` to get `{ count }` instead of a page.

## Request body

- ContactsSearchRequest
  - `search` string
  - `filters` object[]
    - `field` string, required — The contact column or custom-field name to filter on (e.g. `email`, `firstName`, or a key from `list_custom_fields`).
    - `operator` string, required — One of: equals, not_equals, contains, not_contains, contains_any, not_contains_any, starts_with, ends_with, gt, gte, lt, lte, between, is_true, is_false, in, not_in, is_empty, not_exists, is_not_empty, exists, is_set, before, after, on_date. Unrecognized operators are ignored (the clause is dropped), so stick to this list — e.g. use `equals`, not `eq`.
    - `value` string, required — The comparison value, as a string. Unary operators (`is_set`, `is_empty`, `exists`, …) ignore it — pass "".
  - `audienceId` string
  - `logic` 'and' | 'or' | 'none'
  - `sort` string
  - `order` 'asc' | 'desc'
  - `count` boolean
  - `limit` integer
  - `cursor` string

## Response `200`

A page of matching contacts — or `{ count }` when `count: true`.

- union
  - ContactsListResponse
    - `data` object[], required
      - `email` string, email, required
      - `firstName` string
      - `lastName` string
      - `subscribed` boolean
      - `validationStatus` 'valid' | 'risky' | 'invalid'
      - `verificationStatus` 'valid' | 'risky' | 'invalid' — Deprecated: legacy mirror of validationStatus. Will be removed; read validationStatus.
      - `suppressed` boolean
      - `suppressedReason` string, nullable
      - `lastValidatedAt` string, date-time
      - `validationDetails` object
        - `provider` 'brew', required
        - `reason` string
        - `didYouMean` string
        - `risk` string
        - `isDisposable` boolean
        - `isRole` boolean
      - `createdAt` string, date-time, required
      - `updatedAt` string, date-time, required
      - `importId` string, nullable
      - `customFields` object
    - `pagination` object, required
      - `limit` integer, required
      - `cursor` string, nullable, required
      - `hasMore` boolean, required
  - ContactsCountResponse
    - `count` integer, required

## Other responses

- `400` — The request body or query string was invalid (unknown key, wrong type, or missing required field). Strict schemas reject unknown keys — including `brandId`, which is always resolved from the API key.
- `401` — The API key was missing, invalid, or revoked.
- `403` — The caller does not have the required `contacts` permission.
- `429` — The request hit the rolling rate limit window.
- `500` — Unexpected internal error.

---

[API](https://skmtc.net/brew/apis/brew-public-api-v1.md) · [All operations](https://skmtc.net/brew/apis/brew-public-api-v1/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/brew/brew-public-api-v1/versions/db96cfedc6c5/schema)
