ЛОТУС

Зачем ЛОТУСКак начать

Документация

Справочник API интеграции

Контракт обмена: адреса, аутентификация, форматы данных, методы и коды ответов. Действующая редакция — обновляется вместе с платформой.

Содержание

  1. 1. О документе
  2. 2. Схема интеграции
  3. 3. Общие правила запросов
  4. 4. Типы и форматы данных
  5. 5. Два идентификатора: заказ и позиция
  6. 6. Статусы заказа
  7. 7. Цикл обмена
  8. 8. Объекты
  9. 9. Методы
  10. 10. Справочник категорий
  11. 11. Коды ошибок и частые ошибки
  12. 12. Тестовый контур и журнал обмена
  13. 13. Чек-лист готовности к боевому запуску

Контракт обмена между учётной системой поставщика (1С или любой другой) и платформой Lotus Market.

Эта страница — действующая редакция: она обновляется вместе с платформой. Как устроено внедрение и с чего начать — в гайде внедрения.


1. О документе

Здесь описано только то, что нужно для обмена: адреса, аутентификация, форматы данных, методы, коды ответов.

Задача Разделы
Что изменится в кабинете поставщика §2.1
Что проверить до начала работ §2.2
Общая картина §2, §9.0 (сводка методов)
Разработка §3 (запросы) → §4 (форматы) → §7 (цикл) → §9 (методы)
Идентификаторы заказа и позиции §5
Приёмка §13

2. Схема интеграции

┌────────────────────┐         исходящие HTTPS-запросы          ┌──────────────────┐
│  ВАША СИСТЕМА (1С) │ ─────────────────────────────────────►   │   LOTUS MARKET   │
│      = клиент      │   ◄─────── только ответы на них ──────   │     = сервер     │
└────────────────────┘                                          └──────────────────┘
  1. Запросы всегда идут от вас к нам. Платформа не подключается к вашей сети; открытых портов и публикации базы не требуется.
  2. Опросом забираются только заказы — это единственное событие, происходящее на нашей стороне. Остальное (готовность, недостача, остатки, поставки) вы отправляете сами, когда оно у вас произошло.
  3. Направление заказов одностороннее: платформа → вы. Ваши продажи мимо платформы не передаются — от них к нам приходит только новый остаток.

2.1. Что меняется у поставщика при живом ключе

Эти правила наступают от самого факта подключённой учётной системы, независимо от того, какие методы вы вызываете. Их стоит проговорить с владельцем кабинета заранее.

  • Загрузка прайса из Excel закрывается (409): у остатка должен быть один источник.
  • Количество партии с вашим кодом больше не правится в кабинете (409): его ведёт обмен. Цена и размер банча остаются доступными; отзыв ключа снова открывает поле.
  • Одновременно работает одна учётная система: подключить 1С и МойСклад разом нельзя (409).
  • Расхождение остатка платформа фиксирует, но не разрешает сама: если товар продан и на платформе, и вне её, витрина по позиции закрывается, а в кабинете появляется список затронутых заказов — решение (недостача, замена) принимает человек.

2.2. Требования к среде

Требование Пояснение
1С:Предприятие 8.3, конфигурация любая Включая самописную
Поддержка TLS 1.2 в сборке платформы В ранних сборках 8.3 её нет, обмен не заработает. Проверить до начала работ
Исходящий HTTPS (порт 443) к нашему домену Входящих подключений с нашей стороны нет
Две фоновые задачи Опрос заказов и отправка ваших событий (§7)
Синхронное системное время (NTP) Для сопоставимости журналов. На обмен не влияет: since берётся из нашего ответа (§7.2)

3. Общие правила запросов

3.1. Адреса

Контур Базовый адрес
Тестовый https://stg.lotusmarket.ru/api/v1
Боевой https://lotusmarket.ru/api/v1

Пути в документе указаны относительно базового адреса. Только HTTPS, TLS 1.2+.

3.2. Аутентификация — заголовок X-Api-Key

X-Api-Key: lm_a1b2c3d4e5f6g7h8...
  • Ключ выпускает владелец кабинета (сотруднику это недоступно); для тестового контура — отдельный ключ, ключ одного контура на другом не работает.
  • Показывается один раз при выпуске, дальше только выпуск нового.
  • Бессрочный, действует до отзыва. Отзыв действует немедленно и необратим.
  • Активных ключей у компании — до 10. Несколько ключей штатны: так делается ротация без простоя (выпустить новый → прописать → отозвать старый).
  • Права выпущенного ключа меняются в кабинете без перевыпуска и действуют со следующего запроса.
  • 401 — ключ отсутствует, неверен или отозван (все три случая отвечают одинаково): остановить обмен, уведомить администратора.

Права. У ключа есть матрица прав по разделам и отдельные галочки действий; уровень раздела и галочка проверяются независимо. По умолчанию: «Заказы» — просмотр, «Товары и партии» — полный, «Поставки» — полный; действия «Выдача заказов», «Отклонение и недостачи», «Внесение товара», «Изменение цен», «Списание остатков» включены.

Метод Требуемое право
§9.1 проверка изменений, §9.2 лента Заказы: просмотр
§9.3 ready, §9.13 unready Заказы + «Выдача заказов»
§9.4 shortfall, §9.5 restore, §9.6 remove, §9.7 reject Заказы + «Отклонение и недостачи»
§9.8 остатки и цены Товары и партии: полный
§9.9–9.12 поставки Поставки: полный + «Внесение товара»
§9.14 контрагенты Заказы: полный — по умолчанию стоит просмотр, поэтому из коробки метод отвечает 403

Кроме прав, все изменяющие методы требуют подтверждённого профиля владельца (почта, ИНН, банк) — иначе 403 PROFILE_INCOMPLETE со списком недостающих полей. Чтения (§9.1, §9.2) этого не требуют. Того же условия требует и сам выпуск ключа.

Обращение по адресу, которого нет в списке доступных ключу, отвечает 403 PERMISSION_DENIED с сообщением, что метод недоступен ключу интеграции: права здесь ни при чём и расширение матрицы не поможет.

Ключ не является пользователем: у него нет почты и пароля, он не участвует в двухфакторной аутентификации и не подписывает юридические документы — публикация новой редакции документов обмен не останавливает.

3.3. Формат запросов и ответов

GET — только заголовки:

GET /api/v1/supplier/integration/changes?since=2026-08-04T06:00:00Z HTTP/1.1
Host: stg.lotusmarket.ru
X-Api-Key: lm_a1b2c3d4...

POST — плюс Content-Type и Idempotency-Key, тело JSON (UTF-8):

POST /api/v1/supplier/orders/018f3b2a-8d2f-7c3b-a011-2b3c4d5e6f70/shortfall HTTP/1.1
Host: stg.lotusmarket.ru
X-Api-Key: lm_a1b2c3d4...
Content-Type: application/json
Idempotency-Key: 7f0d2c9e-1a2b-4c3d-8e9f-0a1b2c3d4e5f

{ "quantity": 50, "reason": "Пересорт на складе" }

Строгость к телу у методов разная: §9.4 и §9.5 требуют разбираемый JSON (пустое или испорченное тело — 400); §9.6 допускает отсутствие тела, но испорченное отвергает; §9.3 тело не читает вовсе; §9.13 читает мягко — тело можно не передавать.

Успешный ответ — в обёртке data (+ meta у списков), ошибка — объект error:

{ "data": { ... } }
{ "data": [ ... ], "meta": { "cursor": "...", "hasMore": true } }
{ "error": { "code": "VALIDATION_ERROR", "message": "Количество должно быть кратно банчу" } }

