Остатки склада в свою систему
Зачем
Своя система принятия решений — ценообразование, планирование закупки,
внутренний дашборд — должна знать, сколько товара реально лежит на складе
ПерфектПак, без ручной сверки раз в неделю. GET /v1/stock отдаёт
ровно то число, которым живёт склад: физический остаток по сканам каждой
единицы, а не отражение из чужой системы.
Полная синхронизация
У типового селлера 11–106 SKU — обычно одна страница. На старте интеграции или для ежедневной сверки прогоните весь список без фильтра по времени:
curl "https://api.perfektpak.ru/v1/stock?limit=1000" \
-H "Authorization: Bearer $PP_KEY"
{
"items": [
{ "barcode": "4680012340015", "article": "COFFEE-1KG-BRIZ", "name": "Кофе в зёрнах «Утренний Бриз» 1 кг",
"quantity": 560, "available": 512, "in_question": 48, "reserved": 120, "boxes": 14, "pallets": 1,
"marketplace_stock": [
{ "marketplace": "WB", "quantity": 430, "warehouse_id": 1234567, "warehouse_name": "Коледино", "synced_at": "2026-08-29T14:47:00+03:00" }
],
"updated_at": "2026-08-29T13:05:00+03:00" }
],
"total": 37,
"next_cursor": null
}
Если next_cursor не null — заберите следующую
страницу тем же курсором (см. «Пагинация курсором» на странице
«Быстрый старт»). Для одной страницы
сравнивайте ответ по ETag/If-None-Match — если
ничего не изменилось, вернётся 304 без тела и без лишнего трафика.
Инкрементальная синхронизация
GET /v1/stock не принимает фильтр по времени — список короткий,
и вытянуть его целиком дешевле, чем поддерживать отдельный протокол
дельта-синхронизации. На практике это означает: опрашивайте эндпоинт целиком
с разумным интервалом (раз в одну–пять минут — укладывается в лимит 60
запросов в минуту с большим запасом) и в своей системе сравнивайте
updated_at каждой позиции с тем, что сохранили в прошлый раз —
обновляйте только те SKU, где метка времени сдвинулась.
known = load_last_seen() # { barcode: updated_at }
page = requests.get(
"https://api.perfektpak.ru/v1/stock", params={"limit": 1000},
headers={"Authorization": f"Bearer {PP_KEY}"},
).json()
changed = [
item for item in page["items"]
if known.get(item["barcode"]) != item["updated_at"]
]
apply_changes(changed)
save_last_seen({item["barcode"]: item["updated_at"] for item in page["items"]})
Если ваш каталог большой и вам важна доставка изменений без опроса —
подпишитесь на события приёмки и отгрузки (receipt.completed,
shipment.status_changed) на странице «Вебхуки»
и по ним точечно перечитывайте затронутые SKU через GET /v1/stock?barcode=,
вместо того чтобы держать вебхук на сам остаток — отдельного события на
изменение остатка v1.1 не вводит.
Два числа и почему они расходятся
Каждый SKU несёт два независимых числа, и синхронизировать нужно оба — осознанно, а не сводя их в одно:
| Поле | Что это | Метка времени |
|---|---|---|
quantity / available | Физический остаток у нас, по сканам каждой единицы. available = quantity − in_question — именно его закладывайте в продажу. | updated_at — момент последнего скана. |
marketplace_stock[].quantity | Остаток, который отдаёт API самого маркетплейса по этому SKU. Мы его только зеркалим — не проверяем и не пересчитываем. | synced_at — когда мы последний раз опросили маркетплейс. |
Это не «наша цифра точнее» и не «их цифра устарела» — это два независимых источника, которые по построению не обязаны совпадать. Практические причины расхождения обычно одни и те же:
- задержка синхронизации кабинета — смотрите
synced_atрядом с числом; - несколько складов маркетплейса под одним SKU, а
marketplace_stock— массив по каждому складу отдельно; - товар уже отгружен у нас, а карточка маркетплейса ещё не обновилась.
Если расхождение большое и не объясняется задержкой синхронизации — это повод написать менеджеру, а не признак ошибки на одной из сторон.
Что дальше
Если синхронизация нужна не только для чтения, а вы уже заводите приёмку
из своей системы — см. «Заявка на приёмку из 1С».
Полная схема полей StockItem — в справочнике методов.