API дає зовнішній програмі доступ до даних вашого акаунта: товари, прайс-листи, контрагенти, документи, довідники. Це стаття-довідник: усі приклади нижче виконані на живому акаунті, і відповіді в них справжні.
Стаття розрахована на розробника. Адміністратору достатньо першого розділу: завести ключ і передати його тому, хто пише інтеграцію.
Крок 1. Ключ доступу
- Вікно
- Налаштування системи → розділ «Інші налаштування» → плитка «API та токени доступу»
- Що створюєте
- пару «користувач + ключ доступу» для однієї зовнішньої програми
- Скільки
- окремий ключ на кожну інтеграцію
1
Плитка «API та токени доступу»2
Розділ «Інші налаштування»- Плитка «API та токени доступу» — звідси відкривається список ключів API.
- Розділ «Інші налаштування» — сюди зібрані вікна, яких немає в головному меню.
| Поле картки ключа | Параметр у запиті | Що робить |
|---|---|---|
| «Користувач» | username | імʼя для входу |
| «Ключ доступу» | key | секрет; кнопка «Генерувати» створює його за вас |
| «Активність» | — | вимкнений ключ не пускає ні на вхід, ні за вже виданим токеном |
| «Тільки для читання» | — | будь-яка зміна даних відповідає помилкою; читання працює |
| «IP-адреси для доступу» | — | перелік через кому, пробіл або з нового рядка. Порожньо — звідусіль |
| «Дата закінчення доступу» | — | ключ працює по цю дату включно; порожня дата — безстроково |
| «Примітка» | — | кому й навіщо виданий ключ |
Три поля закривають доступ миттєво, а не з наступного входу. «Активність», «Дата закінчення доступу» й «Тільки для читання» перечитуються з картки ключа на кожному запиті, тому діють і на вже виданий токен: вимкнули ключ або настав наступний день після дати — поточний токен перестає працювати без перелогіну. Це зручно, коли ключ треба відкликати негайно.
Крок 2. Швидкий старт
Мінімальний робочий скрипт: вхід, запит даних, друк результату. Підставте свою адресу, імʼя користувача та ключ.
<?php
$host = 'https://ваш-акаунт.elbuz.com'; // адреса вашого акаунта
$user = 'site'; // «Користувач» з картки ключа
$key = 'ВАШ_КЛЮЧ'; // «Ключ доступу»
function elbuz_call($host, $route, array $post) {
$ch = curl_init($host . '/admin/index.php?route=' . $route);
curl_setopt_array($ch, array(
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($post),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
));
$body = curl_exec($ch);
return json_decode($body, true);
}
// 1. вхід — отримуємо токен
$login = elbuz_call($host, 'api/login', array('username' => $user, 'key' => $key));
if (empty($login['api_token'])) {
exit('Не вийшло увійти: ' . json_encode($login, JSON_UNESCAPED_UNICODE));
}
$token = $login['api_token'];
// 2. запит даних
$res = elbuz_call($host, 'api/scope/get', array(
'api_token' => $token,
'scope' => 'category',
'max_rows' => 2,
'limit_field' => 'name',
));
print_r($res);
Відповідь на вхід:
{
"success": "Сеанс API успішно розпочато!",
"api_token": "b439dd5c3b386d36464bef450d"
}
Відповідь на запит категорій — дослівно те, що повернув сервер:
{
"item_list": {
"83": {
"category_id": "83",
"parent_id": "11",
"name": "Варочные поверхности"
},
"84": {
"category_id": "84",
"parent_id": "11",
"name": "Встраиваемые вытяжки"
}
},
"meta": {
"item_scope": "category",
"total_row": 234,
"page_total": 117,
"page_next": 2,
"page_current": 1,
"request_time": "00:00:00"
}
}
Зверніть увагу: у запиті було limit_field=name, а у відповіді ще й category_id із parent_id. Так і має бути — обовʼязкові поля довідника дописуються завжди.
Основи протоколу
| Що | Як |
|---|---|
| Базова адреса | адреса акаунта + /admin/index.php?route= + маршрут |
| Метод | тільки POST |
| Тіло запиту | звичайна форма, application/x-www-form-urlencoded |
| Відповідь | JSON із заголовком Content-Type: application/json |
| Код HTTP | завжди 200 — навіть на помилку |
| Токен | параметр api_token у тілі POST або в адресі |
Не орієнтуйтеся на код HTTP: помилка теж приходить із кодом 200. Розбирайте структуру: помилка — це JSON-масив рядків, успішна відповідь — обʼєкт із item_list або meta.
Час життя токена
Токен — це ідентифікатор сеансу. Сеанси, до яких не зверталися більше години, видаляються; кожен запит із токеном продовжує сеанс. Тому постійно працююча інтеграція входить один раз, а новий api/login робить лише у відповідь на помилку доступу.
function elbuz_get($host, $user, $key, &$token, array $post) {
$post['api_token'] = $token;
$res = elbuz_call($host, 'api/scope/get', $post);
// помилка приходить масивом рядків — можливо, сеанс закінчився
if (isset($res[0]) && is_string($res[0])) {
$login = elbuz_call($host, 'api/login', array('username' => $user, 'key' => $key));
if (empty($login['api_token'])) return $res; // справа не в сеансі
$token = $login['api_token'];
$post['api_token'] = $token;
$res = elbuz_call($host, 'api/scope/get', $post);
}
return $res;
}
Обмеження частоти
У ключа є межа звернень — за замовчуванням 30 за хвилину. Влаштована вона як мінімальний проміжок: 60 поділити на це число, тобто за замовчуванням не частіше ніж раз на дві секунди. Накопичити паузу й видати пачку запитів поспіль не можна — відхилений буде кожен, що прийшов раніше за проміжок.
{"Error": "Rate limit exceeded! Time passed:0.0582 Time minimal request:0.1"}
У відповіді видно обидва числа: скільки минуло від минулого запиту й скільки потрібно. Формат тут інший, ніж у решти помилок, — ключ Error з великої літери й обʼєкт замість масиву. Обробляйте обидва варіанти:
if (isset($res['Error'])) { // межа частоти
sleep(2);
// повторити той самий запит
}
if (isset($res[0]) && is_string($res[0])) { // звичайна помилка API
throw new RuntimeException($res[0]);
}
З інтерфейсу межа не міняється — це питання до технічної підтримки.
Мова відповідей
Тексти приходять мовою співробітника, від імені якого працює ключ — це власник акаунта. А помилка доступу без токена приходить мовою бази за замовчуванням, бо співробітник ще не визначений. Тобто два повідомлення одного API можуть бути різними мовами. Ще один аргумент розбирати відповідь за структурою, а не за текстом.
Довідник операцій
| Маршрут | Призначення | Обовʼязкові параметри |
|---|---|---|
api/login | отримати токен | username, key |
api/scope/describe | опис полів довідника | api_token, scope |
api/scope/get | отримати записи | api_token, scope |
api/scope/add | додати запис або пачку | api_token, scope + поля запису |
api/scope/update | оновити | api_token, scope, ідентифікатор |
api/scope/delete | видалити | api_token, scope, ідентифікатор |
api/scope/search | пошук товарів пошуковим індексом | api_token, scope=product |
Довідник задають параметром scope або дописують у маршрут — обидва варіанти рівнозначні:
api/scope/get + scope=currency
api/scope/get/currency
Довідники за операціями
Набори різні. Це перше, що варто звірити перед проєктуванням інтеграції: читати можна майже все, змінювати — далеко не все.
| Операція | Коди довідників |
|---|---|
get | category, product, product_price, attribute, attribute_block, language, article, manufacturer, stock_status, warehouse, store, company, contractor, contractor_group, currency, document, document_type, document_status, document_payment_status, document_delivery_status, print_template |
add | category, product, product_price, manufacturer, contractor, document, document_extended, user, company, attribute, attribute_block, article, contractor_price, mail_send |
update | category, product, currency, contractor, document |
delete | category, product, contractor, attribute_block, attribute, manufacturer, article, document |
describe | product, product_price, manufacturer, contractor, document, user, company |
search | product |
product_price є в додаванні, але немає в оновленні: рядки прайсу через API створюються, а змінюються тільки завантаженням прайс-листа. Плануйте інтеграцію з урахуванням цього.
describe: звідки брати назви полів
Назви полів у запитах — не ті, що підписані в інтерфейсі. describe повертає реєстр полів: внутрішню назву, людський підпис і тип.
$res = elbuz_call($host, 'api/scope/describe', array(
'api_token' => $token,
'scope' => 'contractor',
'limit_field' => 'datafield,datafield_type,grid_field_name',
));
{
"item_list": {
"347": {"row_id": "347", "datafield": "name", "datafield_type": "string", "grid_field_name": "Назва"},
"349": {"row_id": "349", "datafield": "phones", "datafield_type": "string", "grid_field_name": "Телефон"},
"560": {"row_id": "560", "datafield": "status_name", "datafield_type": "string", "grid_field_name": "Статус"},
"562": {"row_id": "562", "datafield": "manager_name", "datafield_type": "string", "grid_field_name": "Відповідальний менеджер"}
},
"meta": {"item_scope": "contractor", "total_row": 118, "page_total": 2, "page_next": 2, "page_current": 1, "request_time": "00:00:00"}
}
Це той самий реєстр, який видно у вікні «Налаштувати сітку» з перемикачем «Тех. назви». Без limit_field кожне поле приходить із трьома десятками службових властивостей — беріть три потрібні колонки.
get: вибірка даних
Параметри
| Параметр | За замовчуванням | Що робить |
|---|---|---|
scope | — | код довідника, обовʼязковий |
page | 1 | номер сторінки |
max_rows | 100 | записів на сторінці. Стеля 500 у хмарі, 10 000 в офлайн-версії; більше просто обрізається |
language_id | мова акаунта | мова текстових полів |
limit_field | усі поля | перелік потрібних полів через кому |
extend | — | вкладені дані: фото, характеристики, позиції документа |
filterscount і далі | — | умови відбору |
Обовʼязкові поля
Навіть якщо ви їх не просили, ці поля приходять завжди — без них відповідь неможливо зіставити з вашими даними:
| Довідник | Дописуються завжди |
|---|---|
product | product_id, name, price |
product_price | supply_product_id, contractor_id, price_id, product_id, sku_manufacturer, manufacturer, name, price, stock_status_id, field_ssd_stock_status_name |
category | category_id, parent_id, name |
document | document_id, contractor_id, type_id, document_type_name, document_number |
| решта | власний ідентифікатор і name |
Сторінки
Записи приходять обʼєктом, а не масивом: ключ — ідентифікатор запису. У блоці meta лежить усе для обходу, ознака кінця — page_next дорівнює нулю.
$page = 1;
$all = array();
do {
$res = elbuz_call($host, 'api/scope/get', array(
'api_token' => $token,
'scope' => 'product',
'max_rows' => 500, // стеля для хмари
'limit_field' => 'product_id,name,price,quantity',
'page' => $page,
));
if (!isset($res['item_list'])) break; // помилка або порожня вибірка
$all += $res['item_list']; // ключі = ID, дублів не буде
$page = (int)$res['meta']['page_next'];
sleep(2); // межа частоти за замовчуванням
} while ($page > 0);
echo 'отримано: ' . count($all);
На довіднику валют із тринадцяти записів по пʼять на сторінку цей цикл робить рівно три запити: третя сторінка повертає page_next: 0, і обхід завершується.
Порожня вибірка — це не порожній item_list, а помилка «Елемент не знайдено». Якщо фільтр нікого не пропустив, перевірка isset($res['item_list']) обірве цикл — саме тому вона стоїть до розбору.
Фільтри
Для product і document працює той самий відбір, що й у сітках інтерфейсу: filterscount — скільки умов, далі для кожної четвірка параметрів із номером, починаючи з нуля.
| Параметр | Значення |
|---|---|
filterdatafield0 | поле, як у describe |
filtervalue0 | значення |
filtercondition0 | EQUAL, NOT_EQUAL, CONTAINS, STARTS_WITH, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL |
filteroperator0 | 0 — зʼєднати з наступною умовою через «і», 1 — через «або» |
Товари, змінені після заданої дати:
$res = elbuz_call($host, 'api/scope/get', array(
'api_token' => $token,
'scope' => 'product',
'max_rows' => 2,
'limit_field' => 'product_id,name,quantity,date_modified',
'filterscount' => 1,
'filterdatafield0' => 'date_modified',
'filtervalue0' => '2026-01-01',
'filtercondition0' => 'GREATER_THAN_OR_EQUAL',
'filteroperator0' => 0,
));
{
"item_list": {
"6068": {
"product_id": "6068",
"price": "1200.00000000",
"quantity": "0",
"date_modified": "2026-09-12 14:54:23",
"name": "Ardesto LESH-Y305"
}
},
"meta": {
"item_scope": "product",
"total_row": 126999,
"page_total": 63500,
"page_next": 2,
"page_current": 1,
"request_time": "00:00:00"
}
}
Фільтрувати можна й за характеристиками: імʼя поля — attribute_ плюс номер характеристики, його дає describe.
Вкладені дані
Фото, характеристики й позиції документа лежать не в самому записі — їх домовляють параметром extend через кому.
| Довідник | Значення extend | Поле у відповіді |
|---|---|---|
product | attribute, image, file, video, category | extend_attribute, extend_image, extend_file, extend_video, extend_category |
product_price | attribute, image | ті самі |
document | contractor, product | extend_contractor, extend_product |
$res = elbuz_call($host, 'api/scope/get', array(
'api_token' => $token,
'scope' => 'product',
'max_rows' => 1,
'limit_field' => 'product_id,name',
'extend' => 'image,category',
));
{
"item_list": {
"6068": {
"product_id": "6068",
"price": "1200.00000000",
"name": "Ardesto LESH-Y305",
"extend_image": [
{
"product_image_id": "201587",
"image": "100/68/product_image_6068_201587.jpg",
"sort_order": "1",
"image_original": "https://…/product_image_6068_201587.jpg",
"image_cloud_url": ""
}
],
"extend_category": [ {"category_id": "1201"} ]
}
}
}
Набір полів вкладеного блока теж керований: extend_limit_field_attribute, extend_limit_field_image, extend_limit_field_product і подібні. За замовчуванням у характеристик це attribute_id, attribute_name, attribute_value, у позицій документа — product_id, sku, mpn, name, quantity, price, price_sum, ean, isbn.
З параметром extend форма відповіді змінюється: item_list перестає бути обʼєктом із ключами-ідентифікаторами й приходить звичайним списком. Код, написаний без extend, зламається рівно тоді, коли хтось його додасть. Розбирайте обидві форми одразу:
$rows = $res['item_list'];
$rows = isset($rows[0]) ? $rows : array_values($rows); // однаково для обох форм
foreach ($rows as $row) { /* … */ }
add, update, delete
Відповідь у всіх трьох однакова й коротка:
{"meta": {"item_scope": "product", "result": 1024, "request_time": "00:00:00"}}
У result лежить те, що повернула операція: для додавання — ідентифікатор нового запису, для решти — ознака успіху. Окремого поля з текстом помилки валідації немає: якщо запис не створився, result буде порожнім.
Пакетні операції
Замість полів одного запису передається item_list — список записів:
$res = elbuz_call($host, 'api/scope/add', array(
'api_token' => $token,
'scope' => 'product',
'item_list' => array(
array('name' => 'Товар 1', 'price' => 100, 'sku' => 'A-001'),
array('name' => 'Товар 2', 'price' => 200, 'sku' => 'A-002'),
),
));
| Довідник | Операція | Записів за раз |
|---|---|---|
product | додавання й оновлення | до 10 000 |
product_price | додавання | до 100 |
Пакет — не просто зручність. З межею в один запит на дві секунди десять тисяч товарів поштучно — це майже шість годин, пакетом — один запит.
Позначка джерела
Параметр source_code в додаванні позначає, звідки прийшли дані; позначка потрапляє в історію запису, і потім видно, що товар завела саме ця інтеграція, а не людина. Без параметра ставиться jumper.
Видалення
Ідентифікатор передається в полі з іменем довідника: category_id, product_id, manufacturer_id, attribute_id, attribute_block_id, article_id, document_id.
Передавайте лише те поле, що відповідає довіднику. Якщо потрібного ідентифікатора немає, запит не завершиться помилкою — обробка піде далі за списком довідників і може дійти до видалення документа, якщо в тілі запиту трапиться document_id. Відповідь при цьому назве той довідник, який ви просили. Не складайте універсальне тіло запиту з ідентифікаторами різних довідників.
Проведені документи не видаляються — така спроба повертає порожній результат.
Помилки
Помилки api/scope/* приходять масивом з одного рядка:
["Увага: у вас немає дозволу на зміну даних!"]
| Повідомлення | Причина | Що робити |
|---|---|---|
| немає дозволу на доступ до API | токен не переданий, сеанс закінчився або ключ вимкнено | повторити api/login; якщо не допомогло — перевірити «Активність» |
| немає дозволу на зміну даних | у ключа стоїть «Тільки для читання» | зняти прапорець у картці ключа |
| Не задано код довідника | немає параметра scope | додати scope або дописати його в маршрут |
| Невідомий код довідника | довідник не підтримується цією операцією | звірити з таблицею довідників |
| Елемент не знайдено | вибірка порожня | перевірити фільтр; це не збій |
{"error":{"key":…}} | вхід: не той користувач або ключ | звірити пару з карткою ключа |
{"error":{"ip":…}} | вхід: адреса не в списку | адреса названа в тексті помилки — вписати її в ключ |
{"Error":"Rate limit exceeded…"} | запити частіше за дозволений проміжок | пауза й повтор |
Кожен запит записується в журнал роботи як операція типу «api» разом із тілом відповіді — це найпростіший спосіб побачити, що саме робила інтеграція.
Готові рецепти
Інкрементальна синхронізація каталогу
Замість того щоб щоночі викачувати весь каталог, забирайте змінене з минулого разу.
$since = file_exists('last_sync.txt') ? trim(file_get_contents('last_sync.txt')) : '2000-01-01';
$started = date('Y-m-d H:i:s');
$page = 1;
do {
$res = elbuz_call($host, 'api/scope/get', array(
'api_token' => $token,
'scope' => 'product',
'max_rows' => 500,
'limit_field' => 'product_id,name,price,quantity,sku',
'page' => $page,
'filterscount' => 1,
'filterdatafield0' => 'date_modified',
'filtervalue0' => $since,
'filtercondition0' => 'GREATER_THAN_OR_EQUAL',
'filteroperator0' => 0,
));
if (!isset($res['item_list'])) break; // «Елемент не знайдено» = змін немає
foreach ($res['item_list'] as $id => $row) {
// ваша логіка: оновити товар у своїй системі
}
$page = (int)$res['meta']['page_next'];
sleep(2);
} while ($page > 0);
file_put_contents('last_sync.txt', $started); // час СТАРТУ, а не завершення
Зберігайте час старту прогону: те, що змінилося під час обходу, тоді потрапить у наступний прогін, а не загубиться між ними.
Пакетне завантаження товарів
$products = array( /* ваш масив товарів */ );
foreach (array_chunk($products, 1000) as $chunk) {
$res = elbuz_call($host, 'api/scope/add', array(
'api_token' => $token,
'scope' => 'product',
'source_code' => 'my_erp', // позначка джерела в історії записів
'item_list' => $chunk,
));
if (isset($res['Error'])) { sleep(2); continue; } // межа частоти — повторити пачку
sleep(2);
}
Тисяча записів у пачці — компроміс: межа 10 000, але чим більша пачка, тим довша відповідь і тим болючіший повтор, якщо запит обірвався.
Віддати сайту залишки й ціни
Найчастіша задача — і найдешевша: потрібні три поля, тож відповідь виходить маленькою, а сторінок мало.
$res = elbuz_call($host, 'api/scope/get', array(
'api_token' => $token,
'scope' => 'product',
'max_rows' => 500,
'limit_field' => 'sku,price,quantity', // product_id і name додадуться самі
));
Коли API не потрібне
Перш ніж писати інтеграцію, перевірте готові механізми — вони закривають більшість задач без розробника:
- Сайт на популярній CMS — тунель пише товари, ціни й залишки прямо в базу сайту.
- Маркетплейс — готові підключення вже є для десятка майданчиків.
- Регулярний файл — вивантаження за розкладом у XML чи XLSX.
API лишається для своєї логіки: нестандартний сайт, внутрішня система обліку, обмін, якого немає в готових форматах.
Розбір частих проблем
| Що ви бачите | Чому так | Що зробити |
|---|---|---|
| Працювало, а через годину перестало | сеанс токена закінчився без звернень | перелогінитися при помилці доступу |
| «Rate limit exceeded» на кожному другому запиті | запити йдуть частіше за проміжок | пауза між запитами або пакетні операції |
| Приходить рівно 100 записів | розмір сторінки за замовчуванням | max_rows до 500 і обхід по page_next |
| Обхід сторінок зациклився | не перевіряється page_next | нуль означає кінець |
| У відповіді зайві поля | обовʼязкові поля довідника дописуються завжди | так і має бути |
Після додавання extend зламався розбір | item_list став списком замість обʼєкта | розбирати обидві форми |
| Порожня вибірка приходить помилкою | так відповідає get, коли нічого не знайшлося | перевіряти наявність item_list |
| Оновлення рядків прайсу не працює | product_price немає в списку оновлення | оновлювати прайс завантаженням файлу |
| Читання працює, запис відхиляється | у ключа «Тільки для читання»; прапорець діє на всі маршрути, зокрема старі | зняти прапорець — діє одразу, без перелогіну |
| Ключ перестав пускати, хоча активний | настав день після «Дати закінчення доступу» | посунути дату або очистити поле — тоді ключ безстроковий |
Часті питання
Де в програмі знайти ключі API?
Налаштування системи → розділ «Інші налаштування» → плитка «API та токени доступу». Окремого пункту в головному меню немає.
Чи треба логінитися перед кожним запитом?
Ні. Сеанс живе, поки ним користуються, і видаляється після години без звернень. Новий вхід робіть у відповідь на помилку доступу.
Скільки запитів на хвилину дозволено?
За замовчуванням тридцять, і рахуються вони як мінімальний проміжок: не частіше ніж раз на дві секунди. Пачку підряд зробити не можна, навіть після довгої паузи.
Як дізнатися справжні назви полів?
Операцією api/scope/describe для потрібного довідника — вона повертає внутрішню назву, підпис і тип кожного поля.
Як синхронізувати каталог, не викачуючи його цілком?
Фільтром за date_modified із датою минулої синхронізації. Зберігайте час старту прогону, а не завершення.
Як завантажити багато товарів за раз?
Параметром item_list в операції додавання: товарів каталогу — до десяти тисяч за запит, рядків прайсу — до ста.
Чи можна орієнтуватися на код відповіді HTTP?
Ні, він завжди 200 — і на успіх, і на помилку. Розбирайте тіло: помилка приходить масивом рядків, успіх містить item_list або meta.
Суміжні теми
- Налаштування системи — звідки відкривається вікно ключів.
- Налаштування сітки — той самий реєстр полів, що повертає
describe. - Elbuz Tunnel — обмін із власним сайтом без програмування.
- Імпорт через API майданчиків — коли навпаки: Elbuz забирає дані ззовні.
- Журнал роботи — де видно кожен запит інтеграції.