3.4. HTTP-коды

Код Что случилось Действие клиента
200 Успех Обработать data
201 Объект создан (создание поставки) Обработать data
304 Данные не менялись (ответ на If-None-Match, §9.1) Использовать сохранённый ответ
400 Некорректный запрос: битый JSON, некратное количество, неверный формат Не повторять; в журнал, оператору
401 Ключ отсутствует, неверен или отозван Остановить обмен, уведомить администратора
403 Не хватает прав ключа, метод недоступен ключу, не заполнен профиль, аккаунт удалён либо приостановлено размещение нового (§3.8) Не повторять; администратору
404 Объект не найден или в нём нет ваших позиций Не повторять; в журнал
409 Конфликт состояния (см. описание метода) Обработать по описанию метода. Исключение: «запрос с этим Idempotency-Key ещё выполняется» — повторить тем же ключом через паузу (§3.5)
429 Превышена частота Пауза Retry-After секунд, затем повтор
500, 503 Ошибка на стороне платформы Повтор с нарастающей задержкой: 1 → 5 → 15 мин

3.5. Идемпотентность — заголовок Idempotency-Key

Обязателен в каждом POST; без него — 400. Длина до 200 символов. Изменение поставки (PUT, §9.10) принимается и без него: там от дублей защищает номер вашего документа.

  1. На каждую новую операцию — новый ключ (случайный UUID), генерирует ваша сторона.
  2. Повтор после сбоя — с тем же ключом: если первая попытка дошла, действие не выполнится второй раз, вернётся сохранённый ответ (с исходным кодом, включая 201).
  3. Повтор с тем же ключом, но другим телом → 409. Слепок считается по методу, пути и телу.
  4. 409 «запрос с этим ключом ещё выполняется» означает, что первая попытка не завершилась. Это единственный 409, который нужно повторить — тем же ключом, через паузу. Зависший резерв освобождается через 5 минут.
  5. Частично неуспешный ответ тоже сохраняется. Пакет, вернувший accepted: 48, rejected: 2, при повторе с тем же ключом вернёт тот же ответ и ничего не применит: исправленные строки отправляйте с новым ключом.

Ключи хранятся на платформе 7 суток и привязаны к конкретному ключу доступа: после его ротации прежние ключи идемпотентности начинают новые операции.

3.6. Частота запросов

Ограничений три:

Ограничение На что считается
20 запросов в минуту Проверка изменений (§9.1), по ключу доступа
300 запросов в минуту Весь машинный обмен, по ключу доступа
120 запросов в минуту Любые запросы с заголовком X-Api-Key с одного IP-адреса, включая неудачные попытки аутентификации

Превышение — 429 с заголовком Retry-After: выдержать паузу и повторить.

Интервал опроса задаёт сервер — поле nextPollAfterSeconds в ответе и заголовок X-Next-Poll-After (заголовок нужен потому, что у 304 тела нет). Значение меняется само: от 30 секунд при активности до 900 ночью; платформа может разредить опрос глобально. Читайте его из каждого ответа, не зашивайте в код.

3.7. Пагинация

Списки отдаются страницами; в meta приходит курсор:

адрес = /supplier/integration/orders?since=<время>&limit=50
цикл:
    ответ = GET адрес
    обработать ответ.data
    если ответ.meta.hasMore = false → конец
    адрес = /supplier/integration/orders?cursor=<ответ.meta.cursor>&limit=50

3.8. Ограничение размещения за неоплаченный счёт

Комиссия платформы в ценах API не фигурирует — платформа выставляет на неё отдельный счёт вне API (§4.6). Если счёт не оплачен в срок, оператор вправе приостановить поставщику размещение нового: товары скрываются с витрины покупателей, а выгрузка новых позиций отклоняется. Ограничение снимается платформой после оплаты.

Отвечают 403 с кодом BILLING_RESTRICTED:

Метод Когда
§9.8 Остатки и цены Всегда — весь пакет целиком, построчного results в этом случае нет
§9.9 Создание поставки Всегда
§9.10 Изменение поставки Только если документ размещает новое: добавляет позицию, увеличивает количество существующей или меняет publishAt у поставки, которую покупатели ещё не видели (черновик либо запланированная к показу). Повтор того же момента показа отказа не вызывает

Продолжает работать всё остальное, в том числе:

  • лента заказов и все действия по ним — §9.1–9.7, §9.13;
  • приход и отмена поставки — §9.11, §9.12;
  • §9.10, если документ только убирает позиции, уменьшает количество или меняет цены. Наведение порядка в поставке ограничением не закрыто.

Что делать клиенту. Запрос не повторять (ретраи не помогут — состояние меняется на стороне платформы), событие записать в журнал, уведомить администратора: вопрос решается оплатой счёта, а не в обработке.

⚠️ После снятия ограничения выгрузите полную сверку прайса (§9.8). Строки, отклонённые за время ограничения, по общему правилу §3.4 не повторяются и в очереди не сохраняются — без полной сверки товары вернутся на витрину с остатками, устаревшими на весь период ограничения.


4. Типы и форматы данных

Нарушение любого правила — ответ 400.

4.1. Идентификаторы

UUID в текстовом виде: 018f3b2a-7c1e-7b2a-9f00-1a2b3c4d5e6f. Регистр не значим.

4.2. Дата и время

RFC 3339, всегда UTC с суффиксом Z:

Правильно Неправильно
2026-08-04T09:14:02Z 04.08.2026 12:14:02 · 2026-08-04T12:14:02 (без зоны)

Дата без времени (ожидаемый приход поставки): 2026-08-15. Такая дата понимается как конец указанного дня по московскому времени: документ, выписанный на сегодня, принимается, а опубликованная поставка закрывается в конце этого дня (§9.11).

4.3. Деньги

Целое число копеек, всегда за одну штуку (стебель):

В жизни В API Типичная ошибка
45 ₽ 4500 45
45 ₽ 50 коп 4550 45.5, "45,50"
1 200 ₽ 120000 1200

4.4. Количества

Целое число штук (не банчей, не коробок).

Кратность банчу (stemsPerBundle) обязательна в недостаче (§9.4) и восстановлении (§9.5): выдать покупателю часть связки платформа не может.

Банч Допустимо Недопустимо
25 0, 25, 50, … 250 … 10, 240, 251
10 0, 10, 20, … 5, 15, 99

Остатки (§8.3) кратности не требуют — передавайте фактическое число, округлять не нужно: на витрине оно так и встанет, а кратность связке обеспечивает корзина покупателя. Позиции поставки (§8.4) кратности тоже не требуют, но там количество округляется вниз до целых банчей при заведении — фактически применённое значение приходит в ответе §9.9.

4.5. externalCode — ваш код номенклатуры

Строка до 120 символов, ваш внутренний код или артикул.

  • Стабильность: код позиции не меняется со временем. Изменившийся код платформа считает новой позицией и заводит рядом вторую — на витрине товар раздвоится. Карточку товара при этом она, как правило, переиспользует: та ищется по совпадению названия, категории, страны, длины стебля и описания.
  • Уникальность в пределах компании: один код = одна позиция.

То же для складов (warehouseCode) и поставок (externalCode = номер вашего документа).

4.6. Цены: передаёте свою — в заказах приходит цена покупателя

Направление Какая цена Пример
Вы → платформа (остатки §9.8, поставки §9.9) Ваша цена продажи за штуку 10000 (100 ₽)
Платформа → вы (заказы §9.2) Полная цена покупателя: ваша цена за вычетом скидки покупателя, плюс комиссия платформы 10300 (103 ₽)

