/v1/contacts/searchbetaSearch 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
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
| Field | Type | What it is |
|---|---|---|
| filter | object | The advanced filter: {field: {operator: value}} joined with and / or / not. See the filtering guide. |
| saved_filter_id | string | integer | Apply a saved contact filter.pattern ^[1-9]\d{0,18}$ |
| sort | object | {"field": "createdAt"|"updatedAt"|"name"|"phoneNumber"|"id", "direction": "asc"|"desc"} (default newest first). |
| fieldrequired | string | Sort key.one of: createdAt, updatedAt, name, phoneNumber, id |
| direction | string | asc or desc (default desc).one of: asc, desc · default "desc" |
| detail | string | summary (default) or full (adds groups).one of: summary, full · default "summary" |
| limit | integer | How many items per page (1–100).1–100 · default 25 |
| cursor | string | The next_cursor from the previous page, with the same filter. Cursors expire after 24 hours.1–2048 characters |
Headers
| Field | Type | What it is |
|---|---|---|
| Authorizationrequired | header | Bearer $API_KEY — your API key. |
| Api-Version | header | The 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.
| Field | Type | What it is |
|---|---|---|
| datarequired | array of object | |
| idrequired | string | |
| namerequired | string or null | |
| phonerequired | string or null | As 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_maskedrequired | boolean | |
| whatsapp_user_idrequired | string or null | |
| dndrequired | boolean | Do-not-disturb: the contact receives no broadcasts/marketing. |
| starredrequired | boolean or null | null only when it could not be read this time (unavailable includes profile_extras). |
| created_atrequired | string or null | |
| updated_atrequired | string or null | |
| string or null | ||
| web_visitor_id | string or null | |
| tags | array of object | |
| idrequired | string | |
| namerequired | string or null | |
| groups | array of object | |
| idrequired | string | |
| namerequired | string or null | |
| attributes | array of object | Every 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. |
| keyrequired | string | The custom field key (as in GET /v1/custom-fields). |
| labelrequired | string or null | The field label shown in the panel. |
| typerequired | string or null | text, textarea, number, int, float, decimal, email, phone, url, select, radio, multiselect, checkbox, boolean, date or datetime. |
| valuerequired | any | The 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. |
| displayrequired | string or null | value as text. |
| typed_valuerequired | string | number | boolean | array of string or null | value 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. |
| conversations | array of object | Newest first (at most 10), a timeline summary; read messages with the inbox tools. |
| idrequired | string | |
| channel_idrequired | string or null | |
| staterequired | string or null | |
| assignedrequired | boolean | |
| assigned_staff_idrequired | string or null | |
| created_atrequired | string or null | |
| last_activity_atrequired | string or null | |
| last_activity_at | string or null | Latest conversation activity (from the conversation list). |
| unavailable | array of string | Present 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. |
| totalrequired | integer or null | Contacts matching the filters (all pages); with filter / savedFilterId / sort only on the first page.-9007199254740991–9007199254740991 |
| approximate | boolean or null | true when total is an estimate (advanced path). |
| resolved_dates | array of object | Relative or local dates in filter and the absolute instants they were run with. |
| pathrequired | string | |
| inputrequired | string | |
| valuerequired | string | |
| warnings | array of object | Filter conditions that may not mean what they look like: {path, message}. |
| path | string | |
| messagerequired | string | |
| next_cursorrequired | string or null | Pass as "cursor" to get the next page; null when there are no more results. |
Errors
Errors are application/problem+json. Branch on code.
| Status | Code | When |
|---|---|---|
| 400 | invalid_input | A field is missing or has the wrong format. |
| 401 | unauthenticated | The Authorization header is missing, the key is unknown, expired or revoked. |
| 403 | entitlement_required | The workspace's plan does not include API access ( |
| 403 | forbidden | The filter or sort compares phone numbers, which are hidden from this staff member ( |
| 403 | insufficient_scope | The key does not have the permission this operation needs. |
| 404 | not_found | A saved filter it names does not exist in your workspace ( |
| 429 | rate_limited | The key or workspace went over its rate limit. Wait for |
| 502 | upstream_error | The contacts service could not run this filter ( |
| 503 | service_unconfigured | Advanced filters are switched off on this server ( |
| 503 | upstream_unavailable | A service behind the API is briefly unavailable. Safe to retry with backoff. |
| 504 | timeout | The filter took too long to run ( |
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.