
# Batch calls

A single HTTP request combines up to 50 operations across different entities. Each call is identified by its own `id` and processed independently: an error in one call does not cancel the others.

`POST /v1/batch`

**Scope:** checked individually per entity (`crm`, `task`, `im`, `disk`, etc.) | **Base URL:** `https://vibecode.bitrix24.com/v1` | **Authorization:** `X-Api-Key` (APP key)

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:---:|---------|
| `calls` | array | ★ | Array of calls (1 to 50). Each element is an object — its format is described in the table below. |

## Fields of a single call

| Field | Type | Required | Description |
|------|-----|:---:|---------|
| `id` | string | no | Call identifier in the response. If omitted, a sequential index is assigned (`"0"`, `"1"`, ...). Up to 64 characters. |
| `entity` | string | ★ | Plural entity name: `deals`, `contacts`, `companies`, `tasks`, `users`, `files`, `folders`, and others. The full list of entities available to your key is returned by `GET /v1/me` — see [Keys and authorization](/docs/keys-auth). |
| `action` | string | ★ | Operation: `list`, `get`, `create`, `update`, `delete`, `fields`, `search`. |
| `entityId` | number / string | for `get` / `update` / `delete` | Record identifier. |
| `params` | object | no | Operation parameters in the unified entity format (field names in `camelCase`, filters in the [filtering syntax](/docs/filtering)). |

The parameters inside `params` match the parameters of a single Entity API endpoint:

- `list` and `search` — `filter`, `select`, `sort`, `order`, `limit`. An accepted field name in `sort` is always translated, and `"-id"` is an example of the short form. The `order` object is translated to Bitrix24 field names on entities with `sortKeysCaseSensitive`, `sortFlatParams`, or `enforceUnknownSortFields`, including `users`, `requisites`, and `requisite-presets`. For `users`, the only supported ordinary sort field is `id`, while `UF_*` keeps its passthrough behavior only with `limit ≤ 50` and `offset = 0`. Multiple recognized `users` fields, as well as a `UF_*` sort with `limit > 50` or `offset > 0`, are replaced in full with ascending `id` order. The sub-call succeeds and `data.meta[<id>].warnings` contains one `SORT_FALLBACK_TO_ID` warning for the `sort` field. A single ordinary field other than `id` still returns `UNKNOWN_SORT_FIELD` for its sub-call, while neighboring calls continue. On every other entity, including `deals`, `order` keeps its previous behavior: it is forwarded verbatim and Bitrix24 may silently ignore it. If both parameters are present, `sort` determines the effective order, but `order` is still validated. An error stays inside that sub-call and neighboring calls continue. `withTotal` is separate: it takes effect only on `list` and only with a zero `offset` (see below). A `select` you pass also applies to the response: records keep only the listed fields plus `id`. Unknown names are listed in that call's `meta` as `UNKNOWN_SELECT_FIELD` warnings, and on entities with a verified field set they reject that sub-call with an `UNKNOWN_SELECT_FIELD` error — neighboring sub-calls still run. On requisites and bank details such a name breaks the sub-call itself instead of warning: the Bitrix24 refusal arrives in `data.errors` under the sub-call identifier with the code `100`, and its neighbors in the batch still run. The value `*` (and `UF_*`, case-insensitive) means "return every field" — no selection is applied
- `get` — the `include` field, if the entity supports it
- `create` and `update` — entity fields (names in `camelCase`), flat inside `params`. If the schema does not declare `fields`, an object under `params.fields` is rejected for that call with `NESTED_FIELDS_ENVELOPE`; pass fields directly in `params`
- `delete` and `fields` — no parameters needed
- smart processes (`entity: "items"`) — `entityTypeId` is required inside `params`