Цена заказа — та, которую фактически платит покупатель: реализацию и УПД проводите по ценам заказа, а не по прайсу. Порядок арифметики: скидка вычитается из вашей цены, и уже к уменьшенной прибавляется комиссия. Скидка не обязательно персональная — платформа применяет наибольшую из подходящих покупателю (персональная, за объём заказа, за первый заказ).

Комиссия отдельным полем в API не фигурирует. По заказам, оплаченным через платёжный сервис платформы, она удерживается из перечисляемой вам суммы (§8.1, paidOutAmount); по остальным приходит отдельным счётом вне API. В обоих случаях сумма реализации и сумма поступления различаются на величину комиссии — это штатное расхождение, а не ошибка обмена.

4.7. Остаток — физический

Передаётся фактический остаток на складе, без вычета броней покупателей платформы — их вычитает платформа. Двойное вычитание занижает витрину.


5. Два идентификатора: заказ и позиция

Заказ и каждая его строка имеют собственные UUID:

{
  "id": "018f3b2a-7c1e-7b2a-9f00-1a2b3c4d5e6f",        ← идентификатор ЗАКАЗА (orderId)
  "number": "00012345-0001",
  "items": [
    { "itemId": "018f3b2a-8d2f-7c3b-a011-2b3c4d5e6f70", ← идентификатор ПОЗИЦИИ №1 (itemId)
      "title": "Роза Freedom 70 см", "quantity": 250 },
    { "itemId": "018f3b2a-9e30-7d4c-b122-3c4d5e6f7081", ← идентификатор ПОЗИЦИИ №2 (itemId)
      "title": "Тюльпан Strong Gold 40 см", "quantity": 100 }
  ]
}
Действие Метод В адресе
Заказ собран целиком ready orderId
Откат готовности unready orderId
Отклонить заказ целиком reject orderId
Недостача по строке shortfall itemId
Вернуть выдачу по строке restore itemId
Убрать строку remove itemId

Путь начинается с /supplier/orders/… в обеих группах — ориентируйтесь на имя параметра в описании метода ({orderId} / {itemId}), а не на слово orders в пути.

Следствие для 1С: id заказа хранится в шапке документа, itemId каждой строки — в строке табличной части.


6. Статусы заказа

6.1. Жизненный цикл

pre_order ──► confirmed ──► ready ──► received ──► completed
(предзаказ)   (ждёт сборки)  (ждёт     (покупатель   (завершён)
                             покупателя) забрал)

Терминальные ветки: cancelled (отменён покупателем или платформой)
                    rejected  (отклонён вами)

Статус есть у заказа и у каждой позиции отдельно; статус заказа — агрегат по позициям.

6.2. Значения

Статус Что это
pre_order Предзаказ на товар будущей поставки. После прихода поставки (§9.11) сам станет confirmed
confirmed Заказ ждёт сборки
ready Собран, покупатель уведомлён
received Покупатель забрал товар (скан кода на складе)
completed Завершён, окно рекламаций истекло
cancelled Заказ не состоится: отменён покупателем, платформой либо аннулирован вместе с отменённой поставкой (§9.12) — в том числе вашей же, отменённой машинно
rejected Отклонён вами

Заказ, ожидающий оплаты через платёжный сервис платформы, в ленту не попадает вовсе (§9.2), поэтому такого статуса вы не увидите: заказ появится у вас уже оплаченным либо не появится никогда.

6.3. Какое событие — какой вызов

Событие в вашей системе Вызов API
Заказ собран и отложен для покупателя ready (§9.3)
Собрано меньше, чем в заказе shortfall по недоданным строкам (§9.4)
Документ сборки распроведён или удалён unready (§9.13)
Документ заказа удалён у вас reject (§9.7)
Заказ пришёл в ленте как received Вызова нет
Оплата от покупателя напрямую Вызова нет — платформа о прямых расчётах не знает

Два правила:

ready означает «собрали», а не «выдали». Статус received ставит только покупатель, сканом кода на складе — от этого момента идут сроки рекламаций и расчёты. Метода «отметить выданным» в API нет. Когда именно вы проводите реализацию — при отпуске со склада или по факту передачи покупателю — ваш регламент, платформа его не диктует.

Ваши собственные заказы на платформу не передаются. Продали в офлайне — передайте новый остаток (§9.8).


7. Цикл обмена

7.1. Две задачи

Задача А (по расписанию):

1. GET /supplier/integration/changes?since=<serverTime из прошлого ответа>
2. hasChanges = false → спать nextPollAfterSeconds, к шагу 1
3. hasChanges = true  → GET /supplier/integration/orders (все страницы, §3.7)
4. по каждому заказу: создать документ / обновить / закрыть терминальный
5. сохранить serverTime как since следующего опроса
6. спать nextPollAfterSeconds, к шагу 1

Задача Б (отправка ваших событий):

для каждой строки очереди по порядку:
    отправить соответствующий POST
    200/201            → строка выполнена
    400/404/409        → пометить ошибкой, оператору, не повторять
    401                → остановить обмен, уведомить администратора
    5xx / обрыв        → оставить в очереди, повтор с нарастающей паузой (1 → 5 → 15 мин)

7.2. since — только из serverTime

Успешный ответ changes содержит serverTime; именно он передаётся как since следующего опроса. Собственные часы не используются: их расхождение с сервером приводит к пропуску изменений.

Два случая, когда двигать since нельзя: ответ 304 (тела нет, брать значение неоткуда) и любая ошибка. В обоих случаях сохраняется прежний момент — повторная выдача уже известных заказов безопасна, а пропуск нового заказа необратим.

Ограничения на глубину окна нет: since может отстоять сколь угодно далеко, лента просто отдаст больше страниц. «Текущее время минус сутки» на первом запуске — рекомендация, а не требование сервера.

7.3. Отправка через очередь

Требование к вашей стороне: HTTP-вызов не выполняется синхронно из проведения документа. Проведение пишет строку в локальную таблицу-очередь (событие, данные, свежий Idempotency-Key) и завершается; фоновое задание разбирает очередь по алгоритму задачи Б. Иначе недоступность платформы блокирует ваш учёт.


8. Объекты

8.1. Заказ (лента интеграции)

Поле Тип Описание
id UUID Идентификатор заказа (orderId). Хранить в шапке документа
number string Человекочитаемый номер 00012345-0001; в API-запросах не используется
status string Статус заказа (§6)
createdAt datetime Создан
updatedAt datetime Последнее изменение; по нему работает since
warehouse object Склад выдачи: code — ваш код по сопоставлению (null, если не сопоставлен), name, address
buyer object Покупатель: legalName, inn
note string | null Комментарий покупателя
totalAmount int Полная сумма заказа в ценах покупателя (§4.6), копейки
payment object Оплата: method — direct (покупатель рассчитывается с вами напрямую) или platform (через платёжный сервис платформы). При platform после перечисления денег добавляются paidOutAt и paidOutAmount (фактически перечислено вам, копейки — после комиссии и недостач)
items array Позиции (§8.2)

8.2. Позиция заказа

Поле Тип Описание
itemId UUID Идентификатор позиции (itemId). Хранить в строке табличной части
externalCode string | null Ваш код номенклатуры. null = позиция заведена в кабинете вручную и с вашим учётом не связана
title string Название сорта
quantity int Заказано, штук
fulfilledQuantity int | null Фактически к выдаче после недостачи; null = равно quantity
stemsPerBundle int Размер банча. Всегда положительное число, 0 и null не приходят — на него можно делить без проверок
pricePerUnit int Цена покупателя за штуку (§4.6), копейки
amount int Сумма строки в ценах покупателя, копейки
status string Статус позиции (§6)
supplyExternalCode string | null У pre_order-позиций: номер вашего документа поставки
supplyExpectedAt date | null У pre_order-позиций: ожидаемая дата прихода поставки. Календарная дата в московском поясе (не UTC) — та же, что вы передали в §9.9

