
# Пакетные вызовы

Один HTTP-запрос объединяет до 50 операций над разными сущностями. Каждый вызов идентифицируется собственным `id` и обрабатывается независимо: ошибка одного вызова не отменяет остальные.

`POST /v1/batch`

**Скоуп:** проверяется индивидуально по сущности (`crm`, `task`, `im`, `disk` и др.) | **Базовый URL:** `https://vibecode.bitrix24.tech/v1` | **Авторизация:** `X-Api-Key` (APP-ключ)

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

| Поле | Тип | Обяз. | Описание |
|------|-----|:---:|---------|
| `calls` | array | ★ | Массив вызовов (от 1 до 50). Каждый элемент — объект, формат описан в таблице ниже. |

## Поля одного вызова

| Поле | Тип | Обяз. | Описание |
|------|-----|:---:|---------|
| `id` | string | нет | Идентификатор вызова в ответе. Если не передан — присваивается порядковый индекс (`"0"`, `"1"`, ...). Длина до 64 символов. |
| `entity` | string | ★ | Имя сущности во множественном числе: `deals`, `contacts`, `companies`, `tasks`, `users`, `files`, `folders` и другие. Полный список сущностей, доступных вашему ключу, возвращает `GET /v1/me` — см. [Ключи и авторизация](/docs/keys-auth). |
| `action` | string | ★ | Операция: `list`, `get`, `create`, `update`, `delete`, `fields`, `search`. |
| `entityId` | number / string | для `get` / `update` / `delete` | Идентификатор записи. |
| `params` | object | нет | Параметры операции в едином entity-формате (имена полей в `camelCase`, фильтры в [синтаксисе фильтрации](/docs/filtering)). |

Параметры внутри `params` совпадают с параметрами одиночного эндпоинта Entity API:

- `list` и `search` — `filter`, `select`, `sort`, `limit`. ⚠️ Сортировка передаётся именно под именем `sort`: `order` эта дверь в имена полей Битрикс24 не переводит и отправляет их дословно, поэтому порядок молча не применяется. Отдельно `withTotal`: он действует только у `list` и только при нулевом `offset` (см. ниже). Переданный `select` применяется и к ответу: в записях остаются только перечисленные поля плюс `id`. Незнакомые имена перечисляются в `meta` этого вызова как предупреждения `UNKNOWN_SELECT_FIELD`, а у сущностей со сверенным набором полей отклоняют этот подвызов ошибкой `UNKNOWN_SELECT_FIELD` — соседние подвызовы при этом выполняются. У реквизитов и банковских реквизитов такое имя вместо предупреждения валит сам подвызов: отказ Битрикс24 приезжает в `data.errors` под идентификатором подвызова с кодом `100`, соседи по пакету выполняются. Значение `*` (и `UF_*`, в любом регистре) означает «вернуть все поля» — отбор не применяется
- `get` — поле `include`, если сущность его поддерживает
- `create` и `update` — поля сущности (имена в `camelCase`)
- `delete` и `fields` — параметры не нужны
- смарт-процессы (`entity: "items"`) — внутри `params` обязательно `entityTypeId`

