# Поиск дубликатов

`POST /v1/duplicates/find`

Находит в CRM лиды, контакты и компании, у которых телефон или адрес электронной почты совпадает с переданными значениями. Применяется перед созданием новой записи, чтобы не плодить дубли, при импорте данных из внешних источников и когда запись нужно найти по номеру телефона.

Битрикс24 API: `crm.duplicate.findbycomm`
Скоуп: `crm`

## Поля запроса (body)

| Поле | Тип | Обяз. | Описание |
|------|-----|:-----:|----------|
| `type` | string | да | Тип значения для поиска: `phone` или `email` |
| `values` | array | да | Список значений для проверки — телефоны или адреса электронной почты. Максимум 20 значений за запрос |
| `entityType` | string | нет | Ограничить поиск одним типом: `lead`, `contact` или `company`. Без параметра поиск идёт по всем трём типам |

## Примеры

### curl — личный ключ

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/duplicates/find \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "phone",
    "values": ["+79991234567"]
  }'
```

### curl — OAuth-приложение

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/duplicates/find \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "phone",
    "values": ["+79991234567"]
  }'
```

### JavaScript — личный ключ

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/duplicates/find', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'phone',
    values: ['+79991234567'],
  }),
})

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

### JavaScript — OAuth-приложение

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/duplicates/find', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'phone',
    values: ['+79991234567'],
  }),
})

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

## Поля ответа

| Поле | Тип | Описание |
|------|-----|----------|
| `success` | boolean | Всегда `true` при успехе |
| `data` | object \| array | Найденные совпадения, сгруппированные по типу сущности. Пустой массив `[]`, если совпадений нет |
| `data.lead` | array | ID лидов с совпадающим контактом. Карточки: `GET /v1/leads` |
| `data.contact` | array | ID контактов с совпадающим контактом. Карточки: `GET /v1/contacts` |
| `data.company` | array | ID компаний с совпадающим контактом. Карточки: `GET /v1/companies` |

## Пример ответа

Найдены совпадения — `data` содержит ключи по типам сущностей:

```json
{
  "success": true,
  "data": {
    "lead": [105],
    "contact": [42]
  }
}
```

Совпадений нет — `data` приходит пустым массивом:

```json
{
  "success": true,
  "data": []
}
```

## Пример ответа при ошибке

400 — не переданы обязательные параметры:

```json
{
  "success": false,
  "error": {
    "code": "MISSING_PARAMS",
    "message": "Required: type (\"PHONE\" or \"EMAIL\"), values (string array, max 20)"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `MISSING_PARAMS` | Не передан `type` или `values`, либо `values` — пустой массив |
| 400 | `TOO_MANY_VALUES` | В `values` больше 20 значений |
| 422 | `BITRIX_ERROR` | Недопустимый `type` — текст ошибки приходит в поле `message` |
| 403 | `SCOPE_DENIED` | Ключу не хватает скоупа `crm` |
| 401 | `TOKEN_MISSING` | У ключа не настроены токены доступа |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

**Номер находится в любом написании и по любому из номеров записи.** Значения `+7 (999) 123-45-67`, `+79991234567` и `89991234567` найдут одну и ту же запись, как бы номер ни был сохранён на портале. Если у контакта записаны рабочий и мобильный телефоны, метод найдёт его по каждому из них. Поэтому запись по номеру ищут этим методом, а не фильтром `filter.phone` в списке или поиске: тот сравнивает строку с сохранённым значением целиком и видит только первый номер записи — [Фильтр по телефону и почте](/docs/filtering#фильтр-по-телефону-и-почте). Номер передаётся целиком: без кода страны или без ведущей «8» он не опознаётся.

**Тип `data` зависит от результата.** При совпадениях это объект с ключами по типам сущностей, при их отсутствии — пустой массив `[]`. Перед обработкой проверяйте тип: `Array.isArray(data)` означает «совпадений нет».

**Одно значение может совпасть сразу в нескольких типах.** Если телефон или адрес электронной почты есть и у лида, и у контакта, в ответе придут оба ключа со своими ID. Без `entityType` проверяйте все вернувшиеся группы, а не только первую.

## Смотрите также

- [Синтаксис фильтрации](/docs/filtering)
- [Контакты](/docs/entities/contacts)
- [Лиды](/docs/entities/leads)
- [Компании](/docs/entities/companies)
- [Обзор API](/docs/entity-api)
- [Ошибки](/docs/errors)