8.3. Строка остатка (для stock, §9.8)

Поле Тип Обязательно Описание
externalCode string да Код номенклатуры (§4.5)
warehouseCode string да Код склада из сопоставления в кабинете
quantity int да Физический остаток, штук; кратность не требуется (§4.4), до 100 000. 0 = снять с витрины, отрицательное равнозначно нулю. Отсутствие поля читается как 0
pricePerUnit int да Цена за штуку, копейки; строго больше нуля, до 10 000 000 (100 000 ₽)
stemsPerBundle int да Размер банча, до 10 000. Обязателен в каждой строке, но у уже заведённой позиции игнорируется
title string при первом появлении кода Название сорта — из него создаётся карточка
category string при первом появлении кода Категория (§10)
countryOfOrigin string нет, но желательно Страна происхождения — фильтр каталога
stemLengthCm int нет, но желательно Длина стебля, см — фильтр каталога
plantation string нет Плантация

«При первом появлении кода» — платформа ещё не видела такой externalCode и создаёт карточку. Допустимо передавать title/category во всех строках.

⚠️ У существующей позиции обновляются только quantity и pricePerUnit. Остальные поля строки применяются исключительно при первичном заведении: переименовать товар, сменить категорию, банч, страну, ростовку или плантацию выгрузкой нельзя — это делается в кабинете.

8.4. Поставка (для supplies, §9.9)

Поле Тип Обязательно Описание
externalCode string да Номер вашего документа; он же защита от дублей. Уникален в пределах компании и не переиспользуется
warehouseCode string да Склад прихода
expectedAt date да Ожидаемая дата прихода — конец указанного дня (§4.2). Должна быть в будущем
publishAt datetime нет Когда показать покупателям. Не указан → черновик. Не позже конца дня прихода
items array да, ≥ 1 Позиции документа — строки того же вида, что в §8.3, но без warehouseCode: склад один на всю поставку и задан в шапке. quantity здесь — не остаток, а сколько штук вы ждёте. До 500 позиций, коды внутри документа не повторяются

Готовый пример такого документа — в §9.9.


9. Методы

9.0. Сводка

Всего 14 методов; по расписанию вызывается только §9.1. Название метода в таблице — ссылка на его раздел.

Метод Вызов Когда
9.1 Проверка изменений GET /supplier/integration/changes По расписанию
9.2 Лента заказов GET /supplier/integration/orders После hasChanges: true
9.3 Готов к выдаче POST /supplier/orders/{orderId}/ready Заказ собран
9.13 Откат готовности POST /supplier/orders/{orderId}/unready Документ сборки распроведён
9.4 Недостача по позиции POST /supplier/orders/{itemId}/shortfall Часть строки не будет выдана
9.5 Восстановление POST /supplier/orders/{itemId}/restore Недостача ошибочна
9.6 Удаление позиции POST /supplier/orders/{itemId}/remove Строка не будет выдана вовсе
9.7 Отклонение заказа POST /supplier/orders/{orderId}/reject Заказ невозможно выдать
9.8 Передача остатков и цен POST /supplier/integration/stock Изменение остатка или цены
9.9 Создание поставки POST /supplier/integration/supplies Проведён заказ поставщику
9.10 Изменение поставки PUT /supplier/integration/supplies Документ поставки изменился
9.11 Приход поставки POST /supplier/integration/supplies/arrive Проведено поступление
9.12 Отмена поставки POST /supplier/integration/supplies/cancel Поставка не приедет
9.14 Контрагенты POST /supplier/integration/clients При подключении и при изменениях списка

Общее для всех: X-Api-Key в каждом запросе (§3.2); Idempotency-Key в каждом POST (§3.5); {orderId} и {itemId} — разные идентификаторы (§5).

9.1. Проверка изменений

GET /supplier/integration/changes?since=2026-08-04T06:00:00Z
Параметр Обязательно Описание
since да Момент, с которого искать изменения; из serverTime прошлого ответа (§7.2). Первый запуск — текущее время минус сутки

Ответ 200:

{ "data": {
    "hasChanges": true,
    "ordersChanged": 3,
    "serverTime": "2026-08-04T09:14:02Z",
    "nextPollAfterSeconds": 60
} }
Поле Описание
hasChanges true → зовите ленту (§9.2); false → до следующего опроса делать нечего
ordersChanged Сколько заказов затронуто
serverTime since следующего опроса
nextPollAfterSeconds Интервал до следующего опроса; обязателен к соблюдению. Типичные значения: 30 с, когда изменения только что были; 60 с, если заказы шевелились в течение последнего часа; 300 с, если активности не было; 900 с ночью при той же тишине (ночной заказ вернёт обычные 60 с). Итог зажимается диапазоном 15…3600 с и может быть разрежен платформой глобально, поэтому значение читается из ответа, а не зашивается в код

nextPollAfterSeconds дублируется заголовком ответа X-Next-Poll-After — он приходит и с 304, где тела нет.

Поддерживается ETag/If-None-Match (тег слабый, возвращайте его в точности как получили): если с прошлого запроса ничего не изменилось — 304 без тела. Тег учитывает и since, поэтому 304 может прийти и на непустое окно: в этом случае используйте сохранённый ответ целиком, включая hasChanges, и не двигайте since — брать его неоткуда.

⚠️ serverTime намеренно отстаёт от реального времени примерно на полминуты: это гарантирует, что изменение, попавшее в границу опроса, не потеряется. Плата за это — иногда повторно приезжающий заказ, что §9.2 п. 3 и так требует переживать.

9.2. Лента заказов

GET /supplier/integration/orders?since=2026-08-04T06:00:00Z&limit=50
GET /supplier/integration/orders?cursor=<из meta>&limit=50

Заказы, изменившиеся после since, в порядке изменения.

Параметр Обязательно Описание
since для первой страницы Тот же момент, что в changes. Запрос без since и без cursor — 400
cursor для последующих meta.cursor предыдущей страницы. Значение непрозрачно: передавайте как есть, не разбирайте. Нечитаемый курсор — 400
limit нет 1–100, по умолчанию 50. Значение вне диапазона не отвергается: ≤0 и нечисло дают 50, больше 100 — 100

Ответ 200: массив заказов (§8.1) + meta:

{ "data": [ {
    "id": "018f3b2a-7c1e-7b2a-9f00-1a2b3c4d5e6f",
    "number": "00012345-0001",
    "status": "confirmed",
    "createdAt": "2026-08-04T08:55:10Z",
    "updatedAt": "2026-08-04T08:55:10Z",
    "warehouse": { "code": "MSK-01", "name": "Основной склад", "address": "Москва, Цветочный проезд, 1" },
    "buyer": { "legalName": "ООО «Ромашка»", "inn": "7701234567" },
    "note": null,
    "totalAmount": 1287500,
    "payment": { "method": "platform" },
    "items": [ {
        "itemId": "018f3b2a-8d2f-7c3b-a011-2b3c4d5e6f70",
        "externalCode": "RSA-FREEDOM-70",
        "title": "Роза Freedom 70 см",
        "quantity": 250,
        "fulfilledQuantity": null,
        "stemsPerBundle": 25,
        "pricePerUnit": 5150,
        "amount": 1287500,
        "status": "confirmed",
        "supplyExternalCode": null,
        "supplyExpectedAt": null
    } ]
} ],
  "meta": { "cursor": "MjAyNi0wOC0wNFQwODo1NToxMFo…", "hasMore": false } }

