For AI agents: markdown of this page — /docs-content-en/batch.md documentation index — /llms.txt
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 filtering syntax). |
The parameters inside params match the parameters of a single Entity API endpoint:
listandsearch—filter,select,order,limitget— theincludefield, if the entity supports itcreateandupdate— entity fields (names incamelCase)deleteandfields— no parameters needed- smart processes (
entity: "items") —entityTypeIdis required insideparams
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
}
},
{
"id": "contacts",
"entity": "contacts",
"action": "list",
"params": {
"select": ["id", "name", "lastName"],
"limit": 20
}
},
{
"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
}
},
{
"id": "contacts",
"entity": "contacts",
"action": "list",
"params": {
"select": ["id", "name", "lastName"],
"limit": 20
}
},
{
"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 } },
{ id: 'contacts', entity: 'contacts', action: 'list', params: { select: ['id', 'name', 'lastName'], limit: 20 } },
{ 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 } },
{ id: 'contacts', entity: 'contacts', action: 'list', params: { select: ['id', 'name', 'lastName'], limit: 20 } },
{ 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. |
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, returned, hasMore, truncated, and — when a page of results is lost — pageErrorSample. |
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 }
}
}
}
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 field. Returned for the specific call inside data.errors. |
| 400 | EMPTY_UPDATE_BODY |
An update call with no body field. Returned for the specific call inside data.errors. |
| 400 | MISSING_REQUIRED_PARAMS |
A list call for calendar-events did not pass the mandatory type and ownerId in params. |
| 400 | UNSUPPORTED_FILTER |
Fields the entity's method does not accept as a filter were passed in the call's params.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 more than 100 pending). The response is returned instantly with an HTTP header Retry-After: N (seconds) — the client must wait for it 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 30 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 15 seconds — the outcome is unknown. For write calls inside the batch: re-read the entity first, the change may have applied. |
| 500 | INTERNAL_ERROR |
Internal proxy error. |
Full list of common API errors — Error codes.
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.
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–2 550 | 2 |
| 2 551–5 000 | 3 |
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 be re-fetched.
calendar-events in a batch call. The mandatory type and ownerId go into the call's params. Filtering is not supported for this entity — leftover fields in params.filter return UNSUPPORTED_FILTER, and a missing type or ownerId returns MISSING_REQUIRED_PARAMS.
Batch operations for a single entity. The specialized endpoint POST /v1/{entity}/batch works with one entity and supports the actions create, update, delete, list, get, fields. 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 the whole batch with 400 and names the call index in the message.
See also
- Limits and optimization — auto-pagination, batch calls, aggregation
- Filtering syntax — how to describe
params.filter - Entity API — entity reference and single endpoints
- Keys and authorization — key types, scopes, self-description via
GET /v1/me - Error codes — general reference