**Количество записей в списочном вызове.** `withTotal: false` в `params` означает «количество не нужно»: `data.totals.<id>` и `meta.<id>.total` в ответе не приходят. Границы применимости стоит знать: параметр действует на вызовах `action: "list"` — и при `limit` не больше 50 с нулевым `offset`, и при `limit` больше 50. У `action: "search"` и у пакета одной сущности `POST /v1/{entity}/batch` он инертен — подсчёт заказывается как раньше. Инертен он и на `list` сущностей `mail-mailboxes` и `humanresources-nodes`: там количество приходит всегда. **Важно:** Подсчёт у Битрикс24 параметр отменяет только при `limit` не больше 50. При `limit` больше 50 подсчёт нужен платформе, чтобы спланировать обход подзапроса, поэтому там параметр убирает число, а не нагрузку. Правило присутствия `total` то же, что у одиночных эндпоинтов: на короткой странице точное количество приходит и без заказа, а у вызова с отключённым подсчётом его не будет при `offset` больше нуля — см. [Листание и количество записей](./entity-api.md#листание-и-количество-записей). Границей листания в любом случае остаётся `meta.<id>.hasMore`.

**Отмена подсчёта со стороны Битрикс24 — `params.start: -1`.** Помимо `withTotal` у подкоманды есть второй, более низкоуровневый способ отказаться от счёта: значение `-1` в `params.start` — это прямая инструкция Битрикс24 «коллекцию не считать». Тогда счёта нет и в ответе портала, поэтому платформа его не публикует: ключа `total` не будет ни в `meta.<id>`, ни в `data.totals.<id>`. Числа взяться неоткуда, и `withTotal: true` его не вернёт — подсчёт отменил сам клиент. Исключение — [события календаря](./entities/calendar-events.md): их набор платформа получает целиком одним ответом и считает количество сама, поэтому `total` придёт и в этом режиме.

Границу выборки в этом режиме определяет `meta.<id>.hasMore`, и считается она по полноте страницы, которую вернул Битрикс24: заполнена до его размера страницы — 50 записей — → `true`, короче → `false`. У `action: "list"` клиентский `limit` до Битрикс24 не доезжает, поэтому потолком остаются те же 50. Свой `limit` уходит на портал только у `action: "search"`, и там полная страница ровно на лимите (`{"action":"search","params":{"limit":10,"start":-1}}` при десяти записях) — это `hasMore: true`, а не «всё найдено».

Значение читается так же, как его читает Битрикс24 — дробное усекается, строка разбирается по числовому префиксу, — поэтому `-1`, `"-1"`, `-1.5` и `"-1abc"` означают одно и то же. Положительное смещение (`start: 100`) остаётся обычным счётным курсором, `total` по нему приходит как раньше. Значение, которое числом не является вовсе (`"abc"`, пустая строка, `null`, объект, массив, `true`), читается как непереданное: страница будет та же, что и без `start`, но подкоманда возвращается под общее правило подсчёта записей и может прийти без `total`.

**Важно:** `start: -1` — не курсор, а отказ от счёта: он всегда отдаёт начало коллекции, и повторный вызов с тем же значением вернёт ту же страницу. Чтобы двигаться дальше, переходите на положительное смещение (`start: 100`) — по нему `total` приходит.

Проверяйте наличие ключа (`meta.<id>.total !== undefined`), а признаком непрочитанного остатка считайте `hasMore` — так код одинаково работает в обоих режимах.

Идентификатор записи для `get` / `update` / `delete` передаётся в поле `entityId` на уровне вызова, **не** в `params.id`. Вызов `{ "entity": "users", "action": "get", "params": { "id": 1 } }` без `entityId` отклоняется как `MISSING_ENTITY_ID`, и вся пачка возвращает `400` с ошибкой валидации. Правильно: `{ "entity": "users", "action": "get", "entityId": 1 }`. Поле `params` у `get` служит только для `include`. Так же строятся `update` и `delete` — идентификатор в `entityId`, изменяемые поля для `update` в `params`: `{ "entity": "deals", "action": "update", "entityId": 575, "params": { "title": "Новое название" } }` и `{ "entity": "contacts", "action": "delete", "entityId": 42 }`. Успех `update` и `delete` определяется по `data.summary.succeeded` и отсутствию `id` в `data.errors`.

## Примеры

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

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/batch \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "calls": [
      {
        "id": "deals",
        "entity": "deals",
        "action": "list",
        "params": {
          "filter": { "stageId": "NEW" },
          "select": ["id", "title", "amount"],
          "limit": 50,
          "withTotal": true
        }
      },
      {
        "id": "contacts",
        "entity": "contacts",
        "action": "list",
        "params": {
          "select": ["id", "name", "lastName"],
          "limit": 20,
          "withTotal": true
        }
      },
      {
        "id": "user1",
        "entity": "users",
        "action": "get",
        "entityId": 1
      }
    ]
  }'
