HTTP API склада Sklad F5

REST API для товаров, остатков, резервов в сделках amoCRM, возвратов, аналитики и webhook-интеграций. Документация рассчитана на подключение сторонних сервисов (Make, n8n, МойСклад, собственные бэкенды).

Base URL настраивается ниже JSON · UTF-8 Без API-ключа в текущей версии CORS: amoCRM / metch21.ru

Быстрый старт

Минимальный сценарий: проверить сервер → получить товары → зарезервировать в сделку.

BASE="https://sklad.metch21.ru"

# 1) Health
curl -s "$BASE/health"

# 2) Список товаров
curl -s "$BASE/api/products" | jq '.items[0]'

# 3) Резерв в сделку amoCRM (dealId = ID сделки)
curl -s -X POST "$BASE/api/deals/12345678/products" \
  -H "Content-Type: application/json" \
  -d '{"product_id":"UUID-ТОВАРА","quantity":2,"price":1500}'

Соглашения

Запросы

  • Тело: Content-Type: application/json
  • Лимит тела: до 8 MB
  • Даты в ответах — ISO timestamps из PostgreSQL
  • ID товаров — UUID; ID сделок — числовой amo_deal_id

Безопасность (важно)

В текущей версии HTTP API не требует API-ключа. Доступ контролируется сетью/CORS и знанием Base URL. Для публичного интернета рекомендуем ограничить доступ на уровне firewall / reverse proxy / VPN.

Лимиты

ОперацияЛимит
Импорт товаровдо 5000 строк за запрос
Массовое удалениедо 500 id
Движения складапоследние 200
Активные заказыlimit 1–500 (по умолчанию 100)

Ключевые понятия

Остаток ≠ резерв

quantity — физический остаток на складе. reserved_quantity — сколько зарезервировано в сделках со статусом pending. available_quantity = quantity − reserved_quantity.

Резерв в сделке не уменьшает физический остаток. Физическое списание происходит при отгрузке (shipment) или списании (write_off).

Статусы позиций сделки

statusСмысл
pendingЗарезервировано
shippedОтгружено (остаток уменьшен)
written_offСписано (брак/убыль)
returnedВозврат
cancelledОтменено

Объект Product

{
  "id": "4cdf80d1-7d68-49a9-a90c-73a4923ad0ea",
  "name": "Саморез 4.2×16",
  "sku": "SR-4216",
  "category": "Крепеж",
  "unit": "шт.",
  "quantity": 450,
  "reserved_quantity": 40,
  "available_quantity": 410,
  "min_quantity": 50,
  "purchase_price": 1.2,
  "sale_price": 3.5,
  "price": 3.5,
  "stock_value": 1575,
  "status": "ok",
  "comment": null,
  "created_at": "...",
  "updated_at": "..."
}

Ошибки

Единый формат: { "error": { "message": "..." } }. Для коллизии резерва добавляются поля.

Коллизия бронирования — 409 INSUFFICIENT_STOCK

{
  "error": {
    "message": "Нельзя зарезервировать больше свободного остатка...",
    "code": "INSUFFICIENT_STOCK",
    "product_id": "uuid",
    "product_name": "Кабель ВВГ 3×2.5",
    "requested": 20,
    "available": 10,
    "free_stock": 10,
    "stock": 50,
    "reserved": 40,
    "reserved_others": 40,
    "unit": "шт."
  }
}

Рекомендация интеграции: при code === "INSUFFICIENT_STOCK" предложить пользователю количество available и повторить запрос.

HTTPКогда
400Невалидные параметры
404Товар / позиция не найдены
409Конфликт остатка / резерва / бизнес-правило
500Внутренняя ошибка сервера

Health

GET/health

Проверка живости сервиса и соединения с PostgreSQL.

curl -s "$BASE/health"
# { "ok": true, "service": "sklad-f5-backend" }

Warehouses — склады

Мультисклад: один каталог товаров, остатки отдельно на каждом складе. Если warehouse_id не передан, API подставляет склад по умолчанию (Основной).

В ответах товаров есть stocks[] (разбивка по складам) и суммарный quantity. В позициях сделки и движениях — warehouse_id, warehouse_name, warehouse_code.
GET/api/warehouses
QueryОписание
include_inactive=1optВключить отключённые склады
curl -s "$BASE/api/warehouses"
# { items: [{ id, name, code, is_default, is_active, sort_order }] }
POST/api/warehouses
curl -s -X POST "$BASE/api/warehouses" \
  -H "Content-Type: application/json" \
  -d '{"name":"Склад СПб","code":"spb"}'
PUT/api/warehouses/:id

Переименовать, сменить code, is_active, назначить is_default: true.

DELETE/api/warehouses/:id

Только если нет ненулевых остатков и pending-резервов. Иначе 409. Нельзя удалить единственный / основной без замены.

Products — товары

Каталог склада. При создании/изменении товар может синхронизироваться в каталог amoCRM.

