For AI agents: markdown of this page — /docs-content-en/entities/contacts/search.md documentation index — /llms.txt

Search contacts

POST /v1/contacts/search

Searches contacts using conditions in the request body. Accepts the same filters as the contact list and is designed for compound conditions and large result sets.

Request fields (body)

Parameter Type Required Default Description
Parameterfilter Typeobject Requiredno Default— DescriptionFilter by fields from GET /v1/contacts/fields.
Filtering syntax. Example: { "companyId": 15 }
Parameterlimit Typenumber Requiredno Default50 DescriptionNumber of records (up to 5000)
Parameteroffset Typenumber Requiredno Default0 DescriptionSkip N records. Rejected when combined with a date range filter spanning more than 14 days — see UNSTABLE_OFFSET_PAGINATION in Errors
Parametersort Typeobject Requiredno Default— DescriptionSort order: { "lastName": "asc" }
Parameterselect Typestring[] Requiredno Default— DescriptionField selection: ["id", "name", "lastName", "phone"]

Filtering by contact category categoryId is also supported: filter[categoryId]=0 in a GET request or { "filter": { "categoryId": 0 } } in the search request body. Use the exact spelling categoryId.

Examples

curl — personal key

Terminal
curl -X POST "https://vibecode.bitrix24.com/v1/contacts/search" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": { "companyId": 15 },
    "limit": 10,
    "sort": { "lastName": "asc" }
  }'

curl — OAuth application

Terminal
curl -X POST "https://vibecode.bitrix24.com/v1/contacts/search" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": { "companyId": 15 },
    "limit": 10,
    "sort": { "lastName": "asc" }
  }'

JavaScript — personal key

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/contacts/search', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    filter: { companyId: 15 },
    limit: 10,
    sort: { lastName: 'asc' },
  }),
})

const { success, data } = await res.json()

JavaScript — OAuth application

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/contacts/search', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    filter: { companyId: 15 },
    limit: 10,
    sort: { lastName: 'asc' },
  }),
})

const { success, data } = await res.json()

Response fields

Field Type Description
Fielddata Typearray DescriptionArray of contacts (for fields, see Fields)
Fieldmeta.total Typenumber DescriptionNumber of records matching the filter
Fieldmeta.hasMore Typeboolean DescriptionWhether there is another page
Fieldmeta.nextAfterId Typestring DescriptionID of the last returned record. Present when sorting strictly by id in ascending order while hasMore is true. Pass it back in the >id filter as a low-cost alternative to an increasing offset
Fieldmeta.durationMs Typenumber DescriptionRequest duration in milliseconds

The meta fields are alongside data, not inside it. Use meta.hasMore to paginate: a data length equal to limit does not rule out the last page.

Use the id of any contact in the data array to form its detail page URL:

https://<portal>.bitrix24.com/crm/contact/details/<id>/

<portal> is the Bitrix24 account domain. Access is subject to the employee's permissions in Bitrix24.

Response example

JSON
{
  "success": true,
  "data": [
    {
      "id": 71,
      "name": "John",
      "lastName": "Brown",
      "phone": "12025550123",
      "companyId": 15,
      "assignedById": 1,
      "typeId": "CLIENT"
    }
  ]
}

Error response example

403 — missing scope:

JSON
{
  "success": false,
  "error": {
    "code": "SCOPE_DENIED",
    "message": "This endpoint requires 'crm' scope"
  }
}

Errors

HTTP Code Description
HTTP400 CodeUNSTABLE_OFFSET_PAGINATION Descriptionoffset is greater than zero with a date range filter spanning more than 14 days. Two different retrieval algorithms produce inconsistent results, so the request is rejected. Retrieve everything in one request with limit up to 5000, pass autoWindow: false with sorting by id, or split the date range yourself
HTTP403 CodeSCOPE_DENIED DescriptionThe API key does not have the crm scope
HTTP401 CodeTOKEN_MISSING DescriptionThe API key has no configured tokens
HTTP429 CodeRATE_LIMITED DescriptionRate limit exceeded: 300 requests per minute per portal. All API keys of the portal share one limit. The current limit is returned in the x-ratelimit-limit header. Retry after the delay in the Retry-After header

For the full list of common API errors, see Errors.

Known specifics

Email addresses by type. In addition to the primary email value, contact responses include three separate fields: emailWork, emailHome, and emailMailing. You can list them in select and filter by them: "select": ["id", "name", "emailWork"] and "filter": { "emailWork": "info@example.com" }. The $contains operator also works with these fields. An address can only be written through email, so these three fields are read-only. If the contact has no address of a given type, the field returns null.

The phone filter does not suit every search. A value without an operator is compared against the entire stored string: a contact with the number +1 (202) 555-0123 matches that exact string and does not match 12025550123, because the plus sign, spaces, parentheses, and hyphens are part of the value. The filter also sees only the first number in the record: if both work and mobile numbers are stored, searching by the mobile number returns an empty list. To find a contact by any of its phone numbers in any format, use Duplicate search. The $contains operator also works here: it searches for a substring within the value and can be used for email: { "email": { "$contains": "@example.com" } }.

See also