Developing Bitrix24 integration via REST API

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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.