For AI agents: markdown of this page — /docs-content-en/optimization.md documentation index — /llms.txt
Limits and optimization
Vibecode merges calls and paginates result sets server-side on its own. This article describes the built-in mechanisms and helps you pick the right endpoint for the job.
Base URL: https://vibecode.bitrix24.com/v1 | Authorization: X-Api-Key
Auto-pagination | Batch calls | Date-windowed search | Aggregation | Portal queue | Caching | Summary limits
Auto-pagination in `list`
The limit parameter in GET /v1/{entity} accepts values up to 5000. When limit > 50, Vibecode splits the result set into internal pages of 50 records and assembles them into a single response:
GET /v1/deals?limit=500&filter[stageId]=NEW
Returns up to 500 records plus the meta.total and meta.hasMore meta fields. If more than 5000 records match the filter, the first 5000 are included and meta.hasMore comes back true — for the remainder, either narrow the filter or use POST /v1/{entity}/search.
Batch calls across multiple entities
POST /v1/batch merges up to 50 operations over different entities into a single HTTP request. Each call is identified by its own id; a failure in one does not cancel the others.
Suited to dashboards and summary pages where a single load needs data from different places:
{
"calls": [
{ "id": "deals", "entity": "deals", "action": "list", "params": { "filter": { "stageId": "NEW" }, "limit": 50 } },
{ "id": "tasks", "entity": "tasks", "action": "list", "params": { "filter": { "responsibleId": 1 }, "limit": 20 } },
{ "id": "user", "entity": "users", "action": "get", "entityId": 1 }
]
}
Full specification — Batch calls.
Batch operations on a single entity
POST /v1/{entity}/batch bulk-creates, updates, or deletes up to 500 records of a single entity. Internally Vibecode splits the request into batches of 50 items:
curl -X POST https://vibecode.bitrix24.com/v1/deals/batch \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "update",
"items": [
{ "id": 575, "stageId": "WON" },
{ "id": 741, "stageId": "WON" }
]
}'
Actions: create, update, delete, list, get, fields. For delete pass an ids array; for create and update — items; for list / get / fields — calls.
Bulk scanning with on-demand loading of related data
When you need to read thousands of records of one entity and pull in related data from other entities, the flow looks like this:
Fetch the root list in a single call — Vibecode paginates server-side on its own:
GET /v1/deals?limit=5000&select=id,title,companyId,assignedByIdCollect the
idvalues of the related entities and load them in batches of 50 viaPOST /v1/batch:{ "calls": [ { "id": "company-15", "entity": "companies", "action": "get", "entityId": 15 }, { "id": "company-22", "entity": "companies", "action": "get", "entityId": 22 }, { "id": "user-1", "entity": "users", "action": "get", "entityId": 1 } ] }One HTTP request — up to 50 related records; the loop repeats for the next batch of
idvalues.
In this scenario the root fetch takes one network request (Vibecode pages through the records in steps of 50 on its own), and the associations are loaded at a pace of one HTTP request per 50 items instead of a request per item.
Search with date windowing
POST /v1/{entity}/search is designed for large result sets. If the filter contains a date condition with a range greater than 14 days, Vibecode automatically splits the request into 7-day windows and processes them in parallel:
{
"filter": { "$gte": { "createdAt": "2026-01-01" }, "$lte": { "createdAt": "2026-04-30" } },
"select": ["id", "title", "stageId"],
"limit": 5000
}
On a partial window failure — when some windows returned data and the response comes back with HTTP 200 — the following fields appear in meta:
| Field | Description |
|---|---|
meta.autoWindowed |
true when the request was split into date windows. |
meta.windowCount |
Number of windows the request was split into. |
meta.windowErrors |
Number of windows for which Bitrix24 returned an error. The remaining windows return their data. |
meta.windowErrorSample |
An object { code, message } — the code and text of the first failed window, so you can see why data was lost. |
meta.batchWaves |
Number of parallel window-dispatch waves. Appears when batched window dispatch was used. |
meta.hasMore |
Whether there are records beyond limit. When more than 5000 records match, part of them stays outside the result set — narrow the filter or the date range. |
If all windows fail, this meta block is absent — the real Bitrix24 error code is returned, as for a narrow range: UNKNOWN_FILTER_FIELD, INVALID_PARAMS, BITRIX_ACCESS_DENIED, RATE_LIMITED, BITRIX_UNAVAILABLE, or BITRIX_TIMEOUT (503). The separate WINDOWED_SEARCH_FAILED code is no longer returned.
Windowing is disabled with the "autoWindow": false flag in the request body — appropriate during network timeouts or unstable results. The full list of search parameters is in each entity's documentation.
Aggregation instead of fetching records
When you only need counts, sums, minimums, maximums, or averages — POST /v1/{entity}/aggregate returns the result in a single call, without fetching the records themselves:
{
"aggregate": [
{ "field": "amount", "function": "sum" },
{ "field": "amount", "function": "avg" }
],
"filter": { "stageId": "WON" },
"groupBy": "assignedById"
}
The response contains data.count, data.aggregates and (when groupBy is present) a data.groups array broken down by the grouping field. Without groupBy the response is limited to a single object of summary values. For the count function Bitrix24 returns the result in a single call regardless of result-set size; sum / avg / min / max load records page by page up to 5000 — at a larger volume meta.truncated: true comes back.
Portal queue
Each Bitrix24 account has its own queue to the Vibecode API: a limited number of requests run concurrently, the rest wait for a slot. If a request hangs in the queue longer than 30 seconds, 429 QUEUE_TIMEOUT is returned with a userMessage and a hint.
What reduces the load on the queue:
- Merge disparate calls via
POST /v1/batch— one HTTP request instead of several. - For bulk CRUD on a single entity —
POST /v1/{entity}/batch(up to 500 records per request). - For wide result sets —
GET /v1/{entity}?limit=...(Vibecode paginates server-side) orPOST /v1/{entity}/search(date windowing). - When you need numbers, not records —
POST /v1/{entity}/aggregate.
Retry on queue overload. Under load the queue returns two different codes, and both mean "retry later":
429 QUEUE_OVERFLOW— the queue is full, the request is rejected immediately (within milliseconds). TheRetry-Afterheader carries the recommended pause in seconds.429 QUEUE_TIMEOUT— the request waited for a slot longer than 30 seconds. The request was NOT sent to Bitrix24 — safe to retry. Theerror.retryAfterbody field carries the recommended pause.
Analytics widgets that fire a burst of /search calls in a row must retry with exponential backoff and random jitter (backoff with jitter), respecting Retry-After, rather than retrying instantly in a loop — that makes the overload worse. Lower the concurrency: run the requests sequentially or combine them into POST /v1/batch.
async function callWithBackoff(url, options, maxRetries = 4) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, options)
if (res.status !== 429) return res
if (attempt >= maxRetries) return res
// Both codes (QUEUE_OVERFLOW, QUEUE_TIMEOUT) carry the pause in the Retry-After header;
// error.retryAfter in the body duplicates the same value
const headerWait = Number(res.headers.get('Retry-After'))
const bodyWait = Number((await res.clone().json())?.error?.retryAfter) || 0
const baseSec = headerWait || bodyWait || Math.min(2 ** attempt, 30)
const jitterMs = Math.floor(Math.random() * 1000)
await new Promise(r => setTimeout(r, baseSec * 1000 + jitterMs))
}
}
Loading messages from multiple dialogs
POST /v1/chats/messages/bulk returns messages from no more than 50 dialogs in a single response and accepts lastId / firstId cursors and a limit per dialog:
{
"dialogs": [
{ "dialogId": "chat253", "limit": 20 },
{ "dialogId": "chat741", "lastId": 9357, "limit": 50 }
]
}
Scope: im. Response format — { results, errors, summary }, the same as /v1/batch.
Caching
Some responses are served from a cache, so repeat reads don't load Bitrix24 or the portal queue. The cache is transparent — the response body is identical to the uncached one, and the X-Cache header shows where the response came from.
Response cache for `/v1/users` and `/v1/statuses`
Responses of GET /v1/users and GET /v1/statuses are cached server-side: users for 60 seconds, CRM directories for 5 minutes. This speeds up dashboards that request these endpoints on every load.
The cache is bound to the personal key vibe_api_.... For an OAuth-app authorization key vibe_app_... the cache is not used — different users have different access to account data.
A write through the API — POST /v1/users, PATCH /v1/users/:id and similar — clears the cache immediately. An edit made directly in the Bitrix24 interface is invisible to the cache, so such changes may appear with a delay up to the end of the cache lifetime: up to 60 seconds for users and up to 5 minutes for directories.
To get guaranteed-fresh data bypassing the cache, add the Cache-Control: no-cache header:
curl -H "X-Api-Key: YOUR_API_KEY" \
-H "Cache-Control: no-cache" \
https://vibecode.bitrix24.com/v1/users
The X-Cache header in the response shows how the request was handled:
| Value | Meaning |
|---|---|
HIT |
Response served from cache |
MISS |
Response fetched from Bitrix24 and stored in the cache |
COALESCED |
The request joined an in-flight fetch for the same data |
BYPASS |
The cache was not used — an OAuth-app authorization key or the Cache-Control: no-cache header. The reason is given in the X-Cache-Bypass-Reason header |
HTTP cache for discovery endpoints
GET /v1/openapi.json and GET /v1/guide return large documents that a client need not re-download on every startup. Both responses carry standard HTTP cache headers:
/v1/openapi.json—Cache-Control: public, max-age=300. The specification is identical for every client, so any cache may store it./v1/guide—Cache-Control: private, max-age=300. The response content depends on the key's scopes, so a shared cache must not store it.ETag— a content fingerprint that changes only when the document or the key's scope set changes.
Keep the ETag from the first response and send it in the If-None-Match header on repeat requests. If the content hasn't changed, the endpoint answers 304 Not Modified with an empty body instead of re-sending the whole document:
# First call — full body and ETag (openapi.json needs no authorization)
curl -i https://vibecode.bitrix24.com/v1/openapi.json
# ... ETag: "a1b2c3d4e5f6a7b8"
# Repeat call — 304 Not Modified, no body transferred
curl -i -H 'If-None-Match: "a1b2c3d4e5f6a7b8"' \
https://vibecode.bitrix24.com/v1/openapi.json
The max-age=300 header also lets the client and any intermediate cache reuse the response for 5 minutes without contacting the server.
Summary limits
| Scenario | Limit |
|---|---|
GET /v1/{entity} — limit |
up to 5000 records; when limit > 50, auto-pagination on the Vibecode side |
POST /v1/batch — number of calls |
up to 50 in one request |
POST /v1/{entity}/batch — bulk CRUD |
up to 500 records in one request |
POST /v1/{entity}/batch — read actions (list / get / fields) |
up to 50 calls in the calls array |
POST /v1/{entity}/search — limit |
up to 5000 records; for a date range > 14 days — 7-day windows |
POST /v1/chats/messages/bulk — dialogs |
up to 50 in one request |
| Portal queue | a limited number of concurrent requests, wait up to 30 seconds |
Response cache for /v1/users / /v1/statuses |
60 seconds / 5 minutes, bypass — Cache-Control: no-cache header |