GET/api/products
QueryОписание
searchoptПоиск по названию / артикулу
statusoptall | ok | low | out
categoryoptall или точное имя категории
warehouse_idoptОстатки в разрезе склада; без параметра — сумма + stocks[]
curl -s "$BASE/api/products?search=кабель&status=ok&warehouse_id=UUID"
GET/api/products/:id

Один товар. 404 если не найден.

POST/api/products
BodyОписание
namereqНазвание
skuoptАртикул
category, unit, description, commentoptМетаданные; unit по умолчанию «шт.»
quantity, min_quantity, warehouse_idoptОстаток на складе (по умолчанию — основной)
purchase_price, sale_price / priceoptЦены
curl -s -X POST "$BASE/api/products" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Кабель ВВГ 3×2.5",
    "sku": "KB-001",
    "category": "Электрика",
    "unit": "м",
    "quantity": 100,
    "min_quantity": 20,
    "purchase_price": 45,
    "sale_price": 69
  }'
PUT/api/products/:id

Обновление. Нельзя поставить quantity ниже текущего резерва — ответ 409.

DELETE/api/products/:id

Удаляет товар, связанные движения и позиции сделок; пытается убрать из каталога amoCRM.

POST/api/products/bulk-delete
curl -s -X POST "$BASE/api/products/bulk-delete" \
  -H "Content-Type: application/json" \
  -d '{"ids":["uuid-1","uuid-2"]}'
POST/api/products/import

Массовый импорт. До 5000 позиций. При update_quantity: true количество прибавляется к существующему артикулу.

curl -s -X POST "$BASE/api/products/import" \
  -H "Content-Type: application/json" \
  -d '{
    "update_quantity": true,
    "items": [
      {"name":"Саморез","sku":"SR-1","quantity":100,"sale_price":3.5}
    ]
  }'
POST/api/products/sync-catalog

Выгрузить весь каталог в список товаров amoCRM.

GET/api/products/import/templates
POST/api/products/import/templates
DELETE/api/products/import/templates/:templateId

Шаблоны сопоставления колонок для Excel/CSV-импорта в UI.

Stock — остатки и движения

Операции меняют физический остаток и пишут запись в журнал. При включённых исходящих webhook — отправляют события наружу.

Общие поля тела: productId (обяз.), quantity или targetQuantity, warehouseId / warehouse_id (опц., иначе склад по умолчанию), опционально comment, amoDealId, dealName, userId, unitPrice, purchasePrice, accountId.
POST/api/stock/incoming

Поступление (+quantity). 201 → { product, movement }.

curl -s -X POST "$BASE/api/stock/incoming" \
  -H "Content-Type: application/json" \
  -d '{"productId":"UUID","warehouseId":"WAREHOUSE_UUID","quantity":50,"comment":"Приход от поставщика"}'
POST/api/stock/write-off

Списание (−quantity). 409 если не хватает физического остатка.

POST/api/stock/shipment

Отгрузка (−quantity). Аналогично write-off, тип движения shipment.

POST/api/stock/adjustment

Инвентаризация: установить абсолютный targetQuantity.

curl -s -X POST "$BASE/api/stock/adjustment" \
  -H "Content-Type: application/json" \
  -d '{"productId":"UUID","warehouseId":"WAREHOUSE_UUID","targetQuantity":120,"comment":"Инвентаризация"}'
GET/api/stock/movements

Последние 200 движений. Query: warehouse_id — фильтр по складу. В ответе есть поля склада.

Deals — товары в сделках

:dealId — ID сделки (лида) в amoCRM.

GET/api/deals/:dealId/products
curl -s "$BASE/api/deals/12345678/products"
# { items, total_amount, positions, total_quantity }
POST/api/deals/:dealId/products
BodyОписание
product_idreqUUID товара
quantityreq> 0
priceoptЦена в сделке
warehouse_idopt*Склад резерва; без параметра — склад по умолчанию

Создаёт/обновляет резерв pending на выбранном складе. Один товар можно резервировать с разных складов отдельными строками. При нехватке — 409 INSUFFICIENT_STOCK.

POST/api/deals/:dealId/products/batch

Пакетное добавление. Транзакция: при ошибке одной позиции откатывается весь пакет.

curl -s -X POST "$BASE/api/deals/12345678/products/batch" \
  -H "Content-Type: application/json" \
  -d '{
    "warehouse_id": "WAREHOUSE_UUID",
    "items": [
      {"product_id":"uuid-1","quantity":2,"price":1500,"warehouse_id":"WAREHOUSE_UUID"},
      {"product_id":"uuid-2","quantity":1,"price":990,"warehouse_id":"WAREHOUSE_UUID"}
    ]
  }'
PUT/api/deals/:dealId/products/:id

Изменить количество/цену/статус позиции. Для pending снова проверяется свободный остаток.

curl -s -X PUT "$BASE/api/deals/12345678/products/POSITION_UUID" \
  -H "Content-Type: application/json" \
  -d '{"quantity":3,"price":1400,"status":"pending"}'
DELETE/api/deals/:dealId/products/:id

Снять позицию (резерв). Ответ 204.