```

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

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/batch \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "calls": [
      {
        "id": "deals",
        "entity": "deals",
        "action": "list",
        "params": {
          "filter": { "stageId": "NEW" },
          "select": ["id", "title", "amount"],
          "limit": 50,
          "withTotal": true
        }
      },
      {
        "id": "contacts",
        "entity": "contacts",
        "action": "list",
        "params": {
          "select": ["id", "name", "lastName"],
          "limit": 20,
          "withTotal": true
        }
      },
      {
        "id": "user1",
        "entity": "users",
        "action": "get",
        "entityId": 1
      }
    ]
  }'
```

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

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/batch', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    calls: [
      { id: 'deals', entity: 'deals', action: 'list', params: { filter: { stageId: 'NEW' }, select: ['id', 'title', 'amount'], limit: 50, withTotal: true } },
      { id: 'contacts', entity: 'contacts', action: 'list', params: { select: ['id', 'name', 'lastName'], limit: 20, withTotal: true } },
      { id: 'user1', entity: 'users', action: 'get', entityId: 1 }
    ]
  })
})

const { data } = await res.json()
console.log('Сделки:', data.results.deals)
console.log('Контакты:', data.results.contacts)
console.log('Сводка:', data.summary)
```

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

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/batch', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    calls: [
      { id: 'deals', entity: 'deals', action: 'list', params: { filter: { stageId: 'NEW' }, select: ['id', 'title', 'amount'], limit: 50, withTotal: true } },
      { id: 'contacts', entity: 'contacts', action: 'list', params: { select: ['id', 'name', 'lastName'], limit: 20, withTotal: true } },
      { id: 'user1', entity: 'users', action: 'get', entityId: 1 }
    ]
  })
})

const { data } = await res.json()
console.log('Сделки:', data.results.deals)
```

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

| Поле | Тип | Описание |
|------|-----|---------|
| `success` | boolean | `true`, если запрос принят. Частичные ошибки внутри `data.errors` не переводят его в `false`. |
| `data.results` | object | Результаты по `id` каждого вызова. Значение — то, что вернул бы соответствующий эндпоинт Entity API: массив записей для `list` / `search`, объект для `get` / `create`, нормализованная запись для `update`, объект `{ id, deleted: true }` для `delete`, схема полей для `fields`. |
| `data.totals` | object | Общее количество записей под фильтр для `list` / `search`-вызовов. Ключи — `id` соответствующих вызовов. У `list`-вызова присутствие подчиняется тому же правилу, что `meta.total` одиночного списка: не заказал количество — ключа нет. У `search`-вызова количество заказывается всегда. Плюс своя ветка: отрицательный `params.start` отменяет подсчёт на стороне Битрикс24, и тогда числа нет даже при `withTotal: true`. Оба случая разобраны в абзацах «Количество записей в списочном вызове» и «Отмена подсчёта со стороны Битрикс24» выше. Вызова, завершившегося ошибкой, — тоже нет. |
| `data.errors` | object | Ошибки по `id` неудачных вызовов. Каждое значение — `{ "code": "...", "message": "..." }`. |
| `data.summary.total` | number | Общее количество вызовов в запросе. |
| `data.summary.succeeded` | number | Количество успешных вызовов. |
| `data.summary.failed` | number | Количество вызовов с ошибкой. |
| `data.meta` | object | Дополнительные сведения по `id` вызовов с `action: "list"` или `action: "search"`: `total` (может отсутствовать — см. выше), `returned`, `hasMore`, `truncated`, а при потере страницы выборки — `pageErrorSample`. |
| `data.meta.<id>.warnings` | array | Предупреждения по этому вызову. Появляется, только когда они есть. Приходят те же коды, что у одиночного эндпоинта, и в той же форме — `code`, `field`, `message`. Разбирайте массив по `code`, не по позиции. |

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

```json
{
  "success": true,
  "data": {
    "results": {
      "deals": [
        { "id": 575, "title": "Тест валюты", "amount": 0 },
        { "id": 741, "title": "Поставка оборудования", "amount": 250000 }
      ],
      "contacts": [
        { "id": 1, "name": "Иван", "lastName": "Петров" }
      ],
      "user1": [
        { "ID": "1", "NAME": "Мария", "ACTIVE": true }
      ]
    },
    "totals": {
      "deals": 1798,
      "contacts": 305
    },
    "errors": {},
    "summary": {
      "total": 3,
      "succeeded": 3,
      "failed": 0
    },
    "meta": {
      "deals": { "total": 1798, "returned": 2, "hasMore": true, "truncated": false },
      "contacts": { "total": 305, "returned": 1, "hasMore": true, "truncated": false }
    }
  }
}
```

## Частичные ошибки