Семантика ленты:

  1. Это лента изменений, не только новых заказов: заказ приходит повторно при отмене, недостаче, выдаче. Каждый пришедший заказ — его текущее состояние целиком.
  2. Терминальные заказы (cancelled, rejected, completed) тоже приходят.
  3. Заказ может прийти повторно и без видимых изменений; повторная обработка того же состояния должна быть безопасной.
  4. Замена позиции. Если товара нет, поставщик вправе предложить покупателю замену; при согласии исходная строка приходит со статусом cancelled, а в заказе появляется новая строка с новым itemId, своим externalCode, названием и ценой. Сумма заказа пересчитывается платформой. Через API замена не предлагается — это действие человека в кабинете.
  5. Заказ приходит повторно и при перечислении вам денег: в payment появляются paidOutAt и paidOutAmount.
  6. Заказ поднимается в ленту и при приходе поставки — предзаказные позиции становятся обычными, даже если статус самого заказа не изменился.

Каких заказов в ленте нет. Заказ, ожидающий оплаты через платёжный сервис платформы, не отдаётся никогда — ни активным, ни терминальным. Поэтому такой заказ появляется у вас только после оплаты, а не дожидавшийся её — не появляется вовсе: закрывать в вашем учёте было бы нечего.

Что лента не сообщает. Способ выдачи (самовывоз или доставка) и адрес покупателя в проекции отсутствуют: warehouse — всегда ваш склад. totalAmount включает стоимость доставки, если покупатель её выбрал, поэтому сумма строк может быть меньше суммы заказа.

9.3. Готов к выдаче

POST /supplier/orders/{orderId}/ready

Когда: заказ собран. Тела нет. Переводит в ready позиции со статусом confirmed, покупатель получает уведомление.

Ответ 200: { "data": { "status": "ready" } }

Ошибка Причина
404 NOT_FOUND Нет такого заказа, или в нём нет ваших позиций

Повторный вызов безопасен: если готовить нечего (заказ готов, выдан, отклонён), платформа отвечает 200 и ничего не меняет. Состояние заказа сверяйте по ленте (§9.2), а не по коду ответа.

⚠️ Позиции-предзаказы метод не трогает. Заказ, состоящий только из них, ответит 200, но ничего не изменится: покупатель не получит ни уведомления, ни кода выдачи, пока не придёт поставка (§9.11).

9.4. Недостача по позиции

POST /supplier/orders/{itemId}/shortfall

Когда: по одной строке не удаётся выдать всё количество. {itemId} — идентификатор позиции (§5).

Тело:

{ "quantity": 50, "reason": "Пересорт на складе", "restock": false }
Поле Тип Обязательно Описание
quantity int да Размер недостачи — сколько штук НЕ будет выдано (не новое количество к выдаче). Кратно банчу, > 0, ≤ текущего к выдаче
reason string да Причина. Сохраняется в журнале заказа; покупателю текст не показывается
restock bool нет (по умолчанию false) Вернуть ли недоданное в остаток витрины. Для интегрированных систем — false: остаток установит ваш следующий stock (§9.8)

Пример: заказано 250 шт (10 банчей по 25), не хватает трёх банчей → quantity: 75, к выдаче останется 175.

Недостача на всё количество закрывает позицию (эквивалент §9.6); если позиция последняя — заказ уходит в rejected. Деньги за недоданное возвращаются покупателю автоматически.

Ответ 200: { "data": { "message": "..." } }. Актуальное состояние строки придёт ближайшей лентой (§9.2), отдельно запрашивать не нужно.

Ошибка Причина
400 VALIDATION_ERROR Некратно банчу, ≤ 0 или больше текущего к выдаче
404 NOT_FOUND Нет такой позиции — проверить, что передан itemId, а не id заказа
409 CONFLICT Позиция не активна (выдана/отменена)

9.5. Восстановление после ошибочной недостачи

POST /supplier/orders/{itemId}/restore

Тело: { "quantity": 50, "reason": "Нашли на втором складе" } — quantity: прибавка, то есть сколько штук вернуть в выдачу сверх текущего (не итоговое количество к выдаче — зеркало §9.4). Кратно банчу; итог не выше исходно заказанного. reason здесь не обязателен (в отличие от §9.4 и §9.6), но попадает в журнал заказа.

Пример: заказано 250, недостачей снято 75 (к выдаче 175), нашлись два банча по 25 → quantity: 50, к выдаче станет 225.

Ответ 200: { "data": { "message": "..." } }

Ошибка Причина
400 VALIDATION_ERROR Некратно банчу, ≤ 0 или итог выше исходно заказанного
404 NOT_FOUND Нет такой позиции
409 CONFLICT Позиция уже выдаётся в полном объёме, не активна, либо возвращать не из чего: у позиции в наличии не хватает свободного остатка (товар разобрали другие покупатели), у предзаказной — свободного объёма поставки

Восстановление берёт единицы из текущего свободного объёма партии, даже если недостача объявлялась без возврата в остаток (restock: false). Поэтому успех не гарантирован: между недостачей и восстановлением товар мог уйти другим покупателям.

9.6. Удаление позиции

POST /supplier/orders/{itemId}/remove

Когда: строка не будет выдана вовсе.

Тело: { "reason": "Товар списан", "restock": false } — поля как в §9.4; reason обязателен, restock по умолчанию false.

Деньги за позицию возвращаются. Удаление последней позиции переводит заказ в rejected. Убрать позицию можно и из уже собранного заказа (ready).

Ответ 200: { "data": { "message": "Order item removed" } }

Ошибка Причина
400 VALIDATION_ERROR Не передана причина
400 BAD_REQUEST Тело не разобралось
404 NOT_FOUND Нет такой позиции
409 CONFLICT Позиция уже выдана или не активна

9.7. Отклонение заказа

POST /supplier/orders/{orderId}/reject

Когда: заказ невозможно выдать целиком.

Тело: { "reason": "..." }. Причина обязательна, если в заказе есть собранные (ready) позиции — иначе 400 VALIDATION_ERROR.

Все позиции закрываются, деньги возвращаются, заказ — терминальный rejected. Если проблема в одной строке — §9.4 или §9.6.

Ответ 200: { "data": { "message": "Order rejected" } }

Ошибка Причина
404 NOT_FOUND Нет такого заказа или в нём нет ваших позиций
409 CONFLICT Заказ уже отменён либо отклонён, активных позиций не осталось или он ушёл дальше выдачи

⚠️ Повтор на уже отклонённом заказе — 409, а не тихий успех (в отличие от §9.3 и §9.13). Обработка не должна считать повтор безопасным.

9.8. Передача остатков и цен

POST /supplier/integration/stock

Когда: изменился остаток или цена — отправить изменившиеся строки; плюс, по желанию, полная сверка всего прайса по расписанию.

Тело: объект с единственным полем lines — массивом строк остатка. Строка — это «сколько сейчас лежит на складе и почём»: один ваш код номенклатуры на одном складе. Её поля и их обязательность описаны в §8.3; никакого другого источника этих строк нет — вы формируете их из своего регистра остатков.

Строк в пакете от 1 до 500; прайс длиннее отправляется несколькими запросами. Пустой пакет и пакет свыше 500 строк отвергаются целиком (400), построчного results в этом случае нет.

