## Search CRM records

`POST /v1/crm/search`

Searches leads, deals, contacts, companies and other CRM records in one request by title, full name, phone or email. Returns at most 20 records across all types combined.

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:-----:|---------|
| `searchQuery` | string | yes | Search string, not empty and not whitespace only |
| `options.types` | array | no | CRM types to search: `LEAD`, `DEAL`, `CONTACT`, `COMPANY`, `INVOICE`, `QUOTE`, `SMART_INVOICE`, or a smart process `entityTypeId` as a number from [`GET /v1/smart-processes`](/docs/entities/smart-processes/list). A smart process can also be passed as the string `DYNAMIC_<entityTypeId>`. Without the field, the search covers all of these types |
| `options.scope` | string | no | Where to search: `DENOMINATION` — title and full name, `INDEX` — full-text index including phone and email. Without the field, the search covers title and full name, and a phone or email query returns an empty `data` array |
| `options.categoryId` | number | no | Integer. Does not narrow deal results by pipeline: with the `categoryId` of a pipeline from [`GET /v1/deal-categories`](/docs/entities/deal-categories/list), the same records come back as without it |

The body and `options` accept no other fields: an unknown field is rejected with `400 INVALID_PARAMS`.

## Examples

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/crm/search" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "searchQuery": "+12025550123",
    "options": { "scope": "INDEX" }
  }'
```

### curl — OAuth application

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/crm/search" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "searchQuery": "+12025550123",
    "options": { "scope": "INDEX" }
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/crm/search', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    searchQuery: '+12025550123',
    options: { scope: 'INDEX' },
  }),
})

const { data, meta } = await res.json()
for (const item of data) console.log(item.type, item.id, item.title)
if (meta.limits.length) console.log('Excluded by plan:', meta.limits.map((l) => l.type))
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/crm/search', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    searchQuery: '+12025550123',
    options: { scope: 'INDEX' },
  }),
})

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

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `success` | boolean | `true` on success |
| `data` | array | Records found, at most 20 across all types combined |
| `data[].id` | number | Record ID |
| `data[].type` | string | Record type, for example `LEAD`, `DEAL`, `CONTACT`, `COMPANY` |
| `data[].entityTypeId` | number | Numeric record type, for example `1` — lead, `2` — deal, `3` — contact, `4` — company |
| `data[].title` | string | Record title or full name |
| `data[].subTitle` | string | Additional line, for example the contact name on a lead. Can be empty |
| `data[].links.show` | string \| null | Path to the record card in the Bitrix24 web interface, relative to the Bitrix24 account address, for example `/crm/contact/details/3167/`. Not an API endpoint |
| `data[].attributes.phone` | array | Record phone numbers, items `{ type, value }`. The field is absent if there are no phone numbers |
| `data[].attributes.email` | array | Record emails, items `{ type, value }`. The field is absent if there are no emails |
| `meta.limit` | number | Maximum number of records in the response, always `20` |
| `meta.limits` | array | Types excluded from the search by a Bitrix24 plan limit. Items `{ type, title, description }`. An empty array means no exclusions |
| `meta.limitsMeaning` | string | Explanation of `meta.limits` |

## Response example

```json
{
  "success": true,
  "data": [
    {
      "id": 3167,
      "type": "CONTACT",
      "entityTypeId": 3,
      "title": "Anna Smith",
      "subTitle": "",
      "links": {
        "show": "/crm/contact/details/3167/"
      },
      "attributes": {
        "phone": [
          { "type": "WORK", "value": "+12025550123" }
        ],
        "email": [
          { "type": "WORK", "value": "anna.smith@example.com" }
        ]
      }
    }
  ],
  "meta": {
    "limit": 20,
    "limits": [],
    "limitsMeaning": "These CRM types were omitted because the Bitrix24 search tariff limit was exceeded"
  }
}
```

## Error response example

400 — unknown type in `options.types`:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "Unknown CRM type"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `MISSING_REQUIRED_FIELDS` | `searchQuery` is not passed, is not a string, or is whitespace only |
| 400 | `INVALID_PARAMS` | Unknown type in `options.types`, including a smart process `entityTypeId` that does not exist on the portal. Checked before the search |
| 400 | `INVALID_PARAMS` | `options.scope` is neither `DENOMINATION` nor `INDEX`, `options.categoryId` is not an integer, `options.types` is not an array |
| 400 | `INVALID_PARAMS` | Unknown field in the body or in `options`, or the body or `options` is not a JSON object |
| 502 | `BITRIX_RESPONSE_INVALID` | Bitrix24 returned a search response of an unexpected shape |
| 403 | `SCOPE_DENIED` | The key has no `crm` scope |
| 401 | `TOKEN_MISSING` | The key has no Bitrix24 connection |

Full list of common API errors — [Errors](/docs/errors).

## Known specifics

**The 20 slots are shared between types.** A short query without `options.types` can return 20 leads and no contacts. To find a customer, pass `types: ["CONTACT", "COMPANY"]`.

**An empty `data` with a non-empty `meta.limits` does not mean there are no records.** Records of the types in `meta.limits` did not make it into the response. Find them with the list method of the relevant entity, for example [`GET /v1/contacts`](/docs/entities/contacts/list) with a filter.

## See also

- [Get a contact](/docs/entities/contacts/get)
- [Get a company](/docs/entities/companies/get)
- [Smart process types](/docs/entities/smart-processes)
- [CRM search](../crm-search.md)