Если часть вызовов не прошла проверку или вернула ошибку на стороне Битрикс24, успешные результаты остаются в `data.results`, неудачные — в `data.errors` под тем же `id`:

```json
{
  "success": true,
  "data": {
    "results": {
      "deals": [
        { "id": 575, "title": "Тест валюты" }
      ]
    },
    "totals": {
      "deals": 1798
    },
    "errors": {
      "unknown": {
        "code": "UNKNOWN_ENTITY",
        "message": "Unknown entity \"foobar\". Check GET /v1/guide for available entities."
      }
    },
    "summary": {
      "total": 2,
      "succeeded": 1,
      "failed": 1
    },
    "meta": {
      "deals": { "total": 1798, "returned": 1, "hasMore": true, "truncated": false }
    }
  }
}
```

**Пауза по лимиту приходит по отдельному вызову.** Когда метод приостановлен на несколько минут, отказ достаётся не всему пакету, а конкретному вызову — в `data.errors.<id>` здесь и в `data[i].error` у пакета одной сущности `POST /v1/{entity}/batch`. Приостановку описывают два кода: [`OPERATION_TIME_LIMIT`](/docs/errors/limits#operation_time_limit-429) — приостановлена связка «ваш ключ и этот метод», [`TIMEOUT_QUARANTINE`](/docs/errors/limits#timeout_quarantine-429) — пауза действует на весь портал. Конверт при этом отвечает `200`, поэтому заголовка `Retry-After` для отдельного вызова у него нет.

**Отдельный вызов может отбиться и по другим причинам, и у каждой свой код.** Кроме двух кодов приостановки это `RATE_LIMITED` — превышен темп запросов к порталу; `QUEUE_OVERFLOW` и `QUEUE_TIMEOUT` — очередь портала переполнена или вызов не дождался в ней очереди (во втором случае запрос до Битрикс24 не дошёл, повтор безопасен); `BITRIX_TIMEOUT` — портал принял вызов и не ответил в срок, исход неизвестен; `ERROR_LOOP_DETECTED` — платформа временно приостановила метод после серии отказов; `TOKEN_REFRESH_FAILED` — не удалось обновить авторизацию портала, повтор не поможет до переподключения ключа. У всех, кроме последнего, рядом приходит `retryAfter` в секундах. Отказ без собственного кода приходит как `AUTO_PAGINATION_FAILED` в общем пакетном вызове и `CALL_FAILED` в пакете одной сущности.

Если вызов отбила платформа, зная о действующей паузе, рядом с кодом приходят `retryAfter` в секундах, `scope` и `hint` — дождитесь срока и повторите только этот вызов. Так отбиваются вызовы, которые платформа выполняет отдельными запросами: здесь это `search`, `list`, чьё окно не умещается в одну страницу Битрикс24, и `list` у сущностей `mail-mailboxes` и `humanresources-nodes` при любом размере окна; а в пакете одной сущности — любой читающий вызов, то есть `list`, `get` и `fields`. Если же паузу применил сам Битрикс24 к вызову внутри общего пакетного запроса, придут только `code` и `message`: срок повтора в этом случае не приходит, поэтому повторяйте не раньше чем через пять минут.

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

Если все вызовы не прошли валидацию, возвращается `400 INVALID_REQUEST` с разбивкой по `id` в `data.errors`:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "All calls in the batch failed validation"
  },
  "data": {
    "errors": {
      "x": {
        "code": "MISSING_ENTITY_ID",
        "message": "Action \"get\" requires entityId."
      }
    }
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `INVALID_REQUEST` | Тело запроса не соответствует схеме или все вызовы провалили валидацию. |
| 400 | `UNKNOWN_ENTITY` | В одном из вызовов передано неизвестное имя сущности. |
| 400 | `ACTION_NOT_SUPPORTED` | Сущность не поддерживает указанное действие (например, `delete` для справочной сущности без удаления). |
| 400 | `MISSING_ENTITY_ID` | Действия `get`, `update`, `delete` требуют `entityId`. |
| 400 | `EMPTY_CREATE_BODY` | Вызов `create` без единого поля тела. Возвращается для конкретного вызова в `data.errors`. |
| 400 | `EMPTY_UPDATE_BODY` | Вызов `update` без единого поля тела. Возвращается для конкретного вызова в `data.errors`. |
| 400 | `INVALID_PARAMS` | В поле, объявленном простым значением, передан объект или массив — в том числе файл парой «имя файла и содержимое в base64», который пакетный вызов не принимает. Возвращается для конкретного вызова в `data.errors`. |
| 400 | `BIZPROC_CALLBACK_BATCH_UNSUPPORTED` | Вызов `create` или `update` для `bizproc-activities` либо `bizproc-robots`, где `handler` ведёт на субдомен Black Hole. Такой обработчик регистрируется одиночным запросом. Возвращается для конкретного вызова в `data.errors`. |
| 400 | `MISSING_REQUIRED_PARAMS` | В `list`-вызове `calendar-events` не переданы обязательные `type` и `ownerId` в `params`. |
| 400 | `UNSUPPORTED_FILTER` | В `params.filter` вызова переданы поля, которые метод сущности не принимает как фильтр — например, `calendar-events`. |
| 400 | `MISSING_DYNAMIC_PARAM` | Для смарт-процессов (`entity: "items"`) не передан `entityTypeId` внутри `params`. |
| 400 | `INVALID_DYNAMIC_PARAM` | `entityTypeId` для смарт-процессов задан некорректно (не положительное целое). |
| 400 | `USE_DEDICATED_ENTITY` | Для переданного `entityTypeId` существует выделенная сущность — использовать её, а не `items`. |
| 400 | `ENTITY_CUSTOM_ROUTES` | Сущность работает только через специализированные маршруты (например, `task-comments` — через `/v1/tasks/:taskId/comments`). |
| 400 | `INVALID_CALL` | Объект вызова не содержит обязательных полей `entity` и `action`. |
| 401 | `TOKEN_MISSING` | У ключа нет настроенных OAuth-токенов или для OAuth-приложения не передан `Authorization: Bearer ...`. |
| 401 | `TOKEN_REFRESH_FAILED` | Не удалось обновить OAuth-токен портала. |
| 403 | `MANAGEMENT_KEY_NO_ENTITY_ACCESS` | Запрос пришёл с management-ключа — пакетные вызовы доступны только APP-ключам. |
| 403 | `SCOPE_NOT_ALLOWED` | Все вызовы запросили скоупы, которых нет у ключа. Если хотя бы один вызов проходит — этот код возвращается внутри `data.errors` для конкретных вызовов, а сам запрос успешен. |
| 422 | `BITRIX_ERROR` | Битрикс24 отклонил вызов. Тело ответа содержит `bitrixError.error` и `bitrixError.error_description`. |
| 429 | `RATE_LIMITED` | Превышен лимит 30 запросов в минуту на портал — общий для всех его API-ключей. Ответ содержит заголовок `Retry-After`. |
| 429 | `QUEUE_OVERFLOW` | На портале накопилось слишком много одновременных вызовов Битрикс24 (по умолчанию 100 и больше в ожидании). Ответ возвращается мгновенно с HTTP-заголовком `Retry-After: N` (секунды) — клиент должен дождаться его и повторить с экспоненциальным backoff + jitter. Тело: `error.code = QUEUE_OVERFLOW`, `error.retryAfter` дублирует заголовок. |
| 429 | `QUEUE_TIMEOUT` | Запрос ожидал в очереди портала более 30 секунд. Запрос не был отправлен в Битрикс24 — безопасно повторить (`Retry-After`). Ответ содержит `userMessage` и `hint`. |
| 502 | `BITRIX_UNAVAILABLE` | Битрикс24 ответил с ошибкой 5xx. |
| 503 | `BITRIX_TIMEOUT` | Битрикс24 принял запрос, но не ответил за 15 секунд — исход неизвестен. Для write-вызовов внутри batch: сначала перечитайте сущность, изменение могло примениться. |
| 500 | `INTERNAL_ERROR` | Внутренняя ошибка прокси. |

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

Отказы `OPERATION_TIME_LIMIT` и `TIMEOUT_QUARANTINE` приходят по отдельному вызову внутри успешного `200` — они описаны в разделе [Частичные ошибки](#частичные-ошибки).

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

**`search` и `list` с `limit > 50` обходят нативный пакетный вызов Битрикс24.** Эти вызовы выполняются как отдельные последовательные запросы со страничной выборкой до 5000 записей — каждый потребляет свою квоту rate-limit Битрикс24 независимо. Остальные вызовы — `list` с `limit ≤ 50`, `get`, `create`, `update`, `delete`, `fields` — объединяются в один пакетный вызов на стороне Битрикс24 и стоят одну единицу rate-limit суммарно.

**`list` у сущностей `mail-mailboxes` и `humanresources-nodes` обходит нативный пакетный вызов при любом `limit`.** Эти сущности обслуживаются семейством методов REST 3.0, а нативный пакетный вызов Битрикс24 умеет отправлять только методы прежнего поколения — раньше такой подвызов возвращал `ERROR_METHOD_NOT_FOUND` даже при `limit` в пределах 50. Теперь он выполняется отдельным запросом, как `search`, и стоит своей квоты rate-limit; соседние вызовы других сущностей по-прежнему уезжают одним пакетом. Исключение действует только на `list`: `get` у этих сущностей в пакетном вызове по-прежнему не отвечает данными — читайте запись её собственным маршрутом. Отказ такого подвызова приходит под кодом `AUTO_PAGINATION_FAILED` в глобальном `POST /v1/batch` и под `CALL_FAILED` в `POST /v1/{entity}/batch`.

**Количество записей у этих двух сущностей приходит всегда.** `withTotal: false` на их `list` не отключает подсчёт: платформа возвращает `total` и `hasMore` как обычно. Параметр остаётся допустимым, но нагрузку на портал не снимает.

**Стоимость `list` в единицах rate-limit Битрикс24.** Каждые 50 записей = 1 единица. При `limit > 50` авто-пагинатор делает несколько вызовов:

| `limit` | Единиц Битрикс24 |
|---------|-----------------|
| 1–50 | 1 (нативный batch) |
| 51–2 550 | 2 |
| 2 551–5 000 | 3 |

У `mail-mailboxes` и `humanresources-nodes` строки «1–50» не бывает: их `list` всегда идёт отдельными запросами по 50 записей, то есть 1 единица на каждые 50 записей начиная с первой. Один вызов в массиве `calls` — это одна операция для платформы, но несколько запросов к порталу: это разные единицы измерения, не путайте их при планировании квоты. И считайте с запасом: если портал отвечает временной ошибкой, платформа повторяет запрос, а при неудаче повторяет весь обход целиком — вместе с уже успешными страницами. На неудачном обходе фактический расход доходит примерно до восьмикратного от расчётного.

**Если действие в одном вызове провалилось, остальные продолжают выполняться.** Ошибки попадают в `data.errors` под тем же `id`, успешные результаты — в `data.results`. Поле `success` остаётся `true`, проверять нужно `data.summary.failed` или присутствие нужного `id` в `data.results`.

**`users.get` возвращает массив, а не объект.** Это особенность Entity API: ответ `get` для пользователей — `[ { ID, NAME, ... } ]`. Первый элемент — искомая запись.

**Потеря страницы выборки при сбое подзапроса.** Если во время авто-пагинации `list` / `search`-вызова один из подзапросов страницы упал, результат обрезается до непрерывного префикса, а в `data.meta[<id>].pageErrorSample` приходит `{ code, message }` первого сбоя. Поле `hasMore` при этом остаётся `true` — оставшиеся записи можно дозапросить.

В `code` приходит либо код ошибки Битрикс24, либо код самого Вайбкод. Кодов Вайбкод сегодня три, и все означают одно: обход прервал не Битрикс24, а сам Вайбкод, и вернул непрерывное начало выборки. `KEYSET_DISCONTINUITY` — обнаружен разрыв в последовательности страниц, продолжать значило бы отдать дубли. `PAGE2_COUNT_FAILED` — не удался подсчёт записей: тайм-аут, лимит запросов или ошибка портала. `LAZY_COUNT_NO_PROGRESS` — метод вернул те же записи вместо следующих. Реакция во всех трёх случаях та же — дозапросить остаток.

У двух последних кодов есть следствие для меты: `meta.<id>.total` и `data.totals.<id>` в таком ответе отсутствуют — количество не сосчитано, и число отданных строк его не заменяет. Границей остаётся `hasMore`.

**`calendar-events` в пакетном вызове.** Обязательные `type` и `ownerId` передаются в `params` вызова. Фильтр для этой сущности не поддерживается — оставшиеся поля в `params.filter` возвращают `UNSUPPORTED_FILTER`, отсутствие `type` или `ownerId` — `MISSING_REQUIRED_PARAMS`.

**Выделенные сущности недоступны в пакетном вызове.** Чаты, сообщения, лента, база знаний, звонки и рабочий день обслуживаются выделенными маршрутами — вызов с `entity: "chats"`, `"messages"`, `"posts"`, `"note"`, `"calls"` или `"workday"` возвращает ошибку с указателем на нужный маршрут. Пакетный вызов поддерживает только сущности из [справочника](/docs/entity-api). Для чтения сообщений многих диалогов одним запросом используйте `POST /v1/chats/messages/bulk` — до 50 диалогов за вызов.

**Обработчик действия или робота на субдомене Black Hole регистрируется одиночным запросом.** Вызов `create` или `update` для `bizproc-activities` и `bizproc-robots`, где `handler` ведёт на такой субдомен, пакетный вызов отклоняет: `BIZPROC_CALLBACK_BATCH_UNSUPPORTED` приходит в `data.errors` под `id` этого вызова. Обработчик на своём домене проходит пакетным вызовом. Форма отказа на `POST /v1/{entity}/batch`, поведение на аккаунте без надёжной доставки и что даёт одиночная регистрация — [Доставка вызовов действий и роботов](/docs/infra/bizproc-callbacks).

**Пакетный вызов не принимает файл в поле, объявленном простым значением.** Так объявлена фотография сотрудника `personalPhoto`: пара «имя файла и содержимое в base64» в пакете отклоняется, `INVALID_PARAMS` приходит в `data.errors` под `id` этого вызова, а на `POST /v1/{entity}/batch` весь запрос отвечает `400 BATCH_ITEM_VALIDATION` с номером элемента. Причина в размере: подзапрос пакета едет строкой запроса, и пакетные маршруты остались на общем потолке тела, тогда как одиночные подняты до 40 МиБ. За пределом длины подзапроса значение обрезалось бы, а обрезанная запись всё равно отвечает успехом. Отправляйте файл одиночным вызовом — `POST /v1/users` или `PATCH /v1/users/:id`. Какие формы поля принимаются и какие отклоняются — раздел «Фотография профиля» на странице [Обновить сотрудника](/docs/entities/users/update).

Проверка формы охватывает только поля, объявленные в схеме сущности. Поля, которого в схеме нет, — например `FILES` у записей таймлайна, — она не видит: содержимое уедет в подзапросе без предупреждения, и тот же предел длины действует и на него. Такие поля тоже отправляйте одиночным вызовом.

**Пакетные операции по одной сущности.** Специализированный эндпоинт `POST /v1/{entity}/batch` работает с одной сущностью. Набор действий у каждой сущности свой: полный список — в `operations.batch` ответа `GET /v1/guide` (`data.batch` в `GET /v1/{entity}/fields` перечисляет только действия ЗАПИСИ). Действие, которого у сущности нет, отвечает `400 ACTION_NOT_SUPPORTED` — в том числе чтение, выключенное у сущности через `disabledOperations`. Для `delete` передаётся массив `ids`, для `create` и `update` — `items`, для чтений `list` / `get` / `fields` — массив `calls`. Записи идут внутренними пакетами по 50 штук, до 500 за запрос, ответ — массив результатов с пометкой `success` для каждого элемента. Для смарт-процессов путь несёт сегмент типа: `POST /v1/items/{entityTypeId}/batch`. В `list`-вызове поле `filter` проходит через транслятор фильтров — работают псевдонимы полей и операторы `$gt`, `$contains`, `$in`. Некорректный фильтр отклоняет ТОЛЬКО свой подвызов: ответ `200`, код отказа в `data[i].error.code`, остальные подвызовы выполняются. Проверяйте наличие `error` у каждого элемента `data` — как и в глобальном `POST /v1/batch`.

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

- [Лимиты и оптимизация](/docs/optimization)
- [Синтаксис фильтрации](/docs/filtering)
- [Entity API](/docs/entity-api)
- [Ключи и авторизация](/docs/keys-auth)
- [Коды ошибок](/docs/errors)
