Skip to content
API PlatformDevelopers
POST/v1/contacts/searchbeta

Search contacts

Needs
Read your data (mcp:read)
Plan
Any plan with API access
Limits
300 a minute per key
Undo
Nothing to undo: this only reads.

Finds contacts with the advanced filter your team uses on the contacts page: any field, custom fields, dates (also relative, like -7d or start_of_month), groups, assigned staff, conversation tags, broadcasts and saved filters, combined with and, or and not.

filter keeps the contacts page's own format (camelCase keys such as phoneNumber, attributeName), so a filter you build here can be saved and opened in the app unchanged. Give saved_filter_id to apply a saved filter (with filter too, both must match). total comes with the first page.

Try it

Body

123 / 16,384 bytes as JSON

Limits: at most 8 levels deep, 60 conditions, 500 items per list, 500 characters per value, regex ≤ 200 characters, 16 KB in all. Format, operators and recipes: Filtering contacts.

The advanced filter: `{field: {operator: value}}` joined with `and` / `or` / `not`. See the filtering guide.

Apply a saved contact filter.

sort

`{"field": "createdAt"|"updatedAt"|"name"|"phoneNumber"|"id", "direction": "asc"|"desc"}` (default newest first).

Sort key.

asc or desc (default desc).

`summary` (default) or `full` (adds groups).

How many items per page (1–100).

The `next_cursor` from the previous page, with the same filter. Cursors expire after 24 hours.

This only reads. It uses your real data and changes nothing.

Code and response

curl -X POST 'https://mcp.wa-api.cloud/v1/contacts/search' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
  "filter": {
    "and": [
      {
        "attributes": {
          "some": {
            "attributeName": {
              "eq": "city"
            },
            "stringValue": {
              "eq": "Dubai"
            }
          }
        }
      },
      {
        "createdAt": {
          "gte": "-30d"
        }
      }
    ]
  },
  "limit": 2
}'

The code reads your key from $API_KEY.

Body

Body fields
FieldTypeWhat it is
filterobjectThe advanced filter: {field: {operator: value}} joined with and / or / not. See the filtering guide.
saved_filter_idstring | integerApply a saved contact filter.pattern ^[1-9]\d{0,18}$
sortobject{"field": "createdAt"|"updatedAt"|"name"|"phoneNumber"|"id", "direction": "asc"|"desc"} (default newest first).
fieldrequiredstringSort key.one of: createdAt, updatedAt, name, phoneNumber, id
directionstringasc or desc (default desc).one of: asc, desc · default "desc"
detailstringsummary (default) or full (adds groups).one of: summary, full · default "summary"
limitintegerHow many items per page (1–100).1–100 · default 25
cursorstringThe next_cursor from the previous page, with the same filter. Cursors expire after 24 hours.1–2048 characters

Headers

Headers
FieldTypeWhat it is
AuthorizationrequiredheaderBearer $API_KEY — your API key.
Api-VersionheaderThe API version to use, e.g. 2026-10-01. Default: the version your key is pinned to.one of: 2026-10-01 · pattern ^\d{4}-\d{2}-\d{2}$

Response 200

A page of contacts.

