Bitrix24 REST API Integration Development
The Bitrix24 REST API is one of the best-documented parts of the platform, yet integrations built on it still "break" regularly: tokens expire at 1 a.m., batch requests return partial errors, and the 2-requests-per-second limit kicks in precisely when you urgently need to synchronise 5,000 contacts. Building a reliable integration is not simply "call a method and record the response".
Authorisation Model: OAuth2 vs Webhooks
For persistent integrations, OAuth2 is used. Bitrix24 issues an access_token (TTL 1 hour) and a refresh_token (TTL 14 days). The key failure point is token renewal: if two parallel processes simultaneously detect an expired access_token and both try to renew it, one refresh_token is invalidated and the integration "goes down". Solution: token renewal via a Redis lock or a database-level lock.
// Atomic token renewal pattern via Redis
$lock = $redis->set("b24:token_refresh:{$portalId}", 1, ['NX', 'EX' => 10]);
if (!$lock) {
// Another process is already renewing — wait
usleep(500000);
return $this->getToken($portalId);
}
try {
$newTokens = $this->requestNewToken($refreshToken);
$this->storeTokens($portalId, $newTokens);
} finally {
$redis->del("b24:token_refresh:{$portalId}");
}
For simple scenarios where it is not necessary to act on behalf of a specific user — an incoming webhook. A URL of the form https://domain.bitrix24.ru/rest/1/{token}/method.json with a static token is simpler but is not tied to a user and does not refresh.
Working with Rate Limits
Cloud Bitrix24: 2 requests per second, maximum 50 per batch. On-premise — configured in /bitrix/.settings.php with the key throttle_controller.
For bulk operations — only batch. 50 methods in one HTTP request with dependencies via $result[N]:
$batch = [
'contacts' => 'crm.contact.list?start=0&select[]=ID&select[]=NAME',
'deal_1' => 'crm.deal.get?id=$result[contacts][0][ID]',
];
When the rate limit is exceeded, the API returns the error QUERY_LIMIT_EXCEEDED. The correct strategy: exponential backoff — wait 1s, 2s, 4s. Do not retry immediately — that worsens the situation.
Key Method Groups
CRM: crm.lead.*, crm.deal.*, crm.contact.*, crm.company.*. Note: user-defined fields have the prefix UF_CRM_ for leads/deals or an arbitrary prefix for contacts/companies — the type and field ID must be queried via crm.userfield.list.
Tasks: tasks.task.*. UF_* fields in tasks — via task.userfield.getlist. Checklists — separate methods task.checklistitem.*.
Disk: disk.file.*, disk.folder.*. File upload — via multipart POST to a special URL, which must first be retrieved with the disk.folder.uploadfile method.
Users: user.get, user.add, user.update. Filtering by DEPARTMENT — uses the department ID, not its name.
Handling Pagination
Most list methods return a maximum of 50 records and a next field with a cursor. Correct traversal:
$start = 0;
do {
$response = $b24->call('crm.deal.list', ['start' => $start, 'filter' => $filter]);
$items = $response['result'];
// process $items
$start = $response['next'] ?? null;
} while ($start !== null);
For large lists (>10,000 records) start becomes slow — offset pagination degrades. Instead, filter by ID > last_id and sort by ID ASC.
Working with List and Binding Fields
Fields of type "CRM entity binding" (crm_entity) return an array even for a single binding. When updating — pass an array. Fields of type "List" — via the numeric value ID, not its display name. List of values — crm.status.list?filter[ENTITY_ID]=STATUS_ID.
Synchronisation and Idempotency
Each external system record must have a mapped ID in Bitrix24. Standard approach: use the field UF_CRM_EXTERNAL_ID (or a similar custom field) to store the external identifier. Before creating — check crm.deal.list?filter[UF_CRM_EXTERNAL_ID]=ext123. If found — update; not found — create. This protects against duplicates when re-running a synchronisation.
Error Handling and Monitoring
The API returns errors in the error + error_description fields. Common errors:
| Error | Cause | Solution |
|---|---|---|
QUERY_LIMIT_EXCEEDED |
Exceeded 2 rps limit | Exponential backoff |
expired_token |
access_token expired | Renew via refresh_token |
ERROR_CORE |
Error on the Bitrix24 side | Retry after 30–60 sec |
ACCESS_DENIED |
Insufficient permissions | Check app/user permissions |
NOT_FOUND |
Object deleted | Remove mapping in external system |
Log all API calls: method, parameters (without tokens), response status, execution time. If error rate > 5% over 5 minutes — alert.
Development Stages
| Stage | Content | Timeline |
|---|---|---|
| Design | Data map, method selection, mapping model | 3–5 days |
| OAuth2 and token management | Auth, storage, rotation | 2–3 days |
| Synchronisation core | CRUD operations with Bitrix24, pagination | 1–2 weeks |
| Rate limiting and error handling | Rate limiting, retry, circuit breaker | 3–5 days |
| Testing | Unit tests, integration tests on sandbox | 1 week |
| Monitoring | Logging, metrics, alerts | 2–3 days |
Total: 4–8 weeks depending on the number of entities being synchronised and the directions of data transfer.







