Para agentes de IA: markdown desta página — /docs-content-en/batch.md índice da documentação — /llms.txt
Os artigos da documentação estão disponíveis atualmente em inglês.
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 | Req. | 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 | Req. | 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. |
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). |
The parameters inside params match the parameters of a single Entity API endpoint:
listandsearch—filter,select,sort,limit. ⚠️ Sorting is passed under the namesort: this endpoint does not translate the field names inorderinto Bitrix24 names and forwards them verbatim, so the ordering is silently ignored.withTotalis separate: it takes effect only onlistand only with a zerooffset(see below). Aselectyou pass also applies to the response: records keep only the listed fields plusid. Unknown names are listed in that call'smetaasUNKNOWN_SELECT_FIELDwarnings, and on entities with a verified field set they reject that sub-call with anUNKNOWN_SELECT_FIELDerror — 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 indata.errorsunder the sub-call identifier with the code100, and its neighbors in the batch still run. The value*(andUF_*, case-insensitive) means "return every field" — no selection is appliedget— theincludefield, if the entity supports itcreateandupdate— entity fields (names incamelCase)deleteandfields— no parameters needed- smart processes (
entity: "items") —entityTypeIdis required insideparams
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 the sub-request's walk, so there the parameter removes the number, not the load. 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. The paging bound is meta.<id>.hasMore in every case.
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: 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 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
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
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
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
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. Plus a branch of its own: a negative params.start cancels the count on the Bitrix24 side, and then there is no number 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. Read the array by code, not by position. |
Response example
{
"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:
{
"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 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 — the paused pair is "your key and this method", and TIMEOUT_QUARANTINE — 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 the portal was exceeded; QUEUE_OVERFLOW and QUEUE_TIMEOUT — the portal 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 — the portal 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 portal 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.
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 and retry only that call. 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: no retry delay is returned in that case, so retry no sooner than five minutes later.
Error response example
If all calls fail validation, 400 INVALID_REQUEST is returned with a per-id breakdown in data.errors:
{
"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 | 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 | 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 portal 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. |
| 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 portal's API keys. The response contains a Retry-After header. |
| 429 | QUEUE_OVERFLOW |
Too many concurrent Bitrix24 calls have accumulated on the portal (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 portal 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. |
| 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. |
Full list of common API errors — Error codes.
The OPERATION_TIME_LIMIT and TIMEOUT_QUARANTINE refusals arrive on the individual call inside a successful 200 — they are described in 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 returns total and hasMore as usual. The parameter stays valid, but it removes no load from the portal.
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 the portal — these are different units, do not conflate them when planning the quota. And budget with headroom: when the portal 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 have a consequence for the meta: 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. 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. The shape of the refusal on POST /v1/{entity}/batch, the behavior on an account without reliable delivery, and what the single registration gives you — Activity and automation rule callback delivery.
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. Which shapes of the field are accepted and which are rejected — the "Profile photo" section on the Update employee page.
This shape check covers only the fields declared in the entity schema. A field the schema does not declare — FILES of timeline records, for example — is invisible to it: 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, 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. 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.