Каталог и остатки
JSON API для каталога наличия: товары, категории, пакетное обновление остатков и цен. Ключ связи с вашей системой — external_id.
Это не импорт файла в модуль «Прайс-листы». Файл прайса: Прайс-листы.
Базовый префикс: /api/integrations/v1
Цены при записи — в копейках. Обозначения: Структура и обозначения.
Подходит для ERP / склада / учётной системы, которая выгружает номенклатуру и остатки в магазин.
Список товаров
GET /products — products.read
Query: since, per_page, cursor, external_id, article.
Snapshot (то же для GET /products/{id}):
{
"id": 100,
"external_id": "prod-1",
"name": "Колодки BOSCH",
"article": "0986AB1234",
"brand_id": 12,
"brand": "BOSCH",
"slug": "kolodki-bosch",
"is_active": true,
"description": null,
"image_source_mode": "download",
"images": [
{ "url": "https://…/photo1.jpg", "is_main": true, "sort_order": 1 }
],
"images_count": 1,
"has_images": true,
"updated_at": "2026-07-10T10:00:00+03:00"
}Перед upsert сначала читайте товар по external_id или id: если has_images: true, блок images в batch можно не передавать — существующие фото не затираются.
Картинки в batch-upsert через images[].url: магазин скачивает URL сам. HTTP 200 и пустой errors не гарантируют, что файл уже лежит на сайте (сбой скачивания пишется в лог, а не в ответ строки). Надёжный путь — загрузка файла POST /media/upload: файл сразу на диск магазина, в ответе готовый url.
curl -sS "https://ВАШ_ДОМЕН/api/integrations/v1/products?external_id=prod-1" \
-H "Authorization: Bearer pp_abc123:ВАШ_СЕКРЕТ" \
-H "Accept: application/json"curl -sS "https://ВАШ_ДОМЕН/api/integrations/v1/products/100" \
-H "Authorization: Bearer pp_abc123:ВАШ_СЕКРЕТ" \
-H "Accept: application/json"Загрузка изображения файлом
POST /media/upload — multipart/form-data.
Права зависят от сущности:
entity_type | Право |
|---|---|
product | products.write |
category | categories.write |
Файл сразу крепится к существующему товару или категории на этом сайте (Spatie Media Library). Временной «свалки» без модели нет: при ошибке валидации файл не остаётся сиротой на диске.
| Поле | Обяз. | Описание |
|---|---|---|
file | да | jpeg / png / gif / webp / svg, до 20 МБ |
entity_type | да | product или category |
id или external_id | да | К чему крепить (достаточно одного) |
replace | нет | Только для товара: 1 — заменить всю галерею этим файлом. У категории всегда одна картинка (старые снимаются после успешной загрузки) |
is_main | нет | Для товара: сделать главным (первый в галерее). При replace и для категории — и так главное |
sort_order | нет | Подсказка порядка (custom property) |
201:
{
"ok": true,
"media_id": 501,
"url": "https://ВАШ_ДОМЕН/storage/…/photo.jpg",
"entity_type": "product",
"entity_id": 100,
"external_id": "prod-1",
"collection": "products",
"is_main": true,
"sort_order": null,
"replaced_previous": false
}Для категории collection = product_categories. После upload товара image_source_mode принудительно download, а GET /products/{id} отдаёт этот url в images[].
curl -sS -X POST "https://ВАШ_ДОМЕН/api/integrations/v1/media/upload" \
-H "Authorization: Bearer pp_abc123:ВАШ_СЕКРЕТ" \
-H "Accept: application/json" \
-F "entity_type=product" \
-F "external_id=prod-1" \
-F "replace=1" \
-F "file=@/path/to/photo.jpg;type=image/jpeg"curl -sS -X POST "https://ВАШ_ДОМЕН/api/integrations/v1/media/upload" \
-H "Authorization: Bearer pp_abc123:ВАШ_СЕКРЕТ" \
-H "Accept: application/json" \
-F "entity_type=category" \
-F "external_id=cat-brakes" \
-F "file=@/path/to/category.png;type=image/png"Типичные ответы при ошибке: 401 / 403 (нет нужного write-права) / 404 (нет сущности) / 422 (нет файла, тип, размер, нет id/external_id).
Batch: категории
Upsert
POST /categories/batch-upsert — categories.write
Тело: массив категорий или { "categories": [ ... ] }.
| Поле | Обяз. | Описание |
|---|---|---|
external_id | да | Внешний id категории |
name | да | Название |
parent_external_id | нет | Родитель |
description / slug / sort_order / is_active / image_url | нет | Прочее; image_url — скачивание по ссылке (как у товара). Надёжнее — POST /media/upload |
200: { "processed", "created", "updated", "errors": [{ "external_id", "message" }] }
Удаление (деактивация)
POST /categories/delete — categories.delete Тело: { "external_ids": ["cat-1", "cat-2"] }200: { "requested", "deactivated", "not_found": [] }
Batch: товары
Upsert
POST /products/batch-upsert — products.write
Тело: массив или { "products": [ ... ] }.
| Поле | Обяз. | Описание |
|---|---|---|
external_id | да | Внешний id товара |
name | да | Название |
brand | нет | Бренд (создастся при необходимости) |
article | нет | Артикул |
category_external_id / category_external_ids | нет | Категории |
replace_categories | нет | Заменить набор категорий |
description_short / description_full | нет | Описания |
slug / sort_order / is_active | нет | Служебные |
attributes[] | нет | Атрибуты (name обязателен вместе с блоком); у атрибута можно external_id, group_external_id |
replace_attributes | нет | Заменить атрибуты |
images[] | нет | { "url", "sort_order?", "is_main?" } — URL для скачивания; надёжнее файл через /media/upload |
200: { "processed", "created", "updated", "errors": [...] }
curl -sS -X POST "https://ВАШ_ДОМЕН/api/integrations/v1/products/batch-upsert" \
-H "Authorization: Bearer pp_abc123:ВАШ_СЕКРЕТ" \
-H "Content-Type: application/json" \
-d '{
"products": [
{
"external_id": "prod-1",
"name": "Колодки BOSCH",
"brand": "BOSCH",
"article": "0986AB1234",
"category_external_id": "cat-brakes",
"is_active": true
}
]
}'{
"processed": 1,
"created": 1,
"updated": 0,
"errors": []
}Удаление (soft-delete)
POST /products/delete — products.delete Тело: { "external_ids": ["prod-1"] }200: { "requested", "deactivated", "not_found": [] }
Остатки и цены
POST /stocks/batch-update — stocks.write
Тело: массив или объект:
{
"storage_id": 1,
"pick_up_point_id": 1,
"stocks": [
{
"product_external_id": "prod-1",
"quantity": 10,
"selling_price": 150000,
"purchase_price": 100000,
"old_price": 170000,
"delivery_days": 2
}
]
}| Поле | Обяз. | Описание |
|---|---|---|
stocks.*.product_external_id | да | Ищет товар по внешнему id |
stocks.*.quantity | да | Остаток ≥ 0 |
stocks.*.purchase_price / selling_price / old_price | нет | Копейки |
stocks.*.supply_date / delivery_days | нет | Поставка |
storage_id / pick_up_point_id | условно | В теле или в настройках API; оба должны быть известны, иначе 422 |
Ненайденный товар попадает в errors, HTTP при этом обычно остаётся 200.
curl -sS -X POST "https://ВАШ_ДОМЕН/api/integrations/v1/stocks/batch-update" \
-H "Authorization: Bearer pp_abc123:ВАШ_СЕКРЕТ" \
-H "Content-Type: application/json" \
-d '{
"storage_id": 1,
"pick_up_point_id": 1,
"stocks": [
{
"product_external_id": "prod-1",
"quantity": 10,
"selling_price": 150000,
"purchase_price": 100000
}
]
}'{
"processed": 1,
"updated": 1,
"errors": []
}