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": 1, "page_next": 0, "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 |
attribute | attribute_id, attribute_name |
language | language_id, code, name |
print_template | нічого не дописується |
| решта | власний ідентифікатор і 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 буде порожнім.
Пакетні операції з товарами відповідають інакше: у result лежить обʼєкт. Після додавання в ньому status, total_item_add — скільки товарів створено, і item_list_add — список саме створених, з product_id і uuid кожного. Після оновлення — total_item_update. Якщо пакет відхилено цілком, status дорівнює 0, а в status_text причина: наприклад, «Невідоме поле» з назвою поля або «Максимальна кількість товарів» із межею. Звіряйте total_item_add з кількістю відправлених рядків: частину рядків пакет пропускає без помилки, про це — у розділі про обрив і повтор.
Пакетні операції
Замість полів одного запису передається 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.
Пакетне оновлення api/scope/update свою позначку не бере з запиту: кожному оновленому товару воно записує в «Джерело: код» значення api. Тобто ваша позначка живе, доки товар не оновили пакетом, — враховуйте це, якщо збираєтеся шукати товари інтеграції за нею (відкат помилкового пакета).
Видалення
Ідентифікатор передається в полі з іменем довідника: category_id, product_id, manufacturer_id, attribute_id, attribute_block_id, article_id, document_id.
Передавайте лише те поле, що відповідає довіднику. Якщо потрібного ідентифікатора немає, запит не завершиться помилкою — обробка піде далі за списком довідників і може дійти до видалення документа, якщо в тілі запиту трапиться document_id. Відповідь при цьому назве той довідник, який ви просили. Не складайте універсальне тіло запиту з ідентифікаторами різних довідників.
Проведені документи не видаляються — така спроба повертає порожній результат.
Що відбувається з даними
Розділ для того, хто вже запускає інтеграцію на живому акаунті: що перепишеться поверх ручних правок, що буде після обриву, чия ціна переможе і як прибрати наслідки помилкового пакета.
Повторне оновлення поверх ручних правок
api/scope/update для товарів переписує рівно ті поля, які є в запиті, а решту не чіпає. Товар шукається тільки за product_id: рядок з ідентифікатором, якого в базі немає, пропускається без помилки й не входить у total_item_update.
Прапорці картки «Не оновлювати» і «Фіксована ціна» не зупиняють оновлення через API. Якщо менеджер поправив назву чи ціну руками й поставив прапорець, наступний update з цим полем перепише значення все одно. Прапорці захищають від оновлення каталогу з прайсів і від наценок, але не від прямого запису через API.
Окремо про вкладені списки в пакетному оновленні:
- якщо в записі є
image_list, усі фото товару видаляються й замінюються присланими; прапорець «Не оновлювати фото» цього не зупиняє. Немаєimage_list— фото лишаються як були; - якщо є
attribute_list, характеристики товару стираються й записуються з пакета. Тут прапорці діють: у товару з «Не оновлювати» або «Не оновлювати атрибути» характеристики лишаються старі.
Поля, позначені в реєстрі полів як «тільки для читання», пакет пропускає без помилки. Поле, якого в реєстрі немає зовсім, відхиляє весь пакет з текстом «Невідоме поле» і його назвою. Перевіряються назви полів першого запису пакета.
Чим закріпити своє значення:
| Ситуація | Що робити |
|---|---|
| Поле веде людина в Elbuz, інтеграція його не має чіпати | не передавати це поле в update. В одиночному оновленні (без item_list) значення -1 теж означає «поле не чіпати» |
| Потрібно дописувати дані, не затираючи заповнене | замість update слати api/scope/add з параметром update_exist_product=1 — його правила нижче |
Додавання з update_exist_product=1 знаходить наявні товари так само, як при захисті від дублів (нижче), і оновлює їх за іншими правилами, ніж update:
- порожнє значення або нуль у пакеті не затирає заповнене поле. Виняток — поля, де нуль означає дію:
quantity,status,sort_order,subtract_quantity,shippingі всі поляflag_*; - ціни (
price,price_rrp,price_old,price_cost,price_special) не змінюються у товарів з «Фіксована ціна»; - товари з «Не оновлювати» пропускаються цілком.
Без update_exist_product додавання поля наявних товарів не оновлює: знаходить і пропускає. Лише привʼязки до категорій зі списку category_list наявним товарам дописуються. Замість прапорця можна передати field_list_update_exist_product — список полів, які оновити; тоді пишуться тільки вони, так само оминаючи товари з «Не оновлювати» й ціни товарів із «Фіксована ціна» (крім price_special — її цей режим не захищає). Назви, яких немає серед полів товару, пропускаються без помилки — решта списку оновлюється.
Обрив посеред пакета і повтор
Пакет на 10 000 товарів сервер обробляє як послідовність окремих записів у базу, без транзакції: спершу категорії й виробники, потім самі товари, потім категорії товарів, характеристики, фото, знижки. Якщо обробка обірвалася посередині, в базі лишається те, що встигло записатися.
Повтор того самого пакета товари не задвоює. Перед записом кожен рядок шукається серед наявних товарів:
- якщо в рядку є
uuid— за ним; - якщо
uuidпорожній — за збігом трьох полів разом:mpn, назва йext_url(без урахування регістру й пробілів по краях).
Знайдений товар удруге не створюється. Але фото й характеристики при додаванні пишуться тільки новоствореним товарам. Якщо обрив стався після створення товарів, але до запису фото, повтор фото не додасть — товар уже вважається наявним. Характеристики, яких бракує, повтор допише, якщо передати update_existing_attr[product_values]=1: відсутні значення додаються, наявні не чіпаються. Фото доведеться дослати через api/scope/update з image_list.
Передавайте в кожному товарі власний стабільний uuid. Тоді повтор знаходить товар однозначно, навіть якщо ви між спробами змінили назву.
Без помилки пакет пропускає два види рядків, і їх видно лише з того, що total_item_add менший за відправлене:
- рядок без назви — відкидається до запису;
- новий товар без категорії — не створюється. Категорію пакет визначає за
category_id, за повним шляхом уcategory_name_full(частини через/; якщо жодної частини шляху в базі немає, ланцюжок категорій створюється, інакше товар лягає в найглибшу знайдену за назвою) або за спискомcategory_list. Не вийшло жодним способом — товар не записується. Параметрcategory_create_by_source=1кладе такі товари в окрему службову категорію з кодом джерела в назві; вона створюється вимкненою й без вивантаження.
Чия ціна залишиться
Ціну товару в Elbuz пишуть три механізми: API, наценки базового каталогу й оновлення каталогу з прайс-листів. Хто записав останнім, того значення й лишиться — з такими правилами:
| Хто пише | Що робить з ціною | Що його зупиняє |
|---|---|---|
api/scope/update | пише ціну як є, наценки після себе не запускає | нічого: «Фіксована ціна» й «Не оновлювати» не діють |
api/scope/add | пише вашу ціну, а потім у тому ж запиті проганяє наценки базового каталогу по щойно створених товарах: якщо є активні правила наценки й ви передали price_cost більше нуля, ціна перераховується із закупівельної | «Фіксована ціна» або «Не оновлювати» в самому пакеті |
| оновлення каталогу з прайсів і наценки | переписують ціну товарів, зіставлених з прайсом | «Фіксована ціна» (і «Не оновлювати» — для наценок) |
Тобто ціна, яку ви передали через API, доживе лише до наступного оновлення каталогу чи прогону наценок, якщо в товару не стоїть «Фіксована ціна». Щоб ціна з API лишалася головною, передайте разом із нею flag_fix_price=1. Прапорець знімається сам після дати в полі «Дата для зняття прапора "Фіксована ціна"», якщо її задано, — це перевіряється на кожному оновленні каталогу.
Коли додане потрапить на сайт і у вивантаження
Самі операції API нічого не вивантажують: ні add, ні update не запускають тунель чи вивантаження файлу. Товар потрапить далі найближчим прогоном того вивантаження, яке у вас налаштоване, — вручну чи за розкладом.
У межах самого запиту додавання вже зроблено все, що треба каталогу: наценки, службові дані для сортування й зведена (плоска) таблиця товарів. Якщо в акаунті кілька активних магазинів, нові товари додаються до магазину з параметра store_id, а без нього — до магазину з номером 1.
Тонке місце — режим тунелю «Вивантажувати лише нові дані». У ньому тунель бере тільки товари зі знятим «Прапор: вивантажено».
- Якщо товар створено через
add, прапорець знятий, і товар поїде в найближчий прогін. - Якщо товари змінено пакетним
update(зitem_list), прапорець теж знімається, і зміна поїде в найближчий прогін. - Якщо змінено один товар без
item_list, прапорець знімається, коли в запиті є хоч одне поле самого товару (ціна, залишок тощо). Запит лише з текстовими полями — назвою, описами, мета-тегами — прапорець не знімає, і в цьому режимі така зміна на сайт не поїде. Щоб поїхала, передайте в тому самому запитіflag_exported=0.
Видалення через api/scope/delete прибирає товар і з сайту, якщо в налаштуваннях тунелю дозволено видаляти товари на сайті: відразу, коли ввімкнено ще й «Видаляти дані відразу на сайті», інакше — найближчим прогоном тунелю. Без дозволу видаляти товари на сайті він лишається.
Видалене й зникле
Інкрементальна синхронізація за date_modified про видалення не дізнається. Видалений товар стирається з бази повністю — разом з описами, фото, характеристиками й привʼязками до категорій, а серед довідників get журналу видалень немає. Разом із головним товаром видаляються і його товари-опції.
Щоб зловити видалене, періодично забирайте повний перелік ідентифікаторів (limit_field=product_id, max_rows=500, обхід по page_next) і звіряйте зі своїм: чого немає в Elbuz, того вже не існує.
У зворотний бік працює те саме правило: Elbuz нічого не видаляє за вас. Товар, якого у вашій системі не стало, add і update не зачіпають — він лишається в Elbuz з останніми записаними даними. Прибрати його можна лише явним api/scope/delete з його product_id.
Відкат помилкового пакета
Якщо пакет add завів зайві чи зіпсовані товари, знайдіть їх за позначкою джерела. Товари, створені пакетом, мають у полі «Джерело: код» ваш source_code (або jumper, якщо ви його не передавали).
- Через API:
api/scope/getз фільтромfilterdatafield0=source_code,filtercondition0=EQUAL,filtervalue0=my_erpдасть перелік ідентифікаторів. Для відбору саме цього прогону додайте другу умову заdate_added. - У програмі: у таблиці товарів базового каталогу відфільтруйте колонку «Джерело: код», відмітьте знайдені й видаліть — програма перепитає «Видалити вибрані товари?» з кількістю.
api/scope/delete видаляє один товар на запит, тож відкат тисячі товарів через API — це тисяча запитів у межах частоти. Таблиця товарів видаляє всі відмічені однією дією.
Пакетне оновлення перезаписує «Джерело: код» на api. Товари, які після помилкового додавання ще й оновлювали пакетом, за вашим source_code уже не знайдуться — шукайте їх за date_added або за своїм uuid.
З помилковим update складніше: запит оновлення попередніх значень ніде не зберігає, і відкотити його засобами API не вийде. Відновлювати доведеться повторним update з правильними даними з вашої системи.
Документи: статус і залишок
Документ, створений через api/scope/add зі scope=document:
- потребує
type_id— без нього відповідь «Не вказано тип документа», з неіснуючим — «Тип документа не знайдено»; - лягає зі статусом «Новий», якщо
status_idне передано; - отримує номер автоматично, за нумерацією свого типу;
- не проводиться, якщо не передати
is_sign=1. Без цього параметра залишок і резерв такий документ не змінює: резерв рахується тільки під проведені замовлення, а залишок рухає проведення. Зis_sign=1документ проводиться одразу в тому самому запиті — так само, як кнопкою проведення в програмі, з рухом залишків, а у відповіді зʼявляється полеis_sign.
Позиції документа передаються в item_list з product_id. Позиція з товаром, якого немає в базі, не записується, а у відповіді отримує статус Error і текст «Товар не знайдено»; решта документа створюється.
Від дублів при повторі захищає параметр check_unique_field_name — назва поля документа, унікального для вашої системи (наприклад, де лежить номер вашого замовлення). Якщо документ з таким значенням уже є, новий не створюється, а відповідь повертає «Документ уже існує» з document_id наявного. Для перевірки за парою полів є ще check_unique_field_name_2. Назва має бути точною назвою поля документа. Якщо поля з назвою з check_unique_field_name немає, перевірка не виконується й документ створюється; неіснуюча назва в check_unique_field_name_2 лише прибирає другу умову — перевірка йде за першим полем. Якщо значення поля в самому запиті не передали, воно вважається порожнім.
Помилки
Помилки api/scope/* приходять масивом з одного рядка:
["Увага: у вас немає дозволу на зміну даних!"]
| Повідомлення | Причина | Що робити |
|---|---|---|
| немає дозволу на доступ до API | токен не переданий, сеанс закінчився або ключ вимкнено | повторити api/login; якщо не допомогло — перевірити «Активність» |
| немає дозволу на зміну даних | у ключа стоїть «Тільки для читання» | зняти прапорець у картці ключа |
| Не задано код довідника | немає параметра scope | додати scope або дописати його в маршрут |
| Невідомий код довідника | довідник не підтримується цією операцією | звірити з таблицею довідників |
| Елемент не знайдено | вибірка порожня | перевірити фільтр; це не збій |
{"error":{"key":…}} | вхід: не той користувач або ключ | звірити пару з карткою ключа |
{"error":{"ip":…}} | вхід: адреса не в списку | адреса названа в тексті помилки — вписати її в ключ |
{"Error":"Rate limit exceeded…"} | запити частіше за дозволений проміжок | пауза й повтор |
Успішні запити записуються в журнал роботи як операція типу «api» разом із тілом відповіді — це найпростіший спосіб побачити, що саме робила інтеграція. Відповіді з помилкою (немає дозволу, невідомий довідник, «Елемент не знайдено» тощо) і add, який нічого не додав, у журнал не потрапляють — такі збої шукайте у відповідях, які отримала ваша інтеграція.
Сценарії
Сайт тільки забирає залишки й ціни
Задача: сайт читає з Elbuz залишки й ціни і не повинен нічого змінювати в акаунті.
Що ввімкнути: окремий ключ для сайту з прапорцем «Тільки для читання», у полі «IP-адреси для доступу» — адреса сервера сайту. Запит — api/scope/get з limit_field=sku,price,quantity, як у рецепті нижче.
Що вийде: читання працює. Будь-який add, update чи delete цим ключем відповідає «немає дозволу на зміну даних» — навіть якщо в коді сайту помилка і він спробує щось записати. Запит з іншої адреси не пройде ні на вході, ні з уже виданим токеном.
Тимчасовий доступ для стороннього розробника
Задача: підрядник налаштовує інтеграцію, і доступ має закритися разом із проєктом.
Що ввімкнути: окремий ключ, у «Дата закінчення доступу» — останній день робіт, у «Примітка» — кому й навіщо виданий.
Що вийде: ключ працює по цю дату включно. Наступного дня його не пускає ні api/login, ні токен, отриманий раніше, — чекати, поки сеанс закінчиться сам, не треба. Закінчили раніше — зніміть «Активність», діє так само. Чого не станеться: інші ключі акаунта від цього не залежать, у кожного своя дата й свій прапорець.
Облікова система шле ціни, а частину товарів менеджер веде руками
Задача: ціни й залишки приходять з обліку, але на окремих товарах менеджер ставить свою ціну, і обмін не повинен її затерти.
Що ввімкнути: на товарах менеджера — «Фіксована ціна», а там, де не можна чіпати нічого, — «Не оновлювати». Інтеграція шле api/scope/add з item_list, update_exist_product=1 і тим самим uuid, з яким товар заводила.
Що вийде: наявні товари знаходяться за uuid і оновлюються. Ціни товарів із «Фіксована ціна» лишаються менеджерськими, у товарів з «Не оновлювати» поля й характеристики не змінюються, а порожнє поле в пакеті не затирає заповнене (крім кількості, статусу й інших полів, де нуль означає дію, — перелік вище). Чого не станеться: захисту не буде, якщо ту саму зміну відправити через api/scope/update, — там «Фіксована ціна» й «Не оновлювати» поля товару не захищають, і ціна перепишеться.
Замовлення з сайту без дублів при повторі
Задача: сайт передає замовлення в Elbuz і при збої мережі повторює запит — друге таке саме замовлення зʼявитися не повинно.
Що ввімкнути: api/scope/add зі scope=document і type_id; номер замовлення сайту — в полі документа, а назва цього поля — в check_unique_field_name.
Що вийде: документ лягає зі статусом «Новий» і номером за нумерацією свого типу. Повтор з тим самим номером відповідає «Документ уже існує» з document_id першого, другий документ не створюється. Чого не станеться: залишок і резерв не зміняться, якщо не передати is_sign=1, — без нього документ лишається непроведеним, і проводять його в програмі.
Перше завантаження каталогу, яке можна прибрати
Задача: залити каталог з обліку так, щоб після обриву можна було повторити, а невдалий прогін — знайти й видалити.
Що ввімкнути: api/scope/add з item_list, source_code=my_erp, у кожного товару — власний uuid і шлях у category_name_full.
Що вийде: створені товари мають у полі «Джерело: код» значення my_erp. Товар лягає в найглибшу категорію шляху, знайдену за назвою, а якщо жодної частини шляху в базі немає — ланцюжок категорій створюється. Повтор пакета після обриву знаходить уже створене за uuid і не задвоює. Невдалий прогін знаходиться фільтром за «Джерело: код» — як це зробити. Чого не станеться: повтор не додасть фото товарам, які встигли створитися до обриву, а після api/scope/update позначка джерела зміниться на 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) {
do {
$res = elbuz_call($host, 'api/scope/add', array(
'api_token' => $token,
'scope' => 'product',
'source_code' => 'my_erp', // позначка джерела в полі «Джерело: код»
'item_list' => $chunk, // у кожного товару — ваш uuid, див. нижче
));
sleep(2);
} while (isset($res['Error'])); // межа частоти — повторити ту саму пачку
}
Повтор тут іде циклом навколо тієї самої пачки. Просте continue перейшло б до наступної пачки, і відхилена тисяча товарів не потрапила б в Elbuz зовсім.
Тисяча записів у пачці — компроміс: межа 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 забирає дані ззовні.
- Журнал роботи — де видно кожен запит інтеграції.