Response fields
FieldTypeWhat it is
datarequiredarray of object
idrequiredstring
namerequiredstring or null
phonerequiredstring or nullAs the platform shows it to this staff member: E.164 digits, or masked (e.g. "*******12345") when the workspace hides numbers from agents. Never unmasked here.
phone_maskedrequiredboolean
whatsapp_user_idrequiredstring or null
dndrequiredbooleanDo-not-disturb: the contact receives no broadcasts/marketing.
starredrequiredboolean or nullnull only when it could not be read this time (unavailable includes profile_extras).
created_atrequiredstring or null
updated_atrequiredstring or null
emailstring or null
web_visitor_idstring or null
tagsarray of object
idrequiredstring
namerequiredstring or null
groupsarray of object
idrequiredstring
namerequiredstring or null
attributesarray of objectEvery custom field value this staff member may see (manager-only fields are hidden from agents; deleted fields never appear), at most 100. Text values longer than 2000 characters are clipped.
keyrequiredstringThe custom field key (as in GET /v1/custom-fields).
labelrequiredstring or nullThe field label shown in the panel.
typerequiredstring or nulltext, textarea, number, int, float, decimal, email, phone, url, select, radio, multiselect, checkbox, boolean, date or datetime.
valuerequiredanyThe value as the panel shows it: text; checkbox/boolean "Yes"/"No"; date "YYYY-MM-DD"; datetime "YYYY-MM-DD HH:MM:SS" (UTC); multiselect "a, b"; int/float numbers; decimal a string with 8 decimals.
displayrequiredstring or nullvalue as text.
typed_valuerequiredstring | number | boolean | array of string or nullvalue typed by the field type: number/int/float/decimal → number; checkbox/boolean → true/false; date → "YYYY-MM-DD"; datetime → ISO-8601 UTC; multiselect → array of the chosen values; select/radio/text/… → string. null when empty or unreadable.
conversationsarray of objectNewest first (at most 10), a timeline summary; read messages with the inbox tools.
idrequiredstring
channel_idrequiredstring or null
staterequiredstring or null
assignedrequiredboolean
assigned_staff_idrequiredstring or null
created_atrequiredstring or null
last_activity_atrequiredstring or null
last_activity_atstring or nullLatest conversation activity (from the conversation list).
unavailablearray of stringPresent only when part of the contact could not be read this time (retry later for it): profile_extras = starred, createdAt, updatedAt and webVisitorId are null because they are unknown, not empty; conversations = the timeline (and lastActivityAt) is empty because it could not be read, not because there are none.
totalrequiredinteger or nullContacts matching the filters (all pages); with filter / savedFilterId / sort only on the first page.-9007199254740991–9007199254740991
approximateboolean or nulltrue when total is an estimate (advanced path).
resolved_datesarray of objectRelative or local dates in filter and the absolute instants they were run with.
pathrequiredstring
inputrequiredstring
valuerequiredstring
warningsarray of objectFilter conditions that may not mean what they look like: {path, message}.
pathstring
messagerequiredstring
next_cursorrequiredstring or nullPass as "cursor" to get the next page; null when there are no more results.

Errors

Errors are application/problem+json. Branch on code.

StatusCodeWhen
400invalid_input

A field is missing or has the wrong format. errors[] points at each field. The filter is not valid: each problem names its field and how to fix it. reason names the class when there is one: filter_too_deep, filter_too_many_conditions, filter_list_too_long, filter_string_too_long, filter_regex_too_long, filter_regex_invalid, filter_too_large, saved_filter_cycle.

401unauthenticated

The Authorization header is missing, the key is unknown, expired or revoked.

403entitlement_required

The workspace's plan does not include API access (api_access).

403forbidden

The filter or sort compares phone numbers, which are hidden from this staff member (reason: phone_hidden).

403insufficient_scope

The key does not have the permission this operation needs.

404not_found

A saved filter it names does not exist in your workspace (reason: saved_filter_not_found).

429rate_limited

The key or workspace went over its rate limit. Wait for Retry-After seconds.

502upstream_error

The contacts service could not run this filter (reason: filter_failed), e.g. a regular expression its engine gives up on. Simplify the filter; retrying the same one fails again.

503service_unconfigured

Advanced filters are switched off on this server (reason: advanced_filter_disabled).

503upstream_unavailable

A service behind the API is briefly unavailable. Safe to retry with backoff.

504timeout

The filter took too long to run (reason: query_timeout): narrow it.

Examples

Contacts in Dubai added in the last 30 days

Request body

{
  "filter": {
    "and": [
      {
        "attributes": {
          "some": {
            "attributeName": {
              "eq": "city"
            },
            "stringValue": {
              "eq": "Dubai"
            }
          }
        }
      },
      {
        "createdAt": {
          "gte": "-30d"
        }
      }
    ]
  },
  "limit": 2
}

Response 200

{
  "data": [
    {
      "id": "48213",
      "name": "Jane Doe",
      "phone": "15555550123",
      "phone_masked": false,
      "whatsapp_user_id": null,
      "dnd": false,
      "starred": false,
      "created_at": "2026-09-20T08:14:03.000Z",
      "updated_at": "2026-09-23T16:40:11.000Z"
    }
  ],
  "next_cursor": null,
  "total": 1,
  "approximate": false,
  "resolved_dates": [
    {
      "path": "filter.and.1.createdAt.gte",
      "input": "-30d",
      "value": "2026-08-31T08:00:00.000Z"
    }
  ]
}

Operation path

The same operation is also at POST /v1/ops/crm_search_contacts, with every field in the JSON body.