ПерфектПак · API v1.1
openapi.yaml ↗

Остатки склада в свою систему

Зачем

Своя система принятия решений — ценообразование, планирование закупки, внутренний дашборд — должна знать, сколько товара реально лежит на складе ПерфектПак, без ручной сверки раз в неделю. 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 — когда мы последний раз опросили маркетплейс.

Это не «наша цифра точнее» и не «их цифра устарела» — это два независимых источника, которые по построению не обязаны совпадать. Практические причины расхождения обычно одни и те же:

Если расхождение большое и не объясняется задержкой синхронизации — это повод написать менеджеру, а не признак ошибки на одной из сторон.

Что дальше

Если синхронизация нужна не только для чтения, а вы уже заводите приёмку из своей системы — см. «Заявка на приёмку из 1С». Полная схема полей StockItem — в справочнике методов.