POST/api/deals/:dealId/return

Возврат. Для shipped увеличивает остаток; для pending снимает резерв. Может создать примечание/задачу/смену этапа по настройкам.

curl -s -X POST "$BASE/api/deals/12345678/return" \
  -H "Content-Type: application/json" \
  -d '{
    "deal_name": "Заказ Иванова",
    "items": [{"deal_product_id":"POSITION_UUID","quantity":1}]
  }'

Orders — заказы

GET/api/deals/orders/active
QueryОписание
scopeoptcurrent (есть pending) или completed
limitopt1–500, по умолчанию 100
warehouse_idoptТолько заказы с позициями этого склада
curl -s "$BASE/api/deals/orders/active?scope=current&limit=50&warehouse_id=UUID"

В заказе: имя сделки, контакт, компания, телефон/ИНН (если синхронизированы), позиции с остатками.

Settings — настройки

GET/api/settings?accountId=...
PUT/api/settings

Хранит правила этапов, права, return_rules и widget_settings.webhook.endpoints[] (несколько исходящих URL со своими events/fields).

curl -s -X PUT "$BASE/api/settings" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": 123,
    "write_off_rules": [
      {"pipelineId":"111","statusId":"222","action":"shipment"}
    ],
    "widget_settings": {
      "webhook": {
        "enabled": true,
        "endpoints": [{
          "id": "ep1",
          "name": "Make",
          "url": "https://hook.example/in/xxx",
          "enabled": true,
          "events": ["write_off","shipment","low_stock"],
          "fields": ["product","quantity","deal","stock"]
        }]
      }
    }
  }'

Analytics

GET/api/analytics
QueryОписание
periodtoday | week | month | quarter | custom
date_from, date_toДля custom
refresh=1 + accountIdПодтянуть изменения сделок из amoCRM
warehouse_idАналитика по одному складу
curl -s "$BASE/api/analytics?period=month&warehouse_id=UUID"

amoCRM proxy

Прокси к amoCRM с серверным токеном. Для UI склада / интеграций без собственного OAuth.

GET/api/amocrm/account
GET/api/amocrm/pipelines
GET/api/amocrm/users

Install

POST/api/install

Установка: создать каталог товаров в amoCRM, поля, сиды. Используется виджетом при сохранении настроек.

GET/api/install/events

Последние 50 событий установки.

Входящий webhook amoCRM

POST/api/webhooks/amocrm

URL для настройки в amoCRM Digital Pipeline / Webhooks. При смене этапа сделки применяет правила склада (отгрузка/списание pending-позиций).

# Укажите в amoCRM:
$BASE/api/webhooks/amocrm

# Ответ:
# { "ok": true, "processed": 1, "results": [ ... ] }

Исходящие webhook (склад → ваш сервис)

Настраиваются в UI «Исходящий Webhook»: у каждого адреса свои события и поля. Склад шлёт POST JSON, не дожидаясь ответа (таймаут ~8 сек).

События

incoming, write_off, shipment, adjustment, а также производные low_stock / out_of_stock после изменения остатка.

Пример payload

{
  "event": "write_off",
  "timestamp": "2026-08-17T10:15:00.000Z",
  "endpoint": { "id": "ep1", "name": "Make" },
  "product": {
    "id": "uuid",
    "name": "Кабель ВВГ 3×2.5",
    "sku": "KB-001",
    "category": "Электрика",
    "unit": "м"
  },
  "quantity": { "delta": -5, "absolute": 5, "before": 100, "after": 95 },
  "prices": {
    "purchase_price": 45,
    "sale_price": 69,
    "unit_price": 69,
    "total_amount": 345
  },
  "deal": { "id": 12345678, "name": "Заказ Иванова" },
  "manager": { "user_id": null },
  "stock": { "quantity": 95, "min_quantity": 20, "status": "ok" },
  "warehouse": { "id": "uuid", "name": "Основной склад", "code": "main" }
}
В payload движения всегда добавляется объект warehouse (id, name, code).

Типовые сценарии

1. Make / n8n: слушать списания

  1. Создайте входящий webhook в Make/n8n и скопируйте URL.
  2. В складе → Настройки → Исходящий Webhook → добавьте адрес.
  3. Включите события write_off / shipment и нужные поля.
  4. Сохраните настройки и сделайте тестовое списание.

2. Внешняя система резервирует товар в сделку

# Найти товар
curl -s "$BASE/api/products?search=ВВГ" | jq '.items[] | {id,name,available_quantity}'

# Зарезервировать
curl -s -X POST "$BASE/api/deals/$DEAL_ID/products" \
  -H "Content-Type: application/json" \
  -d '{"product_id":"UUID","quantity":10,"warehouse_id":"WAREHOUSE_UUID"}'

# Если 409 — взять error.available и повторить

3. Синхронизация остатков наружу

Периодически вызывайте GET /api/products или слушайте исходящие webhook с полем stock.

Не храните Base URL в публичных клиентских приложениях без ограничений доступа. API сейчас открыт для вызовов, которые доходят до сервера.