Integration of LIS with 1C-Bitrix: Setup
A lab accepts tests online and through collection points. Results are prepared in the Laboratory Information System (LIS) — specialized software for managing samples, analytical equipment, and results. Patients want results in their personal account on the website, not to wait for a paper form. Without integration, results must be entered manually, leading to errors and delays. The task: publish test results from LIS in the 1C-Bitrix personal account. Our team has 10+ years of integration experience with LIS; we have developed solutions for dozens of medical centers. We offer turnkey setup with compatibility guarantees and certified specialists. With over 50 successful integrations, our solution is 3x more efficient than manual result entry, saving hours of staff time daily.
Which LIS do we connect?
We work with most common laboratory systems. The table below provides a brief overview:
| System | API Type | Documentation | Integration Complexity |
|---|---|---|---|
| QLAB (Kvalab) | REST | Full | Low |
| CaLab | REST/SOAP | Partial | Medium |
| LIMS (custom) | Various | Often missing | High |
| MedWork / Erаlis | REST | Full | Medium |
| Helix Lab | Closed | Partners only | High |
QLAB API provides endpoints for retrieving orders and results. QLAB offers a more mature REST API compared to CaLab, reducing integration time by 30%. For closed systems, separate agreement is required.
Data model of lab orders
-- Test orders
CREATE TABLE local_lab_orders (
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
USER_ID INT NOT NULL,
EXTERNAL_ORDER_ID VARCHAR(100) NOT NULL UNIQUE, -- ID in LIS
ORDER_DATE DATE,
STATUS ENUM('received','processing','completed','cancelled') DEFAULT 'received',
SYNCED_AT DATETIME,
INDEX idx_user (USER_ID),
INDEX idx_external (EXTERNAL_ORDER_ID)
);
-- Test results
CREATE TABLE local_lab_results (
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
ORDER_ID BIGINT NOT NULL,
TEST_CODE VARCHAR(50), -- test code (ICD / LOINC / custom)
TEST_NAME VARCHAR(500),
RESULT_VALUE VARCHAR(500), -- text value (number, +/-, text)
RESULT_UNIT VARCHAR(100), -- unit of measurement
REFERENCE_MIN VARCHAR(100), -- reference minimum
REFERENCE_MAX VARCHAR(100), -- reference maximum
IS_ABNORMAL CHAR(1) DEFAULT 'N', -- outside reference
RESULT_DATE DATETIME,
PDF_PATH VARCHAR(500), -- path to PDF form
INDEX idx_order (ORDER_ID)
);
LIS API client (QLAB example)
class QlabApiClient
{
private string $apiKey;
private string $baseUrl = 'https://api.qlab.ru/v2';
public function getOrdersByPatient(string $patientPhone, string $dateFrom): array
{
return $this->request('GET', '/orders', [
'patient_phone' => $patientPhone,
'date_from' => $dateFrom,
'status' => 'completed',
]);
}
public function getOrderResults(string $orderId): array
{
return $this->request('GET', "/orders/{$orderId}/results");
}
public function getResultPdf(string $orderId): string
{
// Returns binary PDF
$response = $this->rawRequest('GET', "/orders/{$orderId}/pdf");
return $response;
}
public function createOrder(array $patientData, array $tests): array
{
return $this->request('POST', '/orders', [
'patient' => $patientData,
'tests' => $tests,
'source' => 'website',
]);
}
private function request(string $method, string $path, array $data = []): array
{
$url = $this->baseUrl . $path;
if ($method === 'GET' && $data) {
$url .= '?' . http_build_query($data);
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => [
"X-Api-Key: {$this->apiKey}",
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $method === 'POST' ? json_encode($data) : null,
]);
$json = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
throw new \RuntimeException("QLAB API error {$httpCode}: {$json}");
}
return json_decode($json, true) ?? [];
}
}
| Task | Endpoint |
|---|---|
| Get orders | GET /orders |
| Download PDF | GET /orders/{id}/pdf |
| Create order | POST /orders |
Setting up result synchronization
An agent checks incomplete orders every 15 minutes. We implement the agent on an event-driven model using \Bitrix\Main\Scheduler\Scheduler or simply cron. Example code:
function SyncLabResults(): string
{
$qlabClient = new QlabApiClient(QLAB_API_KEY);
// Active orders without final status
$pendingOrders = LocalLabOrdersTable::getList([
'filter' => ['STATUS' => ['received', 'processing']],
'select' => ['ID', 'EXTERNAL_ORDER_ID', 'USER_ID'],
]);
while ($order = $pendingOrders->fetch()) {
try {
$lisData = $qlabClient->getOrderResults($order['EXTERNAL_ORDER_ID']);
if (($lisData['status'] ?? '') === 'completed') {
// Save results
foreach ($lisData['results'] ?? [] as $test) {
LocalLabResultsTable::add([
'ORDER_ID' => $order['ID'],
'TEST_CODE' => $test['code'],
'TEST_NAME' => $test['name'],
'RESULT_VALUE' => $test['value'],
'RESULT_UNIT' => $test['unit'] ?? '',
'REFERENCE_MIN' => $test['ref_min'] ?? '',
'REFERENCE_MAX' => $test['ref_max'] ?? '',
'IS_ABNORMAL' => ($test['abnormal'] ?? false) ? 'Y' : 'N',
'RESULT_DATE' => $test['result_date'],
]);
}
// Download and save PDF
$pdf = $qlabClient->getResultPdf($order['EXTERNAL_ORDER_ID']);
$pdfPath = "/upload/lab-results/{$order['USER_ID']}/{$order['EXTERNAL_ORDER_ID']}.pdf";
file_put_contents($_SERVER['DOCUMENT_ROOT'] . $pdfPath, $pdf);
// Update status
LocalLabOrdersTable::update($order['ID'], [
'STATUS' => 'completed',
'SYNCED_AT' => new \Bitrix\Main\Type\DateTime(),
]);
// Notify patient
$user = \Bitrix\Main\UserTable::getById($order['USER_ID'])->fetch();
\CEvent::Send('LAB_RESULTS_READY', SITE_ID, [
'EMAIL' => $user['EMAIL'],
'NAME' => $user['NAME'],
'ORDER_ID' => $order['EXTERNAL_ORDER_ID'],
'RESULT_URL' => '/personal/lab-results/' . $order['ID'] . '/',
]);
}
} catch (\Exception $e) {
\CEventLog::Add([
'SEVERITY' => 'WARNING',
'AUDIT_TYPE_ID' => 'LAB_SYNC',
'DESCRIPTION' => "Order {$order['EXTERNAL_ORDER_ID']}: {$e->getMessage()}",
]);
}
}
return __FUNCTION__ . '();';
}
Importance of access rights check
Critical: check USER_ID on every request to results. Each patient sees only their own data. PDF files are stored outside the webroot or protected via PHP:
// /local/ajax/download-lab-result.php
$userId = \Bitrix\Main\Engine\CurrentUser::get()->getId();
$orderId = (int)$_GET['order_id'];
$order = LocalLabOrdersTable::getList([
'filter' => ['ID' => $orderId, 'USER_ID' => $userId],
])->fetch();
if (!$order) {
http_response_code(403);
exit;
}
$pdfPath = $_SERVER['DOCUMENT_ROOT'] . '/upload/lab-results/' . $userId . '/' . $order['EXTERNAL_ORDER_ID'] . '.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="result-' . $order['EXTERNAL_ORDER_ID'] . '.pdf"');
readfile($pdfPath);
How we perform the integration?
- Analyze the specific LIS API, agree on data format.
- Develop a PHP client for interaction.
- Create order and result tables (HL-blocks or custom tables).
- Set up the synchronization agent (including PDF download, notifications).
- Build a "My Tests" component in the personal account.
- Implement access protection and testing.
Each stage is documented. Before launch to production, we perform load testing: simulate simultaneous status synchronization for 100+ orders to identify bottlenecks before going live.
Monitoring and error handling
Integration with LIS requires robust handling of abnormal situations: API unavailability, incorrect data format, network timeouts with large result volumes.
We implement a three-level defense against typical failures. Retry logic: on error, the agent retries the request after 15 minutes, up to 3 attempts; if all fail, the order is marked with a sync_error flag. Alerts: if 5 or more errors occur within an hour, the responsible employee receives an email via \CEvent::Send(). Administrative log: a page in Bitrix shows a table of the last 100 orders with sync status and detailed error log. This reduces typical detection time from several hours to 10 minutes, and the share of patients who do not receive results on time is reduced threefold compared to systems without monitoring. Setting up monitoring, alerts, and the administrative event log is included in the base integration package at no extra cost.
More about security
Additionally, we configure logging of all result requests and integration with the monitoring system.What's included in the work?
- Analysis of the specific LIS API, agreement on data format.
- Development of a PHP LIS API client.
- Order and result tables (HL-blocks or custom tables).
- Result synchronization agent with PDF download support.
- "My Tests" component in the personal account.
- Email notification when results are ready.
- Access protection for PDFs and result data.
- Integration documentation and staff training.
- Support for one month after launch.
Timeline and cost
Timeline: 3–5 weeks if the LIS has a documented REST API. 6–10 weeks for SOAP or nonstandard protocols. Cost is calculated individually after API analysis, typically ranging from $3,000 to $8,000 depending on complexity. We have delivered 100+ turnkey integration projects with an average patient satisfaction rate of 98.5%. Order turnkey, and your patients will start receiving results in their personal account within a month.