{ "lines": [
    {
      "externalCode": "RSA-FREEDOM-70",
      "warehouseCode": "MSK-01",
      "quantity": 1800,
      "pricePerUnit": 5000,
      "stemsPerBundle": 25
    },
    {
      "externalCode": "TLP-STRONG-GOLD-40",
      "warehouseCode": "MSK-01",
      "quantity": 900,
      "pricePerUnit": 3200,
      "stemsPerBundle": 50,
      "title": "Тюльпан Strong Gold 40 см",
      "category": "cut_flowers",
      "countryOfOrigin": "Нидерланды",
      "stemLengthCm": 40
    }
] }

Первая строка — код, платформе уже известный: у него обновятся только остаток и цена, остальные поля можно не слать. Вторая — код, приходящий впервые: к обязательным полям добавлены title и category, из них платформа создаёт карточку товара (§8.3).

Обработка строки:

  1. Поиск позиции в наличии по паре externalCode + warehouseCode.
  2. Найдена → обновляются остаток и цена, и только они.
  3. Не найдена → создаётся карточка и позиция (нужны title, category).
  4. Остаток применяется как витрина = физический остаток − удержанное платформой (§4.7).
  5. Позиция, удалённая поставщиком на платформе, повторной строкой не пересоздаётся — ответ EXCLUDED; вернуть её можно в кабинете.

Строки независимы: деловая ошибка одной не отменяет остальные. Инфраструктурный сбой — исключение: он роняет весь запрос (5xx), чтобы вы повторили пакет целиком, а не похоронили его как «ошибку данных».

Каждая строка применяется отдельно и сразу, поэтому обрыв связи в середине пакета оставляет уже принятые строки применёнными. Это безопасно: значения абсолютные, и повторная отправка того же пакета (с новым Idempotency-Key, §3.5) приводит витрину к тому же состоянию.

⚠️ Остальные атрибуты применяются только при первичном заведении позиции. У уже существующей позиции название, категория, stemsPerBundle, страна, длина стебля и плантация из строки игнорируются — они правятся в кабинете. Сменить размер банча выгрузкой нельзя.

⚠️ Что именно удерживает платформа (пункт 4): все корзинные брони покупателей и позиции принятых заказов, ещё не выданных покупателю. Оплата роли не играет, выданные заказы не вычитаются, по позиции с недостачей берётся фактическое к выдаче. Результат ниже нуля не опускается.

Товар будущих поставок этим методом не управляется — состав и цены поставки ведут §9.9–9.10, в наличие она попадает приходом (§9.11). Если код закреплён только за позицией будущей поставки, строка stock заведёт под ним отдельную позицию в наличии: это разные сущности.

Позиции, заведённые на платформе до подключения (вручную или загрузкой прайса), вашего кода не имеют. Их сопоставляют с вашей номенклатурой один раз в кабинете — до первой выгрузки. Платформа при этом защищается от дублей сама: строка с новым кодом, похожим на уже выложенную несвязанную позицию того же склада, не применяется — приходит VALIDATION_ERROR с пояснением, а в кабинете появляется предложение связать их. Ответ «это другой товар» запоминается навсегда, и следующая выгрузка заводит отдельную позицию.

Ответ 200:

{ "data": {
    "accepted": 48,
    "rejected": 2,
    "results": [
      {
        "index": 7,
        "externalCode": "RSA-X",
        "status": "error",
        "code": "VALIDATION_ERROR",
        "message": "Цена должна быть больше нуля"
      },
      {
        "index": 19,
        "externalCode": "TLP-Y",
        "status": "error",
        "code": "UNKNOWN_WAREHOUSE",
        "message": "Код склада «MSK-99» не сопоставлен"
      }
    ]
} }

results содержит только ошибочные строки; index — номер строки в вашем запросе с нуля.

Исключение — 403 BILLING_RESTRICTED (§3.8): при приостановке размещения отклоняется весь пакет целиком, results не приходит. Ответ разбирается до data, по HTTP-коду.

Код Значение
UNKNOWN_WAREHOUSE Код склада не сопоставлен в кабинете
AMBIGUOUS_EXTERNAL_CODE Под кодом на этом складе найдено несколько позиций — платформа не выбирает за вас, сведите их в кабинете
EXCLUDED Позиция удалена поставщиком на платформе. Запрет действует на код в пределах компании, а не на пару «код + склад»
VALIDATION_ERROR Формат, диапазон, отсутствует обязательное поле — а также отказы по существу: «позиция больше не в наличии», «код закреплён за другим вашим товаром», «код похож на вашу позицию …», ограничение публикации

Верхние границы значений (превышение — построчный VALIDATION_ERROR): остаток до 100 000 штук, цена до 10 000 000 копеек (100 000 ₽) за штуку, stemsPerBundle до 10 000.

⚠️ Нулевая строка по НОВОМУ коду не «ничего не делает»: карточка и позиция всё равно заводятся, с нулевым остатком. Выгрузка всего прайса с нулями наполнит каталог пустыми позициями — снимать с витрины нулём имеет смысл только уже заведённые позиции.

Появление позиции на витрине зависит не только от обмена: показывается она в регионах, которые поставщик выбрал в кабинете, и только с ненулевым остатком. Если позиция принята (accepted), а покупатели её не видят — начинать разбор нужно с настроек витрины, а не с обмена.

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

9.9. Создание поставки

POST /supplier/integration/supplies

Когда: проведён заказ поставщику — известны состав и дата прихода. Поставка появляется на витрине заранее, покупатели оформляют предзаказы.

Тело: объект §8.4; один ваш документ = один вызов со всеми позициями.

{
  "externalCode": "ПСТ-000123",
  "warehouseCode": "MSK-01",
  "expectedAt": "2026-08-15",
  "publishAt": "2026-08-10T06:00:00Z",
  "items": [
    {
      "externalCode": "RSA-FREEDOM-70",
      "title": "Роза Freedom 70 см",
      "category": "cut_flowers",
      "countryOfOrigin": "Эквадор",
      "stemLengthCm": 70,
      "quantity": 2500,
      "pricePerUnit": 4800,
      "stemsPerBundle": 25
    }
  ]
}

Ответ 201: созданная поставка — платформенный id, ваш externalCode, статус, склад, даты и позиции с фактически применённым количеством. Количество округляется вниз до целых банчей, поэтому сверяйте его с отправленным: остаток от округления в поставку не попадает.

  • Повторный вызов с тем же externalCode возвращает существующую поставку с ответом 200 — независимо от Idempotency-Key.
  • publishAt не указан → черновик, покупателям не виден; указан → публикация в этот момент. Опубликовать черновик можно из кабинета или следующим §9.10 с publishAt. Пока по поставке висит неотвеченный вопрос о связывании (см. ниже), переданный publishAt не применяется; сама собой такая поставка не опубликуется и после ответа — её публикует человек в кабинете либо следующий §9.10 с publishAt.
  • Если поставка пересекается составом и датой (±3 дня) с заведённой не через обмен — вручную в кабинете или загрузкой прайса, — она создаётся черновиком, а в кабинете появляется предложение связать их. На ваш вызов это не влияет — ответ тот же 201. ⚠️ Если в кабинете ответят «связать», ваш номер документа переедет на существующую поставку, и повторный вызов вернёт 200 с другим платформенным id: адресуйтесь своим externalCode, платформенный id хранить не нужно.
  • При приостановке размещения (§3.8) метод отвечает 403 BILLING_RESTRICTED.
  • Документ обязан нести хотя бы одну позицию; позиций до 500, коды внутри документа не повторяются.
  • externalCode уникален в пределах компании и живёт вечно: переиспользовать номер закрытой или отменённой поставки нельзя.

Прочие причины отказа (400): дата прихода уже прошла, publishAt позже конца дня прихода, несопоставленный код склада, исключённый код (§9.8), код похож на вашу существующую позицию, нулевая цена или банч, объём меньше банча. Отклонённый документ следов в кабинете не оставляет.

