#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.tech/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 — assigned a sequential index ("0", "1", ...). Up to 64 characters. |
entity |
string | ★ | Entity name in plural: 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 serves only for include.
#Examples
#curl — personal key
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
}
},
{
"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.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
}
},
{
"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.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 } },
{ 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.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 } },
{ 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 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. |
#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": "Ivan", "lastName": "Petrov" }
],
"user1": [
{ "ID": "1", "NAME": "Letta", "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 without deletion). |
| 400 | MISSING_ENTITY_ID |
Actions get, update, delete require entityId. |
| 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. |
| 502 | BITRIX_UNAVAILABLE |
Bitrix24 responded with a 5xx error. |
| 503 | 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. |
| 504 | QUEUE_TIMEOUT |
The request waited in the portal queue for more than 30 seconds. The response contains userMessage and hint. |
| 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 an Entity API specific: the get response for users is [ { ID, NAME, ... } ]. The first element is the requested record.
Batch changes for a single entity. If you need to create, update, or delete many records of one entity (up to 500), use the specialized endpoint POST /v1/{entity}/batch with the body { "action": "create" | "update" | "delete", "items" | "ids": [...] }. It runs the operations in internal batches of 50 records and returns an array of results with a success flag for each element.
#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