**The record count in a list call.** `withTotal: false` in `params` means "the count is not needed": `data.totals.<id>` and `meta.<id>.total` are absent from the response. It is worth knowing where it applies: the parameter takes effect on calls with `action: "list"` — both when `limit` is at most 50 with a zero `offset` and when `limit` is above 50. On `action: "search"` and in the single-entity batch `POST /v1/{entity}/batch` it is inert — the count is requested as before. It is inert on `list` for the `mail-mailboxes` and `humanresources-nodes` entities too: the count always arrives there. **Important:** The parameter cancels the Bitrix24 count only when `limit` is at most 50. With a `limit` above 50, the platform needs the count to plan pagination for the sub-request, so the parameter omits the count from the response without reducing the load on Bitrix24. The rule for whether `total` is present is the same as on the single endpoints: a short page carries the exact count even when none was requested, and a call with the count switched off will not have it at an `offset` above zero — see [Paging and record counts](./entity-api.md#paging-and-record-counts). In every case, use `meta.<id>.hasMore` to determine whether more records are available.

**Canceling the count on the Bitrix24 side — `params.start: -1`.** Besides `withTotal`, a sub-call has a second, lower-level way to decline the count: the value `-1` in `params.start` is a direct instruction to Bitrix24 not to count the collection. There is then no count in the Bitrix24 response either, so the platform does not publish one: the `total` key will be absent from both `meta.<id>` and `data.totals.<id>`. There is nowhere for the number to come from, and `withTotal: true` will not bring it back — the client canceled the count itself. The exception is [calendar events](./entities/calendar-events.md): the platform receives their whole set in a single response and counts it itself, so `total` arrives in this mode too.

In this mode the end of the result set is determined by `meta.<id>.hasMore`, computed from the fullness of the page Bitrix24 returned: filled up to the Bitrix24 page size — 50 records — → `true`, shorter → `false`. With `action: "list"` the client's `limit` never reaches Bitrix24, so the ceiling stays at those same 50. Your own `limit` is forwarded to Bitrix24 only with `action: "search"`, and there a full page exactly at the limit (`{"action":"search","params":{"limit":10,"start":-1}}` with ten records) means `hasMore: true`, not "that is all".

The value is read the way Bitrix24 reads it — a fractional number is truncated, a string is parsed by its numeric prefix — so `-1`, `"-1"`, `-1.5`, and `"-1abc"` all mean the same thing. A positive offset (`start: 100`) stays an ordinary counting cursor, and `total` arrives for it as before. A value that is not a number at all (`"abc"`, an empty string, `null`, an object, an array, `true`) is treated as not passed: the page is the same as without `start`, but the sub-call then falls back to the platform's general rule for counting records and may arrive without `total`.

**Important:** `start: -1` is not a cursor but a refusal to count: it always returns the beginning of the collection, and repeating the call with the same value returns the same page. To page further, switch to a positive offset (`start: 100`) — `total` arrives for it.

Check for the key (`meta.<id>.total !== undefined`) and treat `hasMore` as the sign of an unread remainder — that way the code works the same in both modes.

The record identifier for `get` / `update` / `delete` is passed in the `entityId` field at the call level, **not** in `params.id`. The call `{ "entity": "users", "action": "get", "params": { "id": 1 } }` without `entityId` is rejected as `MISSING_ENTITY_ID`, and the whole batch returns `400` with a validation error. Correct: `{ "entity": "users", "action": "get", "entityId": 1 }`. The `params` field for `get` is used only for `include`. `update` and `delete` are built the same way — the identifier goes in `entityId`, and the fields to change for `update` go in `params`: `{ "entity": "deals", "action": "update", "entityId": 575, "params": { "title": "New title" } }` and `{ "entity": "contacts", "action": "delete", "entityId": 42 }`. The success of `update` and `delete` is determined by `data.summary.succeeded` and the absence of the `id` in `data.errors`.

## Examples

### curl — personal key

```bash
curl -X POST https://vibecode.bitrix24.com/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 application

```bash
curl -X POST https://vibecode.bitrix24.com/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 — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/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('Deals:', data.results.deals)
console.log('Contacts:', data.results.contacts)
console.log('Summary:', data.summary)
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/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('Deals:', data.results.deals)
```

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `success` | boolean | `true` if the request is accepted. Partial errors inside `data.errors` do not flip it to `false`. |
| `data.results` | object | Results keyed by the `id` of each call. The value is whatever the corresponding Entity API endpoint would return: an array of records for `list` / `search`, an object for `get` / `create`, a normalized record for `update`, an object `{ id, deleted: true }` for `delete`, a field schema for `fields`. |
| `data.totals` | object | Total record count matching the filter for `list` / `search` calls. Keys are the `id` values of the corresponding calls. For a `list` call its presence follows the same rule as `meta.total` on a single list: no count requested, no key. For a `search` call the count is always requested. There is also a separate case: a negative `params.start` disables counting in Bitrix24, so no count is returned even with `withTotal: true`. Both cases are covered in the paragraphs "The record count in a list call" and "Canceling the count on the Bitrix24 side" above. A call that failed has no key either. |
| `data.errors` | object | Errors keyed by the `id` of failed calls. Each value is `{ "code": "...", "message": "..." }`. |
| `data.summary.total` | number | Total number of calls in the request. |
| `data.summary.succeeded` | number | Number of successful calls. |
| `data.summary.failed` | number | Number of failed calls. |
| `data.meta` | object | Additional details keyed by the `id` of calls with `action: "list"` or `action: "search"`: `total` (may be absent — see above), `returned`, `hasMore`, `truncated`, and — when a page of results is lost — `pageErrorSample`. |
| `data.meta.<id>.warnings` | array | Warnings for this call. Present only when there are any. The codes are the same ones a single endpoint returns, in the same shape — `code`, `field`, `message`. A replaced `users` sort adds one warning with `code: "SORT_FALLBACK_TO_ID"` and `field: "sort"`. A regular `id` sort adds no warning. An execution error remains in `data.errors`. Read the array by `code`, not by position. |

## Response example

```json
{
  "success": true,
  "data": {
    "results": {
      "deals": [
        { "id": 575, "title": "Currency test", "amount": 0 },
        { "id": 741, "title": "Equipment delivery", "amount": 250000 }
      ],
      "contacts": [
        { "id": 1, "name": "John", "lastName": "Brown" }
      ],
      "user1": [
        { "ID": "1", "NAME": "Mary", "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 }
    }
  }
}
```

## Partial errors

If some calls fail validation or return an error on the Bitrix24 side, successful results stay in `data.results`, failed ones go to `data.errors` under the same `id`:

```json
{
  "success": true,
  "data": {
    "results": {
      "deals": [
        { "id": 575, "title": "Currency test" }
      ]
    },
    "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 }
    }
  }
}
```

**A write Bitrix24 accepted but did not apply is a sub-call error too.** A `create` or `update` sub-call whose stage or funnel did not apply (`stageId` / `categoryId` on deals, `stageId` / `statusId` on leads — a nonexistent value), or whose explicitly requested manual amount mode on a smart-process item did not stick (`isManualOpportunity: true` on a type without product rows), moves from `data.results` to `data.errors` under its `id` with the code [`STAGE_NOT_APPLIED`](/docs/errors) or [`AMOUNT_NOT_APPLIED`](/docs/errors): `message` names the requested and the actual value, `details.unappliedFields` and `details.currentValues` say what did not apply and what the record holds, and `data` inside that error carries the record Bitrix24 returned in the answer to the sub-call, in full, with its `id`. The record was already created or updated and the other fields of the body may have been applied: correct the value and send only that, not the whole call again. This check adds no Bitrix24 calls — it compares the record Bitrix24 already returns in the answer to every such sub-call. The same call with a valid value answers as before. An amount without an explicit `isManualOpportunity: true` is not checked — from the response it is indistinguishable from a legitimate recalculation from the product rows.

**A limit pause arrives on the individual call.** When a method is paused for several minutes, the refusal goes to a specific call rather than to the whole batch — to `data.errors.<id>` here and to `data[i].error` in the single-entity batch `POST /v1/{entity}/batch`. Two codes describe the pause: [`OPERATION_TIME_LIMIT`](/docs/errors/limits#operation_time_limit-429) — the paused pair is "your key and this method", and [`TIMEOUT_QUARANTINE`](/docs/errors/limits#timeout_quarantine-429) — the pause applies to the whole Bitrix24 account. The envelope still answers `200`, so it carries no `Retry-After` header for an individual call.

**An individual call can be refused for other reasons too, and each has its own code.** Besides the two pause codes these are `RATE_LIMITED` — the request rate to Bitrix24 was exceeded; `QUEUE_OVERFLOW` and `QUEUE_TIMEOUT` — the account queue is full, or the call did not get its turn there (in the second case the request never reached Bitrix24 and a retry is safe); `BITRIX_TIMEOUT` — Bitrix24 accepted the call and did not answer in time, so the outcome is unknown; `ERROR_LOOP_DETECTED` — the platform temporarily paused the method after a run of failures; `TOKEN_EXPIRED` — the application's user OAuth session expired, so re-open the application from the Bitrix24 menu; `TOKEN_REFRESH_FAILED` — the Bitrix24 authorization could not be refreshed, and retrying does not help until the key is reconnected. The two authorization errors, `TOKEN_EXPIRED` and `TOKEN_REFRESH_FAILED`, have no `retryAfter`: retrying cannot help until authorization is renewed. The other codes in this list arrive with `retryAfter` in seconds. A refusal with no code of its own arrives as `AUTO_PAGINATION_FAILED` in the shared batch call and `CALL_FAILED` in a single-entity batch. A call to the `bizproc-templates`, `bizproc-activities` or `bizproc-robots` entities on an account where Business Processes are not included in the plan or are switched off arrives with the code `BIZPROC_MODULE_NOT_ENABLED` in both the shared batch call and a single-entity batch: retrying does not help until an account administrator enables Business Processes.

When the platform itself rejects the call, knowing the pause is in effect, the code arrives together with `retryAfter` in seconds, `scope`, and `hint` — wait out the delay, then choose the action from that sub-call's code and semantics. This applies to the calls the platform runs as separate requests: here those are `search`, `list` whose window does not fit into a single Bitrix24 page, and `list` on the `mail-mailboxes` and `humanresources-nodes` entities at any window size; and in a single-entity batch it is any read call — `list`, `get` and `fields`. If Bitrix24 itself applied the pause to a call inside the shared batch request, only `code` and `message` arrive, with no retry delay. Retry a read sub-call no sooner than five minutes later. For a write, first verify the effect by business key or with a separate read, and retry only after proving that the operation did not complete. See [What is safe to retry](/docs/errors/retry-safety) for the delivery and retry policy of every code.

## Error response example

If all calls fail validation, `400 INVALID_REQUEST` is returned with a per-`id` breakdown in `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."
      }
    }
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `INVALID_REQUEST` | The request body does not match the schema, or all calls failed validation. |
| 400 | `UNKNOWN_ENTITY` | One of the calls passed an unknown entity name. |
| 400 | `ACTION_NOT_SUPPORTED` | The entity does not support the specified action (e.g. `delete` for a reference entity that does not support deletion). |
| 400 | `MISSING_ENTITY_ID` | Actions `get`, `update`, `delete` require `entityId`. |
| 400 | `EMPTY_CREATE_BODY` | A `create` call with no body fields. Returned for the specific call inside `data.errors`. |
| 400 | `EMPTY_UPDATE_BODY` | An `update` call with no body fields. Returned for the specific call inside `data.errors`. |
| 400 | `NESTED_FIELDS_ENVELOPE` | `params` contains an object under `fields`, although the entity schema does not declare that field. Returned for that call in `data.errors`; pass fields flat inside `params`. |
| 400 | `INVALID_PARAMS` | An object or an array was sent in a field declared as a plain value — including a file sent as the pair "file name and base64 content", which a batch call does not accept. Returned for the specific call inside `data.errors`. |
| 400 | `UNKNOWN_SORT_FIELD` | A single ordinary `users` sort field differs from `id`. Multiple recognized fields instead fall back to ascending `id` with `SORT_FALLBACK_TO_ID`. The single-field error is returned for that specific call inside `data.errors`; neighboring calls continue. |
| 400 | `INVALID_SORT_FIELD` | The sort has a shape the entity does not support: an unsafe field name or multiple fields for an entity with flat sorting. On `users`, multiple recognized fields produce a warning instead. Returned for that specific call inside `data.errors`; neighboring calls continue. |
| 400 | `BIZPROC_CALLBACK_BATCH_UNSUPPORTED` | A `create` or `update` call for `bizproc-activities` or `bizproc-robots` whose `handler` points to a Black Hole subdomain. Such a handler is registered with a single request. Returned for that specific call in `data.errors`. |
| 400 | `MISSING_REQUIRED_PARAMS` | A `list` call for `calendar-events` without the mandatory `type` and `ownerId` in `params`. |
| 400 | `UNSUPPORTED_FILTER` | The call's `params.filter` contains fields the entity's method does not accept as a filter — for example, `calendar-events`. |
| 400 | `MISSING_DYNAMIC_PARAM` | For smart processes (`entity: "items"`), `entityTypeId` was not passed inside `params`. |
| 400 | `INVALID_DYNAMIC_PARAM` | `entityTypeId` for smart processes is set incorrectly (not a positive integer). |
| 400 | `USE_DEDICATED_ENTITY` | A dedicated entity exists for the passed `entityTypeId` — use it instead of `items`. |
| 400 | `ENTITY_CUSTOM_ROUTES` | The entity works only through specialized routes (e.g. `task-comments` — via `/v1/tasks/:taskId/comments`). |
| 400 | `INVALID_CALL` | The call object is missing the required `entity` and `action` fields. |
| 401 | `TOKEN_MISSING` | The key has no configured OAuth tokens, or for an OAuth application no `Authorization: Bearer ...` was passed. |
| 401 | `TOKEN_REFRESH_FAILED` | Failed to refresh the Bitrix24 OAuth token. |
| 403 | `MANAGEMENT_KEY_NO_ENTITY_ACCESS` | The request came from a management key — batch calls are available only to APP keys. |
| 403 | `SCOPE_NOT_ALLOWED` | All calls requested scopes the key does not have. If at least one call passes, this code is returned inside `data.errors` for the specific calls, and the request itself succeeds. Entries of `data.errors` with this code carry `cause` = `key_scope_missing`, `requiredScope`, `fix` and `hint` — details in [Authorization, keys and permissions](/docs/errors/auth#scope_denied-403). |
| 422 | `BITRIX_ERROR` | Bitrix24 rejected the call. The response body contains `bitrixError.error` and `bitrixError.error_description`. |
| 429 | `RATE_LIMITED` | The limit of 30 requests per minute per portal was exceeded — the limit is shared across all of the account's API keys. The current limit is returned in the `x-ratelimit-limit` header. A refusal of the whole batch request carries the delay in the `Retry-After` header. A platform-built `RATE_LIMITED` suberror inside a `200` response carries it in `retryAfter`, with no separate header; a direct Bitrix24 refusal inside the shared batch may have no retry delay. |
| 429 | `QUEUE_OVERFLOW` | Too many concurrent Bitrix24 calls have accumulated on the account (by default 100 or more pending). The response is returned instantly with an HTTP header `Retry-After: N` (seconds) — the client must wait that long and retry with exponential backoff + jitter. Body: `error.code = QUEUE_OVERFLOW`, `error.retryAfter` duplicates the header. |
| 429 | `QUEUE_TIMEOUT` | The request waited in the account queue for more than 80 seconds. The request was NOT sent to Bitrix24 — safe to retry (`Retry-After`). The response contains `userMessage` and `hint`. |
| 502 | `BITRIX_UNAVAILABLE` | Bitrix24 responded with a 5xx error, or the connection to Bitrix24 dropped before it responded. `error.message` carries the machine-readable cause code when it is known, for example `ECONNRESET`, and `error.hint` carries the retry rule. The platform does not retry the batch on its own. Before retrying write calls, re-read the entities — the change may already have been applied. |
| 503 | `BITRIX_TIMEOUT` | Bitrix24 accepted the request but did not respond within 60 seconds — the outcome is unknown. For write calls inside the batch: re-read the entity first — the change may have been applied. |
| 500 | `INTERNAL_ERROR` | Internal proxy error. |

For the full list of common API errors, see [Error codes](/docs/errors).

The `OPERATION_TIME_LIMIT` and `TIMEOUT_QUARANTINE` refusals arrive on the individual call inside a successful `200` — they are described in [Partial errors](#partial-errors).

## Known specifics

**`search` and `list` with `limit > 50` bypass the native Bitrix24 batch call.** These calls run as separate sequential requests with paging up to 5000 records — each consumes its own Bitrix24 rate-limit quota independently. The remaining calls — `list` with `limit ≤ 50`, `get`, `create`, `update`, `delete`, `fields` — are combined into a single batch call on the Bitrix24 side and cost one rate-limit unit in total.

**`list` on the `mail-mailboxes` and `humanresources-nodes` entities bypasses the native batch call at any `limit`.** These entities are served by the REST 3.0 method family, and the native Bitrix24 batch call can only dispatch previous-generation methods — such a sub-call used to come back as `ERROR_METHOD_NOT_FOUND` even with a `limit` within 50. It now runs as a separate request, like `search`, and costs its own rate-limit quota; neighboring calls on other entities still travel in one batch. The exception covers `list` only: `get` on these entities still does not answer with data inside a batch — read the record through its own route. A failure of such a sub-call arrives as `AUTO_PAGINATION_FAILED` in the global `POST /v1/batch` and as `CALL_FAILED` in `POST /v1/{entity}/batch`.

**The record count always arrives for these two entities.** `withTotal: false` on their `list` does not switch counting off: the platform continues to return `total` and `hasMore`. The parameter stays valid, but it removes no load from Bitrix24.

**Cost of `list` in Bitrix24 rate-limit units.** Every 50 records = 1 unit. With `limit > 50` the auto-paginator makes several calls:

| `limit` | Bitrix24 units |
|---------|-----------------|
| 1–50 | 1 (native batch) |
| 51–2550 | 2 |
| 2551–5000 | 3 |

There is no "1–50" row for `mail-mailboxes` and `humanresources-nodes`: their `list` always runs as separate requests of 50 records, i.e. 1 unit per 50 records starting with the first. One entry in the `calls` array is one operation for the platform but several requests to Bitrix24 — these are different units, do not conflate them when planning the quota. And budget with headroom: when Bitrix24 answers with a temporary error the platform repeats the request, and on failure it repeats the whole walk, already-successful pages included. On a failing walk the actual spend reaches roughly eight times the calculated figure.

**If an action in one call fails, the rest keep executing.** Errors land in `data.errors` under the same `id`, successful results in `data.results`. The `success` field stays `true` — check `data.summary.failed` or the presence of the expected `id` in `data.results`.

**`users.get` returns an array, not an object.** This is Entity API-specific behavior: the `get` response for users is `[ { ID, NAME, ... } ]`. The first element is the requested record.

**A page of results is lost when a sub-request fails.** If, during auto-pagination of a `list` / `search` call, one of the page's sub-requests fails, the result is trimmed to a contiguous prefix and `data.meta[<id>].pageErrorSample` carries `{ code, message }` of the first failure. The `hasMore` field stays `true` in that case — the remaining records can still be fetched.

The `code` holds either a Bitrix24 error code or a Vibecode one. There are three Vibecode codes today, and they all mean the same thing: the walk was stopped not by Bitrix24 but by Vibecode itself, which returned a contiguous prefix of the result. `KEYSET_DISCONTINUITY` — a gap in the page sequence was detected, and going on would have returned duplicates. `PAGE2_COUNT_FAILED` — the record count failed: a timeout, a rate limit, or an error on the Bitrix24 side. `LAZY_COUNT_NO_PROGRESS` — the method returned the same records instead of the next ones. The action to take is the same in all three cases — fetch the remainder.

The last two codes also affect the response metadata: `meta.<id>.total` and `data.totals.<id>` are absent from such a response — the count was not computed, and the number of returned rows is not a substitute for it. The bound stays `hasMore`.

**`calendar-events` in a batch call.** The mandatory `type` and `ownerId` go into the call's `params`. Filtering is not supported for this entity — any remaining fields in `params.filter` return `UNSUPPORTED_FILTER`, and a missing `type` or `ownerId` returns `MISSING_REQUIRED_PARAMS`.

**Dedicated entities are not available in a batch call.** Chats, messages, Feed, knowledge base, calls and workday are served by dedicated routes — a call with `entity: "chats"`, `"messages"`, `"posts"`, `"note"`, `"calls"` or `"workday"` returns an error pointing to the right route. The batch call supports only the entities from the [reference](/docs/entity-api). To read the messages of many dialogs in one request, use `POST /v1/chats/messages/bulk` — up to 50 dialogs per call.

**An activity or automation-rule handler on a Black Hole subdomain is registered with a single request.** A `create` or `update` call for `bizproc-activities` and `bizproc-robots` whose `handler` points to such a subdomain is rejected by the batch call: `BIZPROC_CALLBACK_BATCH_UNSUPPORTED` arrives in `data.errors` under that call's `id`. A handler on your own domain passes through the batch call. For the error response format on `POST /v1/{entity}/batch`, behavior on an account without reliable delivery, and the benefits of registering with a single request, see [Activity and automation rule callback delivery](/docs/infra/bizproc-callbacks).

**A batch call does not accept a file in a field declared as a plain value.** The employee photo `personalPhoto` is declared that way: the pair "file name and base64 content" is rejected inside a batch, `INVALID_PARAMS` arrives in `data.errors` under that call's `id`, and on `POST /v1/{entity}/batch` the whole request answers `400 BATCH_ITEM_VALIDATION` with the item index. The reason is size: a batch sub-call travels as a query string, and batch routes stayed on the shared body cap while the single routes were raised to 40 MiB. Past the sub-call length limit the value would be truncated, and a truncated write still answers with success. Send the file with a single call — `POST /v1/users` or `PATCH /v1/users/:id`. For accepted and rejected field formats, see the "Profile photo" section on the [Update employee](/docs/entities/users/update) page.

This shape check covers the fields declared in the entity schema and the fields whose type Bitrix24 itself reports in its field list — custom `UF_*` fields included (the boundaries are in the [`INVALID_PARAMS`](/docs/errors/request#invalid_params-400) section). A field Bitrix24 reports as multi-value — `FILES` of timeline records, for example — is still not validated by this shape check: the content travels in the sub-call without a warning, and the same length limit applies to it as well. Send those fields with a single call too.

**Batch operations for a single entity.** The specialized endpoint `POST /v1/{entity}/batch` works with one entity. The set of actions differs per entity: the full list is in `operations.batch` of the `GET /v1/guide` response (`data.batch` in `GET /v1/{entity}/fields` lists WRITE actions only). An action the entity does not have returns `400 ACTION_NOT_SUPPORTED` — including a read disabled for the entity through `disabledOperations`. For `delete` you pass an `ids` array, for `create` and `update` — `items` (each item's fields are flat; when the schema does not declare `fields`, an object under that key rejects the whole batch with `400 BATCH_ITEM_VALIDATION`, the item index and the `NESTED_FIELDS_ENVELOPE` reason), and for the reads `list` / `get` / `fields` — a `calls` array. Writes run in internal batches of 50 records, up to 500 per request, and the response is an array of results with a `success` flag for each element. A `create` / `update` element whose stage, funnel or explicitly requested manual amount mode Bitrix24 did not apply comes back with `success: false`, the code `STAGE_NOT_APPLIED` or `AMOUNT_NOT_APPLIED` in `error`, an explanation in `message` and `details.unappliedFields` / `details.currentValues` — and keeps its `id`: the record was already created or updated, do not repeat it (the same check described above for the global call; it adds no Bitrix24 calls). For smart processes the path carries the type segment: `POST /v1/items/{entityTypeId}/batch`. In a `list` call the `filter` field goes through the filter translator — field aliases and the operators `$gt`, `$contains`, `$in` work. An invalid filter rejects ONLY its own sub-call: the response is `200`, the refusal code sits in `data[i].error.code`, and the remaining sub-calls run. Check each `data` element for an `error`, exactly as in the global `POST /v1/batch`.

**Listing folder contents in a batch.** In `POST /v1/files/batch` with `action: "list"`, send `folderId` in `calls[].params`. For `POST /v1/folders/batch`, use `parentId`. The nested form in `calls[].params.filter` is also accepted, and the direct parameter takes precedence. Get the root folder identifier from `rootFolderId` in the [storage list](/docs/entities/storages/list). If no non-empty scalar parameter can be resolved, the corresponding `data` entry contains `error.code: "MISSING_REQUIRED_PARAMS"`, and other calls continue. The request still returns `200`. With a scalar direct parameter supplied, an array, object or `null` in the matching `filter` key causes the sub-call error `INVALID_FILTER_OPERATOR`. The same restriction applies to file and folder `list` calls in the global `POST /v1/batch`.

## See also

- [What is safe to retry](/docs/errors/retry-safety)
- [Limits and optimization](/docs/optimization)
- [Filtering syntax](/docs/filtering)
- [Entity API](/docs/entity-api)
- [Keys and authorization](/docs/keys-auth)
- [Error codes](/docs/errors)