⚠️ У поставок отказ всегда роняет ВЕСЬ документ. Построчного results, как в §9.8, здесь нет: одна испорченная позиция отменяет весь вызов с указанием её номера. Это относится и к тем причинам, которые в остатках приходят построчно, — исключённый код и совпадение с существующей позицией.

9.10. Изменение поставки

PUT /supplier/integration/supplies

Тело: то же, что §9.9, с тем же externalCode. Платформа приводит поставку к переданному состоянию по externalCode позиций: новые добавляются, отсутствующие удаляются, совпадающие обновляются.

Попытка Ответ
Количество позиции ниже уже предзаказанного 409 CONFLICT
Удаление позиции с предзаказами 409 CONFLICT
Поставка уже приехала, отменена, либо позиция изменилась параллельно 409 CONFLICT
Неизвестный externalCode 404 NOT_FOUND — документ не заводится заново, это именно «не найдено»
Добавление позиции, увеличение количества или первый показ поставки покупателям при приостановке размещения (§3.8) 403 BILLING_RESTRICTED

409 означает, что объём уже кому-то обещан — предзаказом либо корзиной покупателя, оформляющего заказ прямо сейчас. Если товар не приедет, урезание оформляется недостачей по конкретным заказам после прихода (§9.4).

403 смотрит на содержимое документа, а не на сам метод: при приостановке размещения документ, который только убирает позиции, уменьшает количество или меняет цены, применяется как обычно.

Ещё четыре правила этого метода:

  • publishAt отсутствует = «не менять». Снятия поставки с витрины в контракте нет вовсе. На черновике переданный момент публикует поставку, на уже показанной будущий момент отвечает 409 (предзаказы открыты), прошедший ошибкой не считается. Исключение: пока в кабинете висит неотвеченный вопрос о связывании (см. §9.9), публикация не применяется — ответ будет 200, но поставка останется черновиком. После ответа она тоже не публикуется сама: это делает человек в кабинете или следующий вызов с publishAt.
  • Позиции без вашего кода метод не трогает — их завёл человек в кабинете (например, после ответа «связать»). Позиции, уже уехавшие в наличие поштучным приходом, молча пропускаются.
  • Документ обязан нести хотя бы одну позицию: «обнулить» поставку изменением нельзя — для этого есть §9.12.
  • Дата прихода должна оставаться в будущем. Тело разбирается теми же правилами, что и в §9.9, поэтому изменить поставку, дата которой уже наступила (а такая, скорее всего, уже закрыта платформой), нельзя — 400.
  • Расхождение склада — не ошибка. Если warehouseCode документа не совпадает со складом поставки на платформе (так бывает после ответа «связать» в кабинете), новые позиции заводятся на склад самой поставки, а фактический склад приходит в ответе. Но код склада всё равно обязан быть сопоставлен: несопоставленный отвергает документ целиком.
  • ⚠️ Шапка документа применяется раньше состава. Если состав отбит 409, новая дата прихода к этому моменту уже сохранена, а покупатели с предзаказами уведомлены о переносе. Повтор исправленного документа безопасен, но считать, что «ничего не произошло», нельзя.

9.11. Приход поставки

POST /supplier/integration/supplies/arrive

Когда: проведено поступление — поставка приехала.

Тело: { "externalCode": "ПСТ-000123" }

Предзаказы поставки становятся обычными заказами (pre_order → confirmed) и приходят в ленте (§9.2) как confirmed; товар появляется в наличии.

Ответ Когда
200 Приход применён; повтор по уже закрытой поставке тоже 200 — ретрай безопасен
404 NOT_FOUND Неизвестный externalCode
409 CONFLICT Поставка отменена, либо это черновик (сначала опубликуйте её или заводите товар методом §9.8)

⚠️ Опубликованная поставка закрывается платформой САМА в конце дня expectedAt: предзаказы становятся заказами, товар встаёт в наличие. Если поставка задерживается, перенесите дату методом §9.10 — иначе платформа посчитает её приехавшей, не дождавшись вашего вызова.

9.12. Отмена поставки

POST /supplier/integration/supplies/cancel

Тело: { "externalCode": "ПСТ-000123", "reason": "Отменена производителем" }

reason обязательна (400 при пустой, до 2000 символов): покупатели с предзаказами получат её в уведомлении. Поставка снимается с витрины, предзаказы отменяются с возвратом денег, а позиции поставки убираются из корзин покупателей.

Ответ Когда
200 Отменена; повторная отмена тоже 200
404 NOT_FOUND Неизвестный externalCode
409 CONFLICT Поставка уже приехала — отменять нечего (именно 409, а не 404: документ существует)

9.13. Откат готовности

POST /supplier/orders/{orderId}/unready

Когда: документ сборки распроведён или удалён — заказ ещё не собран. Тело не обязательно; можно передать { "reason": "..." } — причина попадёт в журнал заказа (без неё платформа запишет «Сборка отменена в учётной системе»).

Возвращает заказ из «готов к выдаче» в «ждёт сборки» и снимает отметку о передаче в доставку, если она была. Покупатель не получает уведомления об отмене — только о повторной готовности, когда вы снова отправите ready.

Ответ 200: { "data": { "status": "confirmed" } }

Ошибка Причина
404 NOT_FOUND Нет такого заказа или в нём нет ваших позиций
409 CONFLICT Заказ уже выдан покупателю, терминален либо активных позиций не осталось

Повторный вызов безопасен: заказ, уже ожидающий сборки, отвечает 200 без изменений.

9.14. Контрагенты

POST /supplier/integration/clients

Когда: при подключении — весь список; далее при изменениях: появился новый контрагент, изменилась или снята персональная скидка, изменилось название. Отслеживать изменения поштучно не обязательно — допустимо просто отправлять весь список по расписанию (например, раз в сутки).

Платформа запоминает ваших клиентов по ИНН и применяет вашу скидку, когда клиент с этим ИНН регистрируется на платформе.

Тело — до 500 контрагентов за запрос:

{ "clients": [
    { "legalName": "ООО «Ромашка»", "inn": "7701234567", "discountBps": 500 }
] }
Поле Тип Обязательно Описание
legalName string да Юридическое название
inn string да ИНН: 10 цифр (юрлицо) или 12 (ИП), с верными контрольными цифрами
discountBps int нет Персональная скидка в сотых долях процента: 500 = 5 %, 1050 = 10,5 %. Диапазон 0–9999, где 0 = скидку снять. Поле не передано — скидка не меняется

Правила:

  • строки обновляются по ИНН — повторная передача не создаёт дубль;
  • отсутствие контрагента в пакете ничего не меняет: удаления через этот метод нет;
  • скидку, изменённую человеком в кабинете, выгрузка не перезаписывает;
  • название длиннее 200 символов обрезается, а не отвергается: контрагент со скидкой не теряется;
  • пустой список — штатный успех (200, accepted: 0), в отличие от §9.8: ночное задание с пустым справочником не должно превращаться в инцидент. А вот 501-я строка отвергает весь запрос;
  • дубли внутри одного пакета схлопываются в одну запись: имя берётся из последней строки, скидка — из последней, которая её указала. При этом обе строки считаются принятыми.

Скидка встаёт сразу, если покупатель с этим ИНН уже зарегистрирован, и позже — когда он укажет ИНН в профиле. Засчитывается только аккаунт покупателя: контрагент, зарегистрировавшийся на платформе поставщиком, скидку не получит.

Реестр контрагентов общий для всех учётных систем: строка опознаётся парой «поставщик + ИНН». Если компания переезжает с другой системы на вашу, выгрузка обновит уже существующие строки, а не заведёт вторые.

Телефоны, адреса и другие персональные данные не принимаются — платформа их не хранит.

Ответ 200: форма как в §9.8, но ошибочная строка идентифицируется полями index и inn — внешнего кода у контрагента нет:

{ "data": {
    "accepted": 2,
    "rejected": 1,
    "results": [
      {
        "index": 4,
        "inn": "7701234560",
        "status": "error",
        "code": "INVALID_INN",
        "message": "Неверный ИНН"
      }
    ]
} }

Права: метод требует уровня «Заказы: полный» (§3.2). У нового ключа по этому разделу стоит просмотр, поэтому из коробки метод отвечает 403 PERMISSION_DENIED; уровень переключает владелец в карточке ключа.

Приостановка размещения (§3.8) на этот метод не распространяется.


10. Справочник категорий

Код Значение
cut_flowers Срезанные цветы
plants Растения
dried Сухоцветы
artificial Искусственные цветы
supplies Флористические материалы

Иное значение → 400.


11. Коды ошибок и частые ошибки

11.1. Коды в объекте error

Код HTTP Значение
BAD_REQUEST 400 Запрос не разобрался: битый JSON, не-UUID в адресе
VALIDATION_ERROR 400 Кратность, диапазоны, формат даты
UNAUTHORIZED 401 Ключ отсутствует, неверен или отозван
FORBIDDEN, PERMISSION_DENIED 403 Права ключа не позволяют действие — либо адрес вообще не входит в список доступных ключу (тогда расширение прав не поможет)
PROFILE_INCOMPLETE 403 Профиль владельца не подтверждён: почта, ИНН, банк. Ответ несёт список недостающих полей
ACCOUNT_DEACTIVATED 403 Аккаунт владельца удалён — обмен не возобновится
BILLING_RESTRICTED 403 Размещение нового приостановлено за неоплаченный счёт платформы (§3.8)
SITE_LOCKED 403 Технологический замок контура. У машинного обмена почти всегда означает другое: адрес вне списка доступных ключу либо отсутствующий заголовок X-Api-Key
NOT_FOUND 404 Объект не найден или не ваш
CONFLICT 409 Состояние объекта несовместимо с действием
TOO_MANY_REQUESTS 429 Слишком часто; ждать Retry-After
INTERNAL_ERROR 500 Ошибка платформы; повтор с задержкой
SERVICE_UNAVAILABLE 503 Временно недоступны; повтор с задержкой

11.2. Частые ошибки интеграций

Ошибка Правильно
id заказа в методе позиции (или наоборот) §5: ready/unready/reject — orderId; shortfall/restore/remove — itemId
В shortfall.quantity — новое количество к выдаче Там размер недостачи
Цена 45.50 Целые копейки: 4550
Количество в банчах Всегда в штуках
Остаток за вычетом броней платформы Физический остаток
since из собственных часов Из serverTime прошлого ответа
Новый Idempotency-Key при повторе Повтор со старым ключом
HTTP-вызов из проведения документа Через локальную очередь (§7.3)
Мгновенный повтор при 5xx Пауза 1 → 5 → 15 минут
Повтор при 400/404/409 Не повторять; в журнал. Кроме 409 «запрос ещё выполняется» — его повторяют тем же ключом (§3.5)
Исправленный пакет отправлен со старым Idempotency-Key Новый ключ: старый вернёт сохранённый ответ и ничего не применит
Ожидание, что поставка дождётся вашего «прихода» Опубликованная поставка закрывается сама в конце дня expectedAt — задержку переносят §9.10
Разбор meta.cursor как JSON Значение непрозрачно: возвращать как получено

12. Тестовый контур и журнал обмена

  • Тестовый контур https://stg.lotusmarket.ru/api/v1 изолирован от боевых заказов; ключ для него выдаётся отдельно.
  • Журнал обмена — в кабинете поставщика: метод, адрес, код ответа и число принятых и отклонённых строк по пакетным методам. Хранится 30 суток, переживает отзыв ключа.

⚠️ Причин отказа по конкретным строкам в журнале нет — они приходят только в теле ответа метода. Сохраняйте ответы у себя: иначе разобрать, почему не приехали три позиции из пятисот, будет нечем. (Исключение — методы, где отказ роняет запрос целиком, например поставки: там причина попадает в журнал вместе с кодом ответа.)

В журнал не попадают: успешные проверки изменений (§9.1) — любые, включая hasChanges: true и 304; отказы 401; отказы «метод недоступен ключу»; превышения общих потолков частоты. А вот отказы 409 по идемпотентности (§3.5) записываются — по ним видно, что очередь повторяет один и тот же запрос. Отозванный ключ виден в кабинете только по замершей колонке «Последнее обращение».


13. Чек-лист готовности к боевому запуску

Все сценарии проверяются на тестовом контуре.

Заказы

  • Новый заказ появляется документом; повторный запуск обработки не создаёт второй документ.
  • ready меняет статус на платформе; повтор с тем же Idempotency-Key не дублирует действие.
  • Недостача: заказ на 10 банчей, трёх не хватило → shortfall с quantity = 3 банча в штуках, к выдаче 7 банчей.
  • Недостача с некратным количеством → 400, строка в журнал, очередь не зацикливается.
  • Распроведение документа сборки → unready; повторное проведение → ready.
  • Заказ в cancelled/rejected закрывает ваш документ.
  • Замена позиции: старая строка пришла cancelled, новая с другим itemId заведена, сумма сошлась.
  • Суммы документов совпадают с totalAmount/amount ленты (цены покупателя, §4.6).
  • Заказ, оплаченный через платформу: по приходу paidOutAt/paidOutAmount создан документ поступления денег.

Остатки и цены

  • Изменение остатка и цены отражается на витрине; остаток 0 снимает позицию.
  • Пакет с ошибочной строкой: остальные применены, ошибочная видна в ответе метода (с номером строки и кодом причины) и сохранена в вашем журнале.
  • Позиции, заведённые на платформе ранее, сопоставлены до первой выгрузки — дублей в каталоге не появилось.

Поставки и контрагенты (если используются)

  • Повторная отправка поставки с тем же externalCode не создаёт дубль.
  • После arrive предзаказы приходят в ленте как confirmed.
  • Выгрузка контрагентов принята; строка с битым ИНН отклонена, остальные применены; новый контрагент и снятие скидки (discountBps: 0) доехали.

Отказоустойчивость

  • Обрыв связи посреди цикла: после восстановления ничего не потеряно и не задвоено.
  • 5xx → повтор с нарастающей задержкой; 429 → пауза Retry-After; nextPollAfterSeconds соблюдается.
  • 401 → обмен остановлен, администратор уведомлён, автоповторов нет.
  • События уходят через локальную очередь; недоступность платформы не блокирует проведение документов.

ЛОТУС

оптовый маркетплейс

B2B-маркетплейс оптовой торговли цветами.

О проекте

  • О нас
  • Тарифы
  • Контакты

Документы

Согласия

Поддержка

  • info@lotusmarket.ru
  • 8 (993) 972-23-43
  • Telegram
  • WhatsApp
  • Max
  • Написать нам

ООО «Лотус Маркет» · ИНН 7804721380 · ОГРН 1267800046601 · 195197, г. Санкт-Петербург, вн.тер.г. муниципальный округ Финляндский округ, Полюстровский пр-кт, д. 59, литера Ф, помещ. 1-Н

© 2026 ЛОТУС МАРКЕТ. Все права защищены.

Версия 2.1.0