К-247автозапчасти

Документация для продавцов и программистов

Как подключить прайс и API

Файл, ссылка, почта или API v1. Версия 1.7.0.

Четыре способа подключить прайс

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

  1. 01Загружать файл вручнуюПрайс в Excel, обновляете сами. Кнопка «Загрузить прайс» в кабинете.Как подготовить файл
  2. 02Прайс по ссылкеВаша программа выкладывает файл на сайт, площадка сама забирает его по расписанию.Как настроить ссылку
  3. 03Прайс по почтеПрограмма или сотрудник отправляет письмо с файлом на персональный адрес.Как настроить почту
  4. 04Через программу или APIЕсть программист или 1С-интегратор: остатки, цены и заказы обмениваются автоматически.Как получить ключ

Прайс старше 72 ч показывается покупателям с пометкой «под заказ». Заказы подключённых магазинов нужно подтверждать: срок — 2 ч, подробности в разделе «Заказы продавца».

Файл прайса

Подходят CSV, XLSX, XLS, XML (в том числе фид YML) и ZIP с одним таким файлом, до 100 МБ и 500 000 строк. Первая строка — заголовки, порядок колонок любой: площадка узнаёт их по названию, а если не узнала, в кабинете можно сопоставить колонки вручную и запомнить. В книге с несколькими листами выберите лист с прайсом в мастере загрузки.

Колонки

ПолеОбяз.Что писатьЗаголовки, которые узнаются
brandдаБренд производителя, как в каталоге: FEBI BILSTEIN, BOSCHбренд, производитель, марка, фирма, изготовитель
articleдаАртикул производителя. Храните колонку как текст, чтобы Excel не убрал ведущие нулиартикул, номер, каталожный номер, код производителя, номер детали
nameдаНаименование деталинаименование, название, товар, описание, name
priceдаЦена в рублях: 1234,50 или 1234.50. В API — целые копейки, поле price_kopцена, стоимость, цена продажи, цена руб, розница
qtyдаОстаток, целое число от 0. Ноль снимает позицию с продажиостаток, наличие, количество, кол-во, склад остаток
term_days_minнетСрок поставки в днях; 0 — в наличиисрок, срок поставки, срок мин, дней, delivery
term_days_maxнетГарантированный срок в дняхсрок макс, гарантированный срок, срок гарант, term max, guaranteed
termнетСрок текстом, если нет числовых колонок: «2-3 дня»срок текстом
warehouseнетКод склада из кабинета; пусто — склад по умолчаниюсклад, город, warehouse, store, stock code
multiplicityнетКратность продажи, по умолчанию 1кратность, партия кратно, упаковка, multiplicity, pack
min_orderнетМинимальная партияминимальная партия, мин заказ, min order, moq
probabilityнетВероятность поставки, 0–100вероятность, вероятность поставки, probability
external_idнетВаш внутренний код товара, до 64 символов. По нему API находит позициюкод товара, внутренний код, код у продавца, id, external id
oemнетОригинальный номероригинальный номер, оем, oem, кросс, cross
groupнетГруппа или категориягруппа, категория, group, category
applyнетПрименимостьприменимость, применяемость, apply, application
conditionнетnew или used (новая, б/у)состояние, condition
vatнетСтавка НДС: «НДС 22%», «Без НДС»ндс, vat
returnableнетВозвратный товар: да или нетвозврат, возвратный, returnable
warranty_monthsнетСрок гарантии в месяцах, от 1 до 120: «24», «24 мес.», «2 года». Пусто — гарантия по условиям площадки. В API — целое число или nullгарантия, гарантийный срок, срок гарантии, гарантия мес, warranty
warranty_manufacturerнетГарантия изготовителя детали: да или нет. Указывается вместе со срокомгарантия изготовителя, гарантия производителя, заводская гарантия, manufacturer warranty, warranty manufacturer
photoнетСсылка на фото, только httpsфото, изображение, картинка, photo, image
updated_atнетВремя строки в формате ISO 8601 (только CSV и API)дата обновления, updated, updated at

Колонка «код» без колонки «артикул» считается артикулом, а рядом с «артикулом» — вашим кодом товара (external_id). Бренд, которого нет в справочнике, не публикуется: такие строки проверяет менеджер, после проверки они появятся сами.

Пять колонок Docpart и ABCP

Файл без заголовка с колонками по порядку Производитель; Артикул; Наименование; Количество; Цена принимается как есть.

Кодировка и разделитель

UTF-8 (с BOM или без), Windows-1251 и UTF-16 определяются сами. Разделитель — точка с запятой, табуляция, запятая или вертикальная черта.

Ведущие нули в Excel

Excel превращает артикул 0986424815 в число и теряет ноль. Задайте колонке артикулов формат «Текстовый» до вставки данных или сохраните значение как ="0986424815" — площадка уберёт кавычки и знак равенства.

Режим загрузки

«Файл — это весь мой склад»: позиции, которых нет в файле, обнуляются. «Файл — только изменения»: остальное не трогается. Если пропало больше 30 % позиций или цены у многих строк изменились больше чем вдвое, загрузка останавливается для проверки — в кабинете появятся кнопки «Опубликовать всё равно» и «Отменить загрузку».

Прайс по ссылке

  1. Настройте в своей программе выгрузку прайса в файл на сайт: адрес вида https://shop.ru/export/k247.csv.
  2. В кабинете: «Прайс и подключение» → «Источники» → «Добавить ссылку на прайс». Вставьте адрес, при необходимости логин и пароль.
  3. Выберите, как часто забирать файл (не чаще раза в час), режим и склад, нажмите «Сохранить ссылку».
  4. В карточке источника нажмите «Проверить ссылку» — появятся колонки файла. Первый раз можно нажать «Скачать прайс сейчас», дальше прайс обновляется сам.
  5. Колонки не нашлись, в книге нужен другой лист или по ссылке свой XML: нажмите «Настроить колонки прайса» или «Настроить поля XML». К-247 скачает файл и покажет мастер, сопоставление запомнится для следующих скачиваний.

Площадка запрашивает файл с заголовками If-None-Match и If-Modified-Since: если файл не изменился, сервер может ответить 304, прайс при этом считается свежим. Представляется как K247-PriceFetcher/1. Адреса внутренних сетей не принимаются. Пароли ссылок хранятся зашифрованными.

Три неудачи подряд — уведомление в кабинет, десять — источник выключается. Ответ 401 или 403 означает неверный логин или пароль.

Прайс по почте

Способ для магазина без программиста: достаточно почты, из которой уходит файл прайса.

  1. Войдите в кабинет и откройте раздел «Прайс и подключение». Выберите путь «Прайс по ссылке или почте».
  2. Во вкладке «Источники» создайте источник «Прайс по почте»: укажите склад и режим. Для начала оставьте режим «только изменения».
  3. Кабинет покажет ваш персональный адрес вида price+ab12cd34ef56gh78@k247.online. Нажмите «Скопировать адрес».
  4. Отправьте на этот адрес письмо с файлом прайса во вложении (CSV, XLSX, XLS, XML или ZIP). Тема и текст письма не важны. Если прайс выгружает программа (1С, МойСклад), укажите адрес в её настройке отправки.
  5. Через одну-две минуты во вкладке «Обзор» появится «Прайс обновлён» и число позиций на витрине. Ошибки строк — во вкладке «Прайсы».

Что важно знать

  • Адрес личный: не публикуйте его. Если адрес попал к посторонним, нажмите «Сменить адрес» — старый перестанет работать.
  • Письмо целиком — не больше 30 МБ. Файл больше — заархивируйте в ZIP.
  • В режиме «весь склад» первые три письма ждут вашей проверки: откройте загрузку и нажмите «Опубликовать всё равно».
  • Письмо, не прошедшее проверку отправителя (DMARC), не загружается — в кабинет придёт уведомление. Отправляйте с настоящего адреса вашего домена.
  • В «Дополнительно» укажите адрес или домен отправителя, например *@shop.ru. Без него письмо, отправитель которого не подтверждён проверкой DMARC, ждёт вашей проверки: так прайс не заменит посторонний, узнавший адрес. Там же можно ограничить тему и имя файла. По умолчанию берётся любое вложение CSV, XLSX, XLS, XML или ZIP.
  • Колонки письма не нашлись или во вложении свой XML: нажмите в карточке источника «Настроить колонки прайса» или «Настроить поля XML». К-247 возьмёт файл последнего письма, а если писем ещё не было, попросит образец файла.
  • Одно письмо — один прайс для одного склада. Для нескольких складов создайте несколько источников: у каждого свой адрес.

Вставка таблицы из Excel

Способ для прайса, который открыт в Excel или Google Таблицах: сохранять файл в CSV не нужно.

  1. Выделите в таблице ячейки прайса вместе со строкой заголовков и скопируйте их: Ctrl+C, на Mac — Cmd+C.
  2. В кабинете откройте «Прайс и подключение» → «Прайсы» → «Загрузить прайс» и нажмите «Вставить таблицу из Excel».
  3. Щёлкните в поле и вставьте ячейки: Ctrl+V, на Mac — Cmd+V. Под полем появится итог, например «Вставлено: 1 234 строки, 6 колонок».
  4. Нажмите «Проверить колонки». Дальше всё как с файлом: проверьте, какие колонки нашлись, выберите склад и режим, нажмите «Проверить и загрузить».

Что важно знать

  • Вставка превращается в файл Вставка из буфера ДД.ММ ЧЧ:ММ.tsv: ячейки через табуляцию, кодировка UTF-8. Он появится в истории загрузок во вкладке «Прайсы», как обычный файл.
  • Копируется текст, который виден в ячейках. Поэтому артикул 0986424815 сохраняет ведущий ноль, если в таблице он виден с нулём. Цена вида «1 234,50 ₽» распознаётся.
  • Ячейки с переводом строки и кавычками переносятся без искажений. Если программа кладёт в буфер только HTML-таблицу, кабинет разберёт и её.
  • Лимиты те же, что у файла прайса. Для большой таблицы в поле видны первые 50 строк, загружается вся таблица. Если таблица больше лимита, кабинет попросит загрузить её файлом.
  • Если кабинет пишет «Не видим колонок», в буфере текст, а не ячейки. Скопируйте диапазон ячеек в самой таблице.
  • На телефоне удобнее загрузить файл: сохраните таблицу в XLSX или CSV и выберите её кнопкой «Выбрать файл прайса».

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

Прайс в формате XLS (Excel 97–2003)

Файл .xls из старого Excel, 1С или рассылки дистрибьютора загружается как есть. Пересохранять его в XLSX или CSV не нужно.

  1. В кабинете откройте «Прайс и подключение» → «Прайсы» и нажмите «Загрузить прайс».
  2. Нажмите «Выбрать файл прайса» и выберите файл .xls. Подойдёт и ZIP-архив с одним таким файлом.
  3. Проверьте, какие колонки нашлись по заголовкам, выберите склад и режим. Дальше всё как с файлом XLSX или CSV.

Файл .xls так же принимается во вложении письма, по ссылке и с FTP. Настраивать ничего не нужно: формат определяется по содержимому файла, а не по имени.

Что важно знать

  • Подходят книги Excel 97–2003 и Excel 5.0/95, а также файлы, которые 1С, LibreOffice и другие программы сохраняют как «Microsoft Excel 97–2003».
  • Прайс берётся с первого видимого листа. Скрытые листы и листы-диаграммы пропускаются. Если в книге несколько листов, мастер загрузки покажет поле «Лист книги с прайсом»: выберите нужный лист, и он запомнится вместе с колонками. У ссылки и почты лист выбирается кнопкой «Настроить колонки прайса» в карточке источника.
  • Из ячейки с формулой берётся посчитанное значение, как оно видно в Excel. Дата попадает числом, как в XLSX.
  • Артикул 0986424815 сохраняет ведущий ноль, если в ячейке текст. Если артикул записан числом, Excel хранит его без нуля: задайте колонке формат «Текстовый» и введите артикулы заново.
  • «Защита листа» и «Защитить книгу» загрузке не мешают. Пароль на открытие файла нужно снять: «Файл» → «Сведения» → «Защита книги» → «Зашифровать с использованием пароля», очистите поле и сохраните файл. Иначе загрузка остановится с ошибкой «Файл защищён паролем».
  • Файл XLS — до 50 МБ, но не больше общего лимита 100 МБ. На листе Excel 97–2003 помещается не больше 65 536 строк. Для прайса больше сохраните его в XLSX или CSV.

Если файл не загрузился

  • «Файл XLS повреждён или это не Excel 97⁠–⁠2003»: файл обрезан при скачивании или это другой документ. Откройте его в Excel и сохраните как XLSX или CSV.
  • «Файл Excel 2.0–4.0 не поддерживается»: файл из очень старой версии Excel. Сохраните его как XLSX.
  • «Это веб-страница, а не таблица»: программа сохранила HTML-страницу с расширением .xls. Откройте её в Excel и сохраните как XLSX или CSV. Текст с табуляцией под именем .xls читается как CSV сам.
  • «Слишком много уникальных строк»: в книге больше миллиона разных текстов. Сохраните прайс как CSV.

Прайс в Google Таблицах, на Яндекс Диске и в других облаках

Если прайс лежит в облаке, свой сайт не нужен: К-247 скачивает файл по ссылке «Поделиться» по расписанию, и прайс на площадке обновляется сам.

  1. Откройте доступ к файлу по ссылке и скопируйте ссылку. Как это сделать в каждом сервисе, написано ниже.
  2. В кабинете откройте «Прайс и подключение», вкладку «Источники» и нажмите «Добавить ссылку на прайс». Вставьте ссылку как есть. Логин и пароль не нужны.
  3. Выберите, как часто забирать файл, режим и склад, нажмите «Сохранить ссылку». В карточке источника появится строка «Распознано», например «Google Таблица, лист gid 0».
  4. Нажмите «Проверить ссылку»: карточка покажет, какой файл нашёлся и какие в нём колонки. Для Яндекс Диска это имя файла и дата изменения.

Google Таблицы

  • В таблице нажмите «Настройки доступа». В блоке «Общий доступ» выберите «Все, у кого есть ссылка» и роль «Читатель», затем «Копировать ссылку».
  • Нужен определённый лист: откройте его и скопируйте адрес из строки браузера, в нём есть #gid=…. Если gid в ссылке нет, берётся первый лист.
  • Опубликованная таблица тоже подходит: меню «Файл», пункт «Поделиться», затем «Опубликовать в интернете».
  • Лист скачивается в CSV так, как значения видны в ячейках: ведущие нули артикулов сохраняются, формулы считает Google.

Google Диск

  • Подходит файл прайса CSV, XLSX, XLS, XML или ZIP. Нажмите на файле «Поделиться», выберите «Все, у кого есть ссылка», затем «Копировать ссылку».
  • Ссылка на папку не подходит: откройте файл прайса и скопируйте ссылку на него. Google Таблицу на Диске подключайте ссылкой из открытой таблицы.

Яндекс Диск

  • Нажмите на файле «Поделиться» и «Скопировать ссылку». Получится адрес вида https://disk.yandex.ru/i/… или https://disk.yandex.ru/d/….
  • Можно дать ссылку на папку: К-247 возьмёт в ней самый новый файл CSV, XLSX, XLS или ZIP. Чтобы всегда брать один и тот же файл, откройте его в папке и скопируйте адрес из строки браузера: https://disk.yandex.ru/d/…/price.xlsx.
  • Если файл не менялся (та же контрольная сумма и дата), он не скачивается заново: прайс просто считается свежим.
  • Если в настройках доступа запрещено скачивание, снимите запрет, иначе файл не получить.

Облако Mail.ru (экспериментально)

  • Нажмите «Поделиться», включите доступ по ссылке и скопируйте ссылку вида https://cloud.mail.ru/public/….
  • У Облака Mail.ru нет официального способа скачивать файлы по ссылке программой. Если сервис изменит страницу, загрузка остановится с понятной ошибкой. Тогда подключите Яндекс Диск, Google Диск или отправку прайса почтой.

Dropbox

  • Нажмите «Поделиться», затем «Копировать ссылку». Ссылка вида https://www.dropbox.com/scl/fi/…?rlkey=…&dl=0 подходит как есть.
  • Ссылка на папку не подходит. Из России Dropbox открывается не всегда: если скачивание не работает, выберите другой сервис.

Если не скачивается

КодЧто случилосьЧто сделать
CLOUD_NOT_PUBLICСервис вернул страницу входа вместо файлаОткройте доступ «Все, у кого есть ссылка» и нажмите «Проверить ссылку»
CLOUD_FOLDERЭто ссылка на папкуОткройте файл прайса и скопируйте ссылку на него
CLOUD_NOT_FOUNDФайл удалён или доступ закрытПроверьте, что файл на месте и доступ по ссылке открыт
CLOUD_DOWNLOAD_FORBIDDENВладелец запретил скачивание на Яндекс ДискеСнимите запрет в настройках доступа
CLOUD_PROVIDER_CHANGEDОблако Mail.ru изменило страницуПодключите другой сервис или отправку прайса почтой
CLOUD_PROVIDER_DISABLEDСервис на площадке временно не принимаетсяЗагрузите прайс файлом, по почте или ссылкой другого сервиса

Облачная ссылка работает как обычная ссылка на прайс: частота не чаще, чем разрешено на площадке, три неудачи подряд дают уведомление в кабинет, десять выключают источник. Сервис, который временно ограничил запросы, повторяется по расписанию.

Прайс магазина на ABCP

Магазину на платформе ABCP не нужны API и программист: достаточно прямой ссылки на файл экспорта прайса. Дальше К-247 забирает его по расписанию, как обычную ссылку на прайс.

  1. В панели управления ABCP настройте экспорт прайс-листа по расписанию в файл CSV или XLSX и скопируйте прямую ссылку на файл. Если не нашли настройку, спросите поддержку ABCP о «прямой ссылке на экспорт прайса».
  2. В кабинете К-247 откройте «Прайс и подключение», вкладку «Источники» и нажмите «Добавить ссылку на прайс». Вставьте ссылку: для адреса на abcp.ru окно покажет подсказку про экспорт ABCP.
  3. Выберите частоту не чаще, чем ABCP обновляет файл. Если в экспорте весь склад, выберите режим «Файл — это весь мой склад». Укажите склад и нажмите «Сохранить ссылку».
  4. Нажмите «Проверить ссылку»: карточка покажет колонки файла. Итог первых загрузок смотрите во вкладке «Прайсы».

Колонки

Колонки находятся по заголовкам в первой строке файла. Подходят привычные названия: «Производитель» или «Бренд», «Номер» или «Артикул», «Наименование», «Цена», «Наличие» или «Количество», «Срок», «Кратность». Если в вашем экспорте заголовки другие, переименуйте колонки в настройке экспорта или пришлите образец файла менеджеру К-247.

Почему ссылка, а не API ABCP

  • API ABCP для клиента магазина ищет по отдельным артикулам и не отдаёт весь склад, а лимиты запросов быстро заканчиваются. Файл экспорта содержит весь прайс и лимиты не расходует.
  • Доступ к панели управления ABCP давать не нужно: по ссылке экспорта доступен только файл прайса.
  • Живая проценка и передача заказов в ABCP в этот способ не входят.

Фид Яндекс Маркета (YML) и свой XML

Если у сайта магазина уже есть фид для Маркета, его можно подключить как прайс. Колонки сопоставлять не нужно: К-247 узнаёт YML сам.

Фид YML по ссылке

  1. Найдите в панели сайта адрес фида для Яндекс Маркета. Обычно это ссылка вида https://shop.ru/market.yml или https://shop.ru/yandex.xml. Фид, сжатый в .gz, тоже подходит.
  2. В кабинете откройте «Прайс и подключение» → «Источники» и нажмите «Добавить ссылку на прайс». Вставьте адрес фида.
  3. Выберите, как часто забирать фид, режим и склад. Нажмите «Проверить ссылку»: в предпросмотре будут товары из фида.
  4. Сохраните источник. Дальше прайс обновляется сам.

Файл фида можно и прислать письмом, и загрузить в кабинете: «Прайсы» → «Загрузить прайс». Мастер покажет «Узнали фид Яндекс Маркета: колонки сопоставлены сами».

Что берётся из фида

Поле К-247Элемент offer в YML
Брендvendor
АртикулvendorCode. Если номеров несколько через запятую, берётся первый. offer id — это код товара в магазине, а не номер производителя, поэтому без vendorCode строка не загружается. Если у вас id совпадает с артикулом, включите в мастере «Брать артикул из id товара, если в фиде нет vendorCode»
Наименованиеname, а если его нет — typePrefix, vendor и model
Ценаprice в рублях: currencyId — RUR или RUB. Больше двух знаков после точки — округление до копеек
Остатокcount. Без count: available="false" или disabled — 0; товар в наличии — по умолчанию 10 шт. и предупреждение в отчёте
Код товараoffer id, если он не длиннее 64 символов
Группаназвание категории из categories по categoryId
Оригинальный номерparam с названием «OEM», «Оригинальный номер» или «Кросс»
Применимость, состояниеparam «Применимость» и «Состояние» (например, «б/у»)
Фотопервый picture с адресом https

Свой XML

Выгрузку своей программы в XML загрузите в кабинете: «Прайсы» → «Загрузить прайс». Мастер найдёт в файле повторяющиеся записи и покажет их в поле «Записи товаров в файле», например «Позиция — 12 430 шт.». Если записей несколько видов, выберите нужный.

Дальше сопоставление такое же, как у колонок таблицы, только вместо колонок — поля записи. Поле записывается путём от элемента записи:

  • Номер — вложенный элемент, Остатки/Склад — элемент глубже;
  • @Бренд — атрибут записи, Цена/@Значение — атрибут вложенного элемента;
  • param[@name='Производитель'] — элемент с нужным значением атрибута;
  • sum(Остатки/Склад/@Количество) — сумма остатков по складам. Если в записи несколько складов, мастер сам предложит такое поле.

Мастер подберёт бренд, артикул, наименование, цену и остаток по названиям полей. Оставьте флажок «Запомнить сопоставление для следующих загрузок», и следующий такой файл загрузится с тем же сопоставлением. Через API v1 путь записей и поля передаются в профиле: {"xml": {"record": "Прайс/Позиция", "fields": ["@Бренд", "Номер", "Наименование", "Цена", "sum(Остатки/Склад/@Количество)"]}, "columns": {"brand": 1, "article": 2, "name": 3, "price": 4, "qty": 5}}.

Что важно знать

  • Кодировка — UTF-8 или Windows-1251 из строки <?xml … encoding="…"?>. Другие кодировки, например KOI8-R, не читаются: сохраните фид в UTF-8.
  • Строка с ценой не в рублях пропускается с ошибкой «Цена не в рублях», остальные товары загружаются.
  • По ссылке и почтой автоматически загружается фид YML. Свой XML по ссылке или почте сначала сопоставьте: во вкладке «Источники» нажмите в карточке источника «Настроить поля XML». К-247 скачает файл по ссылке (у почты возьмёт файл последнего письма, а если писем ещё не было, попросит образец файла) и откроет мастер. После «Проверить и загрузить» сопоставление запомнится, и следующие скачивания и письма загрузятся сами. Без сопоставления в истории будет ошибка «Откройте мастер загрузки и укажите, где в XML записи товаров».
  • Файлы с объявлениями сущностей в DOCTYPE (<!ENTITY) не загружаются из соображений безопасности. Обычная строка <!DOCTYPE yml_catalog SYSTEM "shops.dtd"> не мешает, внешние файлы К-247 не скачивает.
  • Вложенность — до 64 уровней, текст одного элемента — до 1 МБ. Длинные описания, которые не загружаются в К-247, обрезаются и не мешают.
  • Сжатый фид .gz распаковывается на лету. Распакованный файл — не больше предела ZIP-архива, подозрительно сильное сжатие отклоняется.
  • Если файл повреждён, в ошибке будет номер строки, например «Файл XML повреждён: строка 6».

Прайс из 1С: обмен с сайтом

Для магазина, который ведёт товары, цены и остатки в 1С: Управление торговлей, УНФ, Комплексная автоматизация, Альфа-Авто. 1С сама отправляет товары, цены и остатки на К-247 по расписанию. Используется стандартный «Обмен с сайтом» (CommerceML 2), программист не нужен.

  1. В кабинете откройте «Прайс и подключение» → «Источники» и нажмите «Подключить 1С (обмен с сайтом)». Нажмите «Сохранить и выпустить пароль».
  2. Окно покажет адрес сайта для 1С https://k-247.ru/api/cml/exchange, имя пользователя вида cml-7h3k9q2m и пароль. Пароль показываем один раз, поэтому сразу перенесите его в 1С. Для этого есть кнопки «Скопировать адрес», «Скопировать имя» и «Скопировать пароль».
  3. В 1С создайте узел обмена с сайтом. В УТ 11 и КА 2 он обычно находится в разделе «Администрирование» → «Интеграция с другими программами» → «Обмен с сайтами», в УНФ — в разделе «Настройки» → «Интеграции». Путь зависит от версии. Укажите адрес, имя пользователя и пароль.
  4. В настройках узла включите выгрузку товаров, цен (вид цен) и остатков (склады). Включите сжатие файлов (zip) и выключите выгрузку картинок: картинки К-247 не принимает. Задайте расписание, например раз в час.
  5. В 1С нажмите «Проверить соединение», затем «Выполнить обмен». Через одну-две минуты в карточке источника появится строка «Последний обмен: 16.09.2026 14:05, принято 12 430 строк». Ошибки строк показаны во вкладке «Прайсы».
  6. Нажмите «Настроить цены и склады». Выберите вид цен, укажите, куда класть остатки каждого склада 1С и откуда брать бренд и артикул. Новые настройки применятся со следующего обмена. Чтобы применить их сразу, нажмите «Загрузить последний обмен заново».

Что загружается

  • Файлы обмена: import.xml с товарами и offers.xml с ценами и остатками (схемы 2.03–2.08). В схемах 2.09–2.10 цены и остатки приходят в prices.xml и rests.xml. Имена с суффиксами, например import0_1.xml или offers___….xml, тоже подходят.
  • Бренд ищется по порядку: свойство, выбранное в «Настроить цены и склады», затем «Изготовитель», затем свойство или реквизит «Производитель», «Бренд», «Марка», «Изготовитель», «Торговая марка». Если бренд не найден, строка не загрузится, а в отчёте загрузки будет ошибка «Не указан бренд».
  • Артикул берётся из поля «Артикул». Если оно пустое, можно брать «Код» товара.
  • Цена — выбранный вид цен. По умолчанию К-247 берёт первый вид цен со словом «розн» в названии, а если такого нет — первый. Принимаются только цены в рублях. Цена округляется до копейки.
  • Остатки всех складов 1С по умолчанию складываются на склад по умолчанию. Можно направить каждый склад 1С на свой склад К-247 или не загружать его. Отрицательный остаток считается нулём, дробный округляется вниз.
  • Характеристики товара (модификации) загружаются отдельными строками, к наименованию добавляются значения характеристик. Характеристики с одинаковым артикулом станут одной позицией: загрузится первая. Поэтому задайте модификациям разные артикулы.
  • Товары, помеченные в 1С на удаление, не загружаются.

Полная выгрузка и только изменения

  • Полная выгрузка в режиме «1С — это весь мой склад» обнуляет позиции, которых в ней нет. Если выгрузка обнуляет или меняет слишком много сразу, она ждёт вашего решения во вкладке «Прайсы».
  • Если 1С прислала только изменения, загрузка идёт в режиме «только изменения». Описания товаров К-247 берёт из последней полной выгрузки. Поэтому первый обмен должен быть полным, иначе появится ошибка «1С прислала только цены и остатки, а описаний товаров ещё не было».
  • Если в пакете изменений у товара нет остатка, строка пропускается, чтобы остаток не обнулился.
  • Если 1С повторно прислала тот же обмен, новая загрузка в истории не появится, а прайс будет считаться свежим.

Что важно знать

  • Обмен идёт только по HTTPS. Пароль у нас хранится в виде хэша, поэтому посмотреть его нельзя. Можно только нажать «Выпустить новый пароль»: старый пароль сразу перестанет работать, и новый нужно будет указать в 1С.
  • После 5 неверных паролей за 15 минут вход с этого адреса блокируется на 15 минут. Неверное имя пользователя и неверный пароль дают одинаковый ответ.
  • Один запрос по умолчанию принимает файл до 50 МБ: большие файлы 1С сама делит на части. Весь обмен ограничен 300 МБ и 500 000 товаров. Если товаров больше, выгружайте их частями или файлом CSV.
  • Если 1С не сообщила о конце обмена, К-247 загрузит полученные файлы через минуту тишины.
  • Заказы К-247 в 1С пока не выгружаются: на запрос заказов 1С получает пустой список.
  • Тексты ошибок видны в протоколе обмена 1С: «Неверное имя пользователя или пароль обмена К-247», «Слишком много попыток входа, повторите через 15 минут», «Источник выключен в кабинете К-247», «Сеанс обмена истёк — запустите обмен заново», «Файл больше 50 МБ — уменьшите размер файла в настройках обмена 1С».
  • Выгрузку из 1С можно загрузить и вручную. Сохраните import.xml и offers.xml в один ZIP-архив и загрузите его кнопкой «Загрузить прайс».

Прайс с FTP

Способ для прайса, который ваша программа или дистрибьютор выкладывает на FTP-сервер. К-247 сам забирает свежий файл по расписанию.

  1. В кабинете откройте «Прайс и подключение» → «Источники» и нажмите «Подключить FTP».
  2. Укажите сервер, например ftp.shop.ru, шифрование, порт, логин и пароль. Для анонимного входа оставьте логин пустым.
  3. Укажите папку, например /export, и имя файла или маску. По маске price_*.xlsx К-247 возьмёт самый новый подходящий файл.
  4. Выберите, как часто скачивать файл (не чаще раза в час), режим и склад. Нажмите «Проверить подключение». Появится до 20 самых новых файлов папки и строка, какой файл будет взят, например «Подключение работает. В папке 3 файла, возьмём price_0916.xlsx (1,0 МБ, 16.09 06:00)».
  5. Нажмите «Сохранить FTP-источник». Первое скачивание пройдёт в ближайшую минуту, дальше прайс обновляется сам. Итог каждой загрузки виден во вкладке «Прайсы».

Шифрование

  • FTPS (рекомендуем) — обычный порт 21, шифрование включается командой AUTH TLS. Логин, пароль и сам файл передаются зашифрованными.
  • FTPS, порт 990 — сервер сразу ждёт шифрованное соединение (неявный FTPS).
  • Без шифрования — пароль передаётся открыто. Используйте этот вариант, только если сервер не умеет FTPS. Менеджер площадки может запретить такой режим.
  • Сертификат сервера проверяется. Если у сервера самоподписанный сертификат, проверка подключения покажет его отпечаток SHA-256. Сверьте отпечаток с данными хостинга и вставьте его в «Дополнительно» → «Отпечаток сертификата SHA-256».

Что важно знать

  • Файл тот же: если имя, размер и дата файла не изменились, К-247 его не скачивает. Прайс при этом считается свежим. Если файл перезаписали тем же содержимым, новой загрузки в истории тоже не будет.
  • Форматы и колонки те же, что у файла прайса. Предел размера тоже тот же. Файл больше предела не скачивается, в кабинете появится понятная ошибка.
  • Порты: 21, 990, 2121 и 2221. Работает только пассивный режим. Соединение для файла идёт на тот же адрес, что и управляющее, даже если сервер за NAT сообщает внутренний адрес.
  • Если на FTP-сервере включён белый список адресов, добавьте в него IP сервера К-247. Адрес показан в итоге проверки подключения. Если адрес там не показан, его подскажет менеджер площадки.
  • Если имена файлов на сервере в кодировке Windows-1251 (старые серверы Windows), включите «Имена файлов в кодировке Windows-1251».
  • Три неудачи подряд — уведомление в кабинет, десять — источник выключается. Ошибка «FTP-сервер не принял логин или пароль» означает ответ 530.
  • Пароль хранится зашифрованным и не показывается. Если вы меняете сервер или логин, пароль нужно ввести заново.
  • SFTP пока не поддерживаем. Выгружайте прайс на FTPS, по ссылке HTTPS или почтой.

Прайс из МойСклад

Для магазина, который ведёт товары, цены и остатки в МойСклад. К-247 сам забирает ассортимент по токену и обновляет прайс по расписанию: выгружать файлы не нужно.

  1. В МойСклад заведите отдельного пользователя с правом только на просмотр товаров и остатков. Токен действует с правами пользователя, поэтому К-247 не сможет ничего изменить в вашем учёте.
  2. Войдите в МойСклад под этим пользователем и выпустите токен: «Настройки», затем «Токены», затем «Добавить токен». Скопируйте токен.
  3. В кабинете К-247 откройте «Прайс и подключение», вкладку «Источники», и нажмите «Подключить МойСклад». Вставьте токен и нажмите «Проверить подключение».
  4. Кабинет покажет виды цен, склады и доп. поля из МойСклад. Выберите цену продажи, доп. поле с брендом, откуда брать артикул («Артикул» или «Код»), какой остаток брать и на какие склады К-247 класть остатки складов МойСклад. Укажите, как часто обновлять: от раза в час до раза в двое суток.
  5. Нажмите «Сохранить и загрузить прайс». Итог появится во вкладке «Прайсы», а в карточке источника — число товаров и время обновления.

Что загружается

  • Товары не из архива, у которых заполнен артикул (или код) и есть цена выбранного вида. Модификации, комплекты и услуги в этой версии не загружаются. Сколько товаров пропущено без цены и без артикула, показывает карточка источника.
  • Бренд берётся из выбранного доп. поля: строки или элемента справочника. Если поле с брендом не выбрано или у товара оно пустое, строка не загрузится, а в отчёте загрузки будет ошибка «Не указан бренд». Новые бренды, как и в файле прайса, проверяет менеджер.
  • Цена в МойСклад хранится в копейках, на площадку она приходит в рублях с копейками без округления.
  • Остаток по умолчанию — остаток минус резерв, но не меньше нуля. Можно выбрать весь остаток без вычета резерва. Товар без остатка приходит с остатком 0.
  • Если склады не сопоставлены, остатки всех складов МойСклад складываются и идут на склад по умолчанию. Если сопоставлены, каждый склад МойСклад идёт на свой склад К-247. Несколько складов МойСклад на один склад К-247 складываются, несопоставленные склады не загружаются.
  • Наименование, группа товара и код товара в МойСклад сохраняются вместе с позицией.

Что важно знать

  • Токен хранится зашифрованным и в кабинете больше не показывается. Чтобы сменить токен, откройте «Изменить настройки» и вставьте новый.
  • К-247 обращается только к api.moysklad.ru и соблюдает ограничения МойСклад: запросы идут по очереди, не больше 9 единиц за 3 секунды, отчёт остатков весит 5 единиц. Поэтому 10 000 товаров загружаются примерно за полминуты, 300 000 — до 20 минут. За одну загрузку принимается до 300 000 товаров.
  • Если в МойСклад ничего не изменилось, прайс считается свежим и новая загрузка в истории не появляется.
  • «МойСклад не принял токен» — токен отозван или вставлен с ошибкой: выпустите новый. «Нет права просматривать товары или остатки» — выдайте пользователю токена право на просмотр. Если МойСклад ограничил частоту запросов или не ответил, загрузка повторится по расписанию.
  • Три неудачи подряд — уведомление в кабинет, десять — источник выключается. После исправления включите источник снова.
  • В режиме «весь склад» позиции, которых больше нет в МойСклад, обнуляются. Если загрузка обнуляет или меняет слишком много сразу, она ждёт вашего решения во вкладке «Прайсы».

Ключи API: тестовый и боевой

Ключ выпускает любой сотрудник компании в кабинете: «Прайс и подключение» → «Для программиста» → «Ключи API». Ключ показывается один раз — сохраните его сразу. О выпуске и отзыве ключа узнают все сотрудники компании.

Тестовый k247_test_…Боевой k247_live_…
Кто может выпуститьЛюбая компания, сразуКомпания с проверенными реквизитами, после принятия «Условий API»
Прайсы и offers/batchРазбор и отчёт без публикации (dry_run)Публикация на витрине
ЗаказыТолько тестовые заказы песочницыНастоящие заказы
ЛимитыПятая часть боевыхПолные

До 5 активных ключей каждого вида на компанию. Права (scopes): offers — прайсы, предложения, склады; orders — заказы; finance — зарезервировано. Для ключа можно задать список IP-адресов. Выпуск первого боевого ключа включает подтверждение заказов. Отзыв действует сразу.

Авторизация

Базовый адрес https://k-247.ru/api/v1, только HTTPS, JSON в UTF-8. Ключ передаётся заголовком — в адресе и теле он не принимается. Cookie браузера API не читает.

curl -s https://k-247.ru/api/v1/ping -H "Authorization: Bearer $K247_TOKEN"

Имена полей — snake_case, идентификаторы — строки, время — ISO 8601 UTC, деньги — целые копейки (price_kop). Неизвестные поля в ответах нужно игнорировать: новые необязательные поля добавляются без смены версии. Каждый ответ несёт заголовок X-Request-Id — назовите его, если пишете в поддержку.

Примеры curl на странице и в OpenAPI написаны для bash: Linux, macOS, Git Bash. В Windows curl получает русские буквы из -d '…' в кодировке cp1251, и площадка отвечает 400 BAD_REQUEST «Тело запроса не в кодировке UTF-8». Тело с русскими буквами сохраните в файл UTF-8 и отправьте curl --data-binary @body.json. Этот ответ 400 за Idempotency-Key не закрепляется: тело файлом можно отправить с тем же ключом.

Спецификация OpenAPI 3.0.3: /api/openapi.json. Импортируйте её в Swagger Editor или Postman.

Ошибки

Все ошибки — один формат. message — по-русски для человека, code стабилен, details необязателен.

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "В 2 строках есть ошибки",
    "request_id": "req_01J8Z5Q0M2AB",
    "details": [
      {
        "row": 3,
        "field": "price_kop",
        "code": "PRICE_INVALID",
        "message": "Цена — целое число копеек больше 0"
      }
    ]
  }
}
HTTPcodeКогда
400BAD_REQUESTНеразборчивый JSON или неверный параметр пути
400VALIDATION_FAILEDПоля не прошли проверку; построчные ошибки — в details
400IDEMPOTENCY_KEY_REQUIREDМетод требует заголовок Idempotency-Key
401UNAUTHORIZEDНет заголовка Authorization или ключ неверный
401KEY_REVOKEDКлюч отозван в кабинете
403FORBIDDEN_SCOPEУ ключа нет нужного права (offers или orders)
403FORBIDDEN_ROLEМетод продавца вызван ключом покупателя и наоборот
403FORBIDDEN_ENVДействие недоступно окружению ключа: склады test-ключом, песочница live-ключом
403COMPANY_NOT_VERIFIEDБоевой ключ компании, реквизиты которой ещё не проверены
403IP_NOT_ALLOWEDАдрес запроса не входит в список адресов ключа
404NOT_FOUNDОбъекта нет или он чужой — чужое не отличается от несуществующего
405METHOD_NOT_ALLOWEDМетод HTTP не поддерживается для этого адреса
409IDEMPOTENCY_CONFLICTТот же Idempotency-Key, но другое тело запроса
409IDEMPOTENCY_IN_PROGRESSПервый запрос с этим ключом ещё выполняется; повторите через Retry-After
409INVALID_STATEПереход статуса недопустим или повтор решения с другими параметрами
409AMBIGUOUS_OFFERПозиция с этим external_id есть на нескольких складах — укажите warehouse
409IMPORT_SUPERSEDEDОстановленная загрузка устарела: после неё пришёл новый прайс
409OFFER_CHANGEDЦена или остаток предложения изменились; актуальные данные — в details
409OFFER_KEY_EXPIREDoffer_key старше срока действия; повторите поиск
413PAYLOAD_TOO_LARGEТело запроса больше лимита метода
415UNSUPPORTED_MEDIA_TYPEНеподдерживаемый Content-Type
422OUT_OF_STOCKНет нужного количества
422INSUFFICIENT_FUNDSНедостаточно средств на балансе
422OWN_OFFERНельзя заказать собственную позицию
422BUSINESS_RULEБизнес-отказ; текст причины — в message
429RATE_LIMITEDПревышен лимит запросов; ждать Retry-After секунд
429IMPORT_QUEUE_FULLВ очереди уже слишком много загрузок компании
500INTERNALСбой на стороне площадки; сообщите request_id
503UNAVAILABLEСервис обновляется или база занята; повторите через Retry-After
409LIMIT_EXCEEDEDДостигнут предел: например, 5 вебхуков компании в одном окружении
401KEY_OWNER_INACTIVEСотрудник, выпустивший ключ, отключён — выпустите новый ключ в кабинете
403LEGAL_NEEDSАдминистратор компании не принял документы площадки: новые КП, заказы и загрузка прайса боевым ключом недоступны
409REQUEST_CLOSEDЗаявка не активна или срок истёк
409PROPOSAL_NOT_ACTIVEКП снято, заменено, отклонено или закрыто
409PROPOSAL_LIMITПо заявке собран лимит КП, у продавца действующего КП нет
409PROPOSAL_REPLACE_LIMITЗамена КП через API чаще раза в 5 минут или больше 10 раз по заявке; когда можно — в details
409ALREADY_BOUGHTПозиция заявки уже выкуплена
409NEED_EXCEEDEDКоличество больше оставшейся потребности позиции
409EXCHANGE_ORDERЗаказ из КП биржи не подтверждается и не отклоняется — только отгрузка (ship)
422CONTACTS_FORBIDDENВ тексте заявки или КП найдены контакты: телефон, почта, ссылка, логин; в КП — ещё реквизиты, название, адрес или сотрудники продавца

Коды строк

В results[].code пакета предложений, в ошибках загрузки прайса и в details[].code.

codeКогда
NOT_FOUNDСтрока только с external_id, а такой позиции нет
KEY_CONFLICTПосле смены бренда, артикула или склада позиция совпала бы с другой существующей
STALE_UPDATED_ATupdated_at строки старше, чем у позиции: строка пропущена
BRAND_EMPTYНе указан бренд
BRAND_UNKNOWNБренда нет в справочнике: строка появится после проверки менеджером
ARTICLE_EMPTYНе указан артикул
NAME_EMPTYНе указано наименование
PRICE_INVALIDЦена не число или меньше или равна нулю
PRICE_BELOW_MINЦена меньше минимальной (1 ₽)
PRICE_TOO_BIGЦена больше допустимой
QTY_INVALIDОстаток не целое число от 0
TERM_UNPARSEDСрок не разобран (предупреждение, строка принята)
WAREHOUSE_UNKNOWNСклад с таким кодом не найден
DUPLICATE_ROWТа же позиция уже встретилась выше в файле или пакете
DUPLICATE_EXTERNAL_IDТот же external_id на том же складе уже встретился в пакете
ROW_TOO_LONGСтрока файла длиннее допустимого
VALUE_TOO_LONGЗначение поля длиннее допустимого
PHOTO_URL_INVALIDСсылка на фото не https (предупреждение)
MULTIPLICITY_VIOLATIONКоличество не кратно кратности предложения
MIN_ORDER_VIOLATIONКоличество меньше минимальной партии
QTY_TOO_BIGКоличество в строке больше допустимого
SUM_TOO_BIGСумма строки слишком большая
ROW_INVALIDСтрока пакета не объект JSON
UPDATED_AT_INVALIDupdated_at строки не время ISO 8601
ITEM_UNKNOWNСтрока КП не относится к позиции этой заявки
VAT_INVALIDНДС строки КП не из списка: vat22, vat5, no_vat
EXTERNAL_ID_INVALIDКод позиции в программе продавца — латинские буквы, цифры и . _ / - (до 64 символов)
CONTACTS_FOUNDВ поле найден контакт (вид — в kind): телефон, почта, ссылка, логин; в КП — реквизиты (requisites), название или адрес (company), сотрудник (person), расчёты мимо площадки (bypass)
WARRANTY_INVALIDГарантия: срок не из 1–120 месяцев; «гарантия изготовителя» без срока (offers/batch, строка КП) — строка не записана
WARRANTY_IGNOREDГарантия в файле прайса не разобрана («есть», «нет», «0», «14 дней») — предупреждение: строка принята без срока гарантии

Лимиты

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

КлассМетодыЗапасВ минуту
defaultВсе остальные, в том числе sandbox/orders, sandbox/exchange/requests и buyer/orders тестовым ключом60120
searchbuyer/search, buyer/quotes, dictionary/brands3060
bulkseller/pricelists, seller/offers/batch, buyer/orders боевым ключом510

Заказ покупателя тестовым ключом считается по классу default: у тестового ключа ведро bulk — на 1 запрос, и второй тестовый заказ подряд получал бы 429. Боевой ключ оформляет заказы в пределах bulk.

Каждый ответ с действующим ключом, в том числе 403, 404 и 405: X-RateLimit-Limit (в минуту), X-RateLimit-Remaining, X-RateLimit-Reset (секунд до полного ведра). При 429 — Retry-After. Пакет предложений — до 1 000 строк и 5 МБ, прочие JSON-запросы — до 2 МБ.

Идемпотентность

Заголовок Idempotency-Key (1–128 символов A-Z a-z 0-9 _ - : .) защищает от дублей при сетевых сбоях. Без него методы из первого списка отвечают 400 IDEMPOTENCY_KEY_REQUIRED.

Idempotency-KeyМетоды
ОбязателенPOST seller/pricelists, POST seller/offers/batch, POST seller/exchange/requests/{id}/proposals, POST buyer/exchange/requests, POST buyer/exchange/proposals/{id}/accept, POST buyer/orders, POST sandbox/orders, POST sandbox/exchange/requests
РекомендуетсяPOST seller/imports/{id}/approve и reject, POST seller/orders/{id}/confirm, reject и ship, POST seller/exchange/proposals/{id}/withdraw, POST buyer/exchange/requests/{id}/update и close, POST buyer/exchange/proposals/{id}/reject, POST webhooks, POST webhooks/{id}/test
Не действуетGET, PUT seller/warehouses, DELETE: заголовок игнорируется
  • Повтор с тем же ключом и телом возвращает сохранённый ответ с заголовком Idempotent-Replayed: true.
  • Тот же ключ с другим телом — 409 IDEMPOTENCY_CONFLICT; первый запрос ещё выполняется — 409 IDEMPOTENCY_IN_PROGRESS.
  • За ключом закрепляется и ответ с ошибкой, который вернул метод (400 VALIDATION_FAILED, 404, 409, 422): повтор с тем же ключом вернёт ту же ошибку, а исправленное тело с тем же ключом — 409 IDEMPOTENCY_CONFLICT. Исправили запрос — возьмите новый ключ. Ответы 429 и 5xx не закрепляются: повтор с тем же ключом выполнится заново.
  • Ключ хранится 24 часа, у тестовых и боевых ключей API пространства независимы. Позже тот же ключ — новый запрос: создаётся новая заявка, КП или заказ, прежний объект не возвращается. Постоянные ключи из примеров («zayavka-4471») на следующий день создадут новый объект — берите ключ от своего номера документа и попытки.
  • Решения по заказу идемпотентны и без заголовка: повтор confirm с теми же параметрами отвечает 200 и Idempotent-Replayed: state, с другими — 409 INVALID_STATE.
  • Для остатков дополнительно передавайте updated_at строки: старая строка не перетрёт более новую.

Заказы продавца

Заказ подключённого магазина приходит в статусе awaiting_confirmation. Каждую строку нужно подтвердить или отклонить за 2 ч. Строка без ответа через 4 ч отменяется автоматически и засчитывается как отказ. Время считается по рабочим часам магазина: по умолчанию пн–пт 09:00–19:00 по Москве, своё расписание задаётся в кабинете.

Цикл обмена

  1. Раз в 1–5 минут: GET /seller/orders?since_seq=N. Обработайте элементы и сохраните change_seq последнего — это N следующего опроса. Пока has_more, запрашивайте дальше с cursor.
  2. Строки в awaiting_confirmation: POST /seller/orders/{id}/confirm (можно меньшее количество и свой срок) или reject с причиной.
  3. Отгрузили — POST /seller/orders/{id}/ship с трек-номером и номером УПД. Когда отгружены все строки, заказ закрывается в расчётах.

Опрос по since (время) удобен для отладки, но для синхронизации не гарантирует полноты — используйте since_seq. Покупатель обезличен: код, город и адрес доставки, без названия и телефона.

Показать пример клиента на Node.js

Файл docs/examples/seller-sync.js из поставки площадки. Без зависимостей, Node.js 18 и новее: K247_TOKEN=k247_test_… node seller-sync.js --base https://k-247.ru.

#!/usr/bin/env node
// К-247 API v1 — пример клиента продавца (спец. G.4, F.8, F.12). Без зависимостей, Node.js 18+ (встроенный fetch).
// Написан только по docs/openapi-v1.json и странице https://k-247.ru/api.html.
//
// Что делает:
//   1. GET  /ping                         — ключ, окружение (test|live), права;
//   2. POST /seller/offers/batch          — остатки и цены пакетом (test-ключ: разбор без записи, dry_run);
//   3. POST /sandbox/orders               — тестовый заказ на свою позицию (только test-ключ). Нужна хотя бы одна
//                                           опубликованная позиция: test-ключ позиций не создаёт (шаг 2 — dry_run),
//                                           поэтому сначала загрузите прайс в кабинете (файл, ссылка, почта) или боевым ключом;
//   4. GET  /seller/orders?since_seq=     — опрос изменений заказов без потерь (сохраняем change_seq последнего);
//   5. POST /seller/orders/{id}/confirm   — подтвердить строки, ожидающие подтверждения;
//   6. POST /seller/orders/{id}/ship      — отгрузить подтверждённые строки.
//
// Запуск:
//   K247_TOKEN=k247_test_... node seller-sync.js --base http://localhost:3247
//   K247_TOKEN=k247_live_... node seller-sync.js --base https://k-247.ru --offers offers.json --state sync-state.json --loop 120
// Параметры:
//   --base <url>       адрес площадки (без /api/v1), по умолчанию https://k-247.ru
//   --offers <file>    JSON-массив предложений для offers/batch (формат OfferInput). Без файла test-ключ шлёт два демо-предложения,
//                      live-ключ пакет не шлёт — чтобы пример случайно не опубликовал демо-остатки.
//   --state <file>     где хранить since_seq между запусками (по умолчанию — только в памяти)
//   --loop <сек>       опрашивать заказы бесконечно с этим интервалом (по умолчанию — один проход)
//   --no-ship          только подтверждать, не отгружать
// Код выхода: 0 — успех, 1 — ошибка (текст, code и request_id — в stderr).
'use strict';

const fs = require('fs');
const crypto = require('crypto');

const args = process.argv.slice(2);
const opt = (name, def) => { const i = args.indexOf(name); return i >= 0 && args[i + 1] !== undefined ? args[i + 1] : def; };
const flag = (name) => args.includes(name);

const BASE = String(opt('--base', 'https://k-247.ru')).replace(/\/+$/, '') + '/api/v1';
const TOKEN = process.env.K247_TOKEN || '';
const STATE_FILE = opt('--state', null);
const LOOP_SEC = Number(opt('--loop', 0)) || 0;
const OFFERS_FILE = opt('--offers', null);
const SHIP = !flag('--no-ship');

if (!/^k247_(test|live)_[a-z0-9]{8}_[A-Za-z0-9_-]{43}$/.test(TOKEN)) {
  console.error('Задайте ключ в переменной окружения K247_TOKEN (k247_test_… или k247_live_…). Ключ выпускается в кабинете: «Прайс и подключение» → «Для программиста».');
  process.exit(1);
}

const log = (...a) => console.log(new Date().toISOString().slice(11, 19), ...a);
const sleep = (ms) => new Promise((ok) => setTimeout(ok, ms));

class ApiError extends Error {
  constructor(status, body, requestId) {
    const e = (body && body.error) || {};
    super((e.message || 'HTTP ' + status) + ' [' + (e.code || status) + ', request_id ' + (e.request_id || requestId || '—') + ']');
    this.status = status; this.code = e.code; this.details = e.details; this.requestId = e.request_id || requestId;
  }
}

/**
 * Запрос к API v1. Повторяет 429 и 503 по Retry-After и сетевые сбои (до 5 попыток) с тем же Idempotency-Key —
 * повтор не создаст дубль. Ошибки 4xx возвращаются исключением ApiError.
 */
async function api(method, path, { body, idem } = {}) {
  const headers = { Authorization: 'Bearer ' + TOKEN, Accept: 'application/json' };
  if (body !== undefined) headers['Content-Type'] = 'application/json';
  if (idem) headers['Idempotency-Key'] = idem;
  for (let attempt = 1; ; attempt++) {
    let res;
    try {
      res = await fetch(BASE + path, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) });
    } catch (e) {
      if (attempt >= 5) throw e;
      log('сеть недоступна (' + e.message + '), повтор через ' + attempt * 2 + ' с');
      await sleep(attempt * 2000);
      continue;
    }
    const requestId = res.headers.get('x-request-id');
    const text = await res.text();
    let data = null;
    try { data = text ? JSON.parse(text) : null; } catch (e) { data = null; }
    if ((res.status === 429 || res.status === 503 || (res.status === 409 && data && data.error && data.error.code === 'IDEMPOTENCY_IN_PROGRESS')) && attempt < 5) {
      const wait = Math.min(60, Number(res.headers.get('retry-after')) || attempt);
      log(method + ' ' + path + ' → ' + res.status + ', ждём ' + wait + ' с');
      await sleep(wait * 1000);
      continue;
    }
    if (res.status >= 400) throw new ApiError(res.status, data, requestId);
    return { status: res.status, data, replayed: res.headers.get('idempotent-replayed') };
  }
}

// ---------- состояние опроса ----------
function loadState() {
  if (!STATE_FILE || !fs.existsSync(STATE_FILE)) return { since_seq: 0 };
  try { return Object.assign({ since_seq: 0 }, JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'))); } catch (e) { return { since_seq: 0 }; }
}
function saveState(state) {
  if (!STATE_FILE) return;
  const tmp = STATE_FILE + '.tmp';
  fs.writeFileSync(tmp, JSON.stringify(state));
  fs.renameSync(tmp, STATE_FILE);   // атомарно: после сбоя остаётся прежний since_seq, заказы придут повторно, но не потеряются
}

// ---------- шаги ----------
async function pushOffers(env) {
  let offers;
  if (OFFERS_FILE) offers = JSON.parse(fs.readFileSync(OFFERS_FILE, 'utf8'));
  else if (env === 'test') {
    const now = new Date().toISOString();
    offers = [
      { brand: 'FEBI BILSTEIN', article: '01-089', name: 'Термостат', price_kop: 123450, qty: 12, term_days_min: 1, term_days_max: 3, external_id: 'A-778', updated_at: now },
      { brand: 'BOSCH', article: '0986424815', name: 'Колодки тормозные передние', price_kop: 389000, qty: 4, term_days_min: 0, term_days_max: 0, external_id: 'A-779', updated_at: now }
    ];
  } else {
    log('offers/batch пропущен: для боевого ключа передайте --offers <файл>');
    return null;
  }
  // Idempotency-Key на логическую операцию: при повторе после сбоя сервер вернёт сохранённый ответ
  const idem = 'batch-' + crypto.createHash('sha256').update(JSON.stringify(offers)).digest('hex').slice(0, 32);
  const r = await api('POST', '/seller/offers/batch', { body: { offers }, idem });
  const b = r.data;
  log('offers/batch: получено ' + b.received + ', создано ' + b.created + ', изменено ' + b.updated + ', без изменений ' + b.unchanged +
    ', пропущено ' + b.skipped + (b.dry_run ? ' (dry_run: test-ключ, без записи)' : '') + (r.replayed ? ' [повтор]' : ''));
  (b.results || []).filter((x) => x.status === 'skipped').forEach((x) => log('  строка ' + x.index + ': ' + x.code + ' — ' + x.message));
  return b;
}

async function createSandboxOrder() {
  let r;
  try {
    r = await api('POST', '/sandbox/orders', { body: {}, idem: 'sandbox-' + crypto.randomUUID() });
  } catch (e) {
    // 422: у продавца нет ни одной опубликованной позиции — тестовый заказ оформлять не на что
    if (e.status === 422) e.message += ' — загрузите прайс в кабинете или боевым ключом: тестовый ключ позиций не публикует';
    throw e;
  }
  const o = r.data;
  log('sandbox/orders: заказ ' + o.number + ' (' + o.order_id + '), статус ' + o.status + ', строк ' + o.lines.length +
    (o.confirm_deadline_at ? ', подтвердить до ' + o.confirm_deadline_at : ''));
  return o;
}

/** Все изменения заказов после since_seq, страницами по cursor. */
async function pollOrders(state) {
  const changed = [];
  let cursor = null;
  do {
    const q = new URLSearchParams({ since_seq: String(state.since_seq), limit: '100' });
    if (cursor) q.set('cursor', cursor);
    const r = await api('GET', '/seller/orders?' + q.toString());
    for (const o of r.data.items) {
      changed.push(o);
      if (typeof o.change_seq === 'number' && o.change_seq > state.since_seq) state.since_seq = o.change_seq;
    }
    cursor = r.data.has_more ? r.data.next_cursor : null;
  } while (cursor);
  return changed;
}

async function confirmOrder(o) {
  const lines = o.lines.filter((l) => l.status === 'awaiting_confirmation')
    .map((l) => ({ line_id: l.line_id, qty: l.qty }));   // здесь ваша логика: меньшее qty, term_days, external_order_ref или reject
  if (!lines.length) return o;
  const r = await api('POST', '/seller/orders/' + encodeURIComponent(o.order_id) + '/confirm', { body: { lines }, idem: 'confirm-' + o.order_id + '-' + o.change_seq });
  log('confirm ' + o.number + ': подтверждено строк ' + lines.length + ', статус ' + r.data.status + (r.replayed ? ' [уже было подтверждено]' : ''));
  return r.data;
}

async function shipOrder(o) {
  const lines = o.lines.filter((l) => l.status === 'confirmed').map((l) => l.line_id);
  if (!lines.length) return o;
  const r = await api('POST', '/seller/orders/' + encodeURIComponent(o.order_id) + '/ship',
    { body: { lines, track: 'CDEK 1234567890', upd_number: 'УПД-' + o.number }, idem: 'ship-' + o.order_id + '-' + o.change_seq });
  log('ship ' + o.number + ': отгружено строк ' + lines.length + ', статус ' + r.data.status + (r.replayed ? ' [уже было отгружено]' : ''));
  return r.data;
}

/** Один проход опроса: подтвердить ожидающие строки и отгрузить подтверждённые. */
async function syncOnce(state, env) {
  const changed = await pollOrders(state);
  const handled = [];
  for (let o of changed) {
    if (env === 'live' && o.test) continue;
    if (o.lines.some((l) => l.status === 'awaiting_confirmation')) o = await confirmOrder(o);
    if (SHIP && o.lines.some((l) => l.status === 'confirmed')) o = await shipOrder(o);
    handled.push(o);
  }
  saveState(state);   // since_seq сохраняется после обработки: сбой в середине — повтор тех же заказов, операции идемпотентны
  log('опрос: изменённых заказов ' + changed.length + ', since_seq = ' + state.since_seq);
  return handled;
}

async function main() {
  const ping = (await api('GET', '/ping')).data;
  log('ping: env ' + ping.env + ', продавец ' + (ping.company && ping.company.seller_code) + ', права ' + (ping.key.scopes || []).join(',') +
    ', подтверждение заказов ' + (ping.company && ping.company.confirm_orders ? 'включено' : 'выключено'));

  await pushOffers(ping.env);

  const state = loadState();
  let sandbox = null;
  if (ping.env === 'test') sandbox = await createSandboxOrder();

  do {
    const handled = await syncOnce(state, ping.env);
    if (sandbox) {
      // проверка сценария песочницы: тестовый заказ найден опросом, подтверждён и отгружен
      const mine = handled.find((o) => o.order_id === sandbox.order_id);
      if (!mine) throw new Error('тестовый заказ ' + sandbox.order_id + ' не пришёл в опросе since_seq');
      const final = (await api('GET', '/seller/orders/' + encodeURIComponent(sandbox.order_id))).data;
      const statuses = final.lines.map((l) => l.status);
      log('итог тестового заказа ' + final.number + ': статус ' + final.status + ', строки ' + statuses.join(','));
      if (SHIP && !statuses.every((s) => s === 'shipped')) throw new Error('строки тестового заказа не отгружены: ' + statuses.join(','));
      if (!SHIP && !statuses.every((s) => s === 'confirmed')) throw new Error('строки тестового заказа не подтверждены: ' + statuses.join(','));
      sandbox = null;
      log('сценарий песочницы пройден: batch → sandbox order → confirm' + (SHIP ? ' → ship' : ''));
    }
    if (LOOP_SEC > 0) await sleep(LOOP_SEC * 1000);
  } while (LOOP_SEC > 0);
}

main().catch((e) => {
  console.error('ОШИБКА: ' + e.message);
  if (e.details) console.error(JSON.stringify(e.details));
  process.exit(1);
});

Биржа: программа продавца

Биржа — вторая витрина площадки. Покупатель публикует заявку («нужен фильтр на MAN TGX, 2 шт., сегодня»), продавцы отвечают коммерческими предложениями (КП), покупатель выбирает и оформляет заказ. Программа делает то же, что сотрудник в кабинете, теми же правилами.

Что нужно

  • Ключ с правом exchange. Другие права для биржи не нужны: заказы из КП такой ключ видит и отгружает.
  • Регион заявок ключа: «моего региона» (по умолчанию, как лента сотрудника) или «всей России». Выбирается при выпуске ключа и меняется кнопкой «Сменить регион заявок»; текущее значение — в ping, поле key.exchange_region.
  • Принятые документы площадки: без них отправка КП боевым ключом отвечает 403 LEGAL_NEEDS, а список недостающего приходит в ping, поле legal_needs.

Цикл обмена

  1. Подпишитесь на exchange.* (вебхуки) или опрашивайте GET /seller/exchange/requests?since_seq=N. Сохраняйте change_seq последнего элемента — это N следующего опроса. Номер заявки растёт и от чужих КП, поэтому один опрос показывает и новые заявки, и изменения по старым.
  2. Смотрите can_propose и cannot_propose_reason: closed, expired, limit (по заявке собран лимит КП), region, bought, replace_limit.
  3. Отправьте КП: POST /seller/exchange/requests/{id}/proposals с обязательным Idempotency-Key. На одну позицию можно дать несколько вариантов — отдельными строками. mode: "draft" готовит черновик: покупатель его не видит, сотрудник проверит и отправит из кабинета.
  4. Новая отправка заменяет прежнее действующее КП (его статус — superseded), ссылка — replaces_proposal_id. Чтобы не заменить чужую правку, передайте replaces_proposal_id из поля current_proposal_id заявки: несовпадение — 409 INVALID_STATE.
  5. Покупатель принял — приходит exchange.proposal.accepted и order.created. Заказ из КП виден в GET /seller/orders?source=exchange.
  6. Заказ из КП не подтверждают: КП — уже ваш ответ. confirm и reject отвечают 409 EXCHANGE_ORDER, работает только ship.
  7. ship отгружает заказ из КП целиком: lines в теле на отгрузку не влияют, но если вы их передали, каждый line_id должен быть из этого заказа, иначе 400 VALIDATION_FAILED. status заказа становится shipped. У строк такого заказа status пустой (null), а shipped_qty — 0, поэтому отгрузку смотрите по status заказа.
# Лента заявок: раз в минуту или по вебхуку exchange.request.created
curl -s "$K247/seller/exchange/requests?since_seq=184000" -H "Authorization: Bearer $K247_TOKEN"

# Коммерческое предложение по заявке: несколько вариантов на одну позицию — отдельными строками
curl -s -X POST $K247/seller/exchange/requests/r_8c1d2e3f4a/proposals -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: kp-2609-412-1" \
  -d '{"mode":"send","lines":[{"item_id":"i_1a2b3c4d5e","brand":"MANN","article":"W 950/26","price_kop":123450,"qty":2,"term":"in_stock"}]}'

# Заказ пришёл: подтверждать не нужно, сразу отгрузка
curl -s "$K247/seller/orders?source=exchange" -H "Authorization: Bearer $K247_TOKEN"

Что считает площадка

  • Стоимость КП (value_kop) — не сумма всех строк, а сумма того, что покупатель купит по кнопке «Принять»: по одному варианту на позицию, самому дешёвому за штуку, и не больше нужного количества. Сумма всех строк, как вы её прислали, — lines_sum_kop.
  • Место — competition.my_rank из competition.proposals по этой стоимости. Сумм и цен соперников площадка не раскрывает.
  • Полнота — coverage: сколько позиций заявки открыто, по скольким у вас есть строка и по скольким вариант закрывает всё количество.
  • Потребность позиции. Купить больше, чем указано в позиции заявки, нельзя — ни вам, ни покупателю. Строка по уже выкупленной позиции при отправке отклоняется кодом ALREADY_BOUGHT.

Чего в API нет

Переписки (у заявки есть только счётчик unread_messages — позовите сотрудника), правки отправленного КП на месте (отправьте новое), встречной цены, отказа от заказа из КП. Замена КП через API — не чаще раза в 5 минут и не больше 10 раз по одной заявке (409 PROPOSAL_REPLACE_LIMIT); в кабинете ограничений нет. Новая отправка после снятия своего отправленного КП тоже считается заменой (в заявке — can_propose: false, причина replace_limit и время replace_available_at); снятый черновик, который не отправлялся, заменой не считается.

Что видно о покупателе

Марка, модель, год и VIN машины — да: без них деталь не подобрать. Названия компании, публичного кода, телефона, госномера и адреса получения в API нет — вместо них постоянный псевдоним buyer.ref вида «П-7F3A9C»: один и тот же для этого покупателя во всех его заявках и заказах у вас. В заявке адреса нет; в заказе из КП есть адрес доставки delivery.address: его покупатель указывает при «Принять», а если не указал — это адрес его компании из кабинета. Телефоны, почта и ссылки в заявках и КП запрещены: при отправке — 422 CONTACTS_FORBIDDEN, в старых записях — «[скрыто]». В КП запрещены и сведения о продавце: реквизиты (ИНН, ОГРН, КПП, БИК, счёт, номер карты — kind: "requisites"), название с формой собственности, название и адрес своей компании и складов (company), фамилии своих сотрудников (person): покупатель видит продавца кодом, сведения о продавце — в документах заказа. Предлагать в КП расчёты мимо площадки («платите напрямую продавцу», «минуя площадку», «переведите на карту») тоже нельзя — kind: "bypass"; оплата заказа — только способами, которые площадка предлагает при оформлении. Покупателю тексты КП показываются очищенными с профилем вашей компании: в старых записях название, адрес складов, фамилии сотрудников и предложение с расчётом мимо площадки заменены на «[скрыто]». Фото детали передаётся отдельным полем photo_url строки КП, только с публичных фотохостингов из списка площадки (GET /dictionary/exchange, поле photo_hosts).

Отладка без живых покупателей — «Песочница биржи».

Покупатель: поиск

Поиск по витрине — GET /buyer/search, он доступен любому ключу. Перепроверка условий и заказы — в разделе «Заказы покупателя», заявки и КП — в разделе «Биржа: программа покупателя»; и то, и другое — только организациям.

  1. По артикулу без бренда: ?article=01089 — ответ mode: brands, список брендов с этим артикулом. Если бренд у артикула один, сразу приходит mode: offers с предложениями. Список брендов при любом их числе — с параметром mode=brands. С параметром similar=1 в списке ещё похожие номера (similar) — номера, которые начинаются с введённого.
  2. Выбрали пару из списка: ?article=01089&brand_id=17 (поля article_norm и brand_id пары) — ответ mode: offers, группы запрошенного артикула и аналогов, в том числе аналогов от поставщиков площадки.
  3. У предложения нет названия продавца и его внутренних идентификаторов: только код продавца, рейтинг, долю отказов, город и offer_key, действительный 30 минут.

У тестового ключа limit не больше 20. Пометка stale: true и availability: on_order — прайс продавца старше 72 ч.

Заказы покупателя

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

# 1. Найти позицию: подставьте свой артикул и brand_id (offer_key живёт 30 минут)
curl -s "$K247/buyer/search?article=01-089&brand_id=17" -H "Authorization: Bearer $K247_TOKEN"

# 2. Перепроверить перед заказом: цена, остаток, срок
curl -s -X POST $K247/buyer/quotes -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"items":[{"offer_key":"eyJvIjoi...","qty":2}]}'

# 3. Оформить заказ (strict: изменилась цена — заказа не будет)
curl -s -X POST $K247/buyer/orders -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: order-4471" \
  -d '{"reference":"ТК-Север-4471","items":[{"offer_key":"eyJvIjoi...","qty":2}],"payment":"invoice","accept_terms":true,"price_policy":"strict"}'

# 4. Следить за заказами: и за магазинными, и за теми, что из КП биржи
curl -s "$K247/buyer/orders?since_seq=0" -H "Authorization: Bearer $K247_TOKEN"

Перепроверка условий

POST /buyer/quotes берёт до 50 offer_key и всегда отвечает 200. Статус каждой строки: ok, price_changed (новая цена в price_kop, прежняя — в previous_price_kop), insufficient_qty, unavailable, invalid_key. Для ok и price_changed приходит свежий offer_key — оформляйте заказ им.

Оформление

  • price_policy: strict (по умолчанию) — любая изменившаяся цена отменяет заказ с 409 OFFER_CHANGED и актуальными условиями в details; accept_lower — принять, если цена не выросла.
  • accept_terms: true обязателен: это ознакомление с условиями возврата, оно записывается в журнал согласий компании. Без него — 422 BUSINESS_RULE, как у корзины сайта.
  • Экспресс-доставка — delivery.type: "express" или delivery.express: true: с ценой экспресса и от порога суммы, как в корзине. Комментарий к заказу — comment (до 500 символов), продавец видит его в заказе.
  • Тело — JSON в кодировке UTF-8; иначе — 400 BAD_REQUEST. В Windows кириллица в -d '…' уходит в cp1251: сохраните тело в файл UTF-8 и отправьте curl --data-binary @body.json; в полях-справочниках передавайте коды (invoice), а не подписи.
  • Позиции разных продавцов разъезжаются по отдельным заказам — в ответе их несколько.
  • Кратность и минимальная партия из прайса не проверяются — как и в корзине сайта; поля multiplicity и min_order даются для сведения.
  • Свою позицию заказать нельзя (422 OWN_OFFER), недостаток остатка — 422 OUT_OF_STOCK, денег на балансе — 422 INSUFFICIENT_FUNDS.

Слежение

GET /buyer/orders?since_seq=N отдаёт заказы обоих видов: из магазина (source: "shop") и по принятым КП биржи (source: "exchange", с блоком exchange и ссылками строк на строки КП). Фильтры — source, status, reference, ids. У отказанных и отменённых строк есть alternatives — до трёх замен с готовым offer_key. Про события заказов — «Вебхуки».

Заказ из КП продавец не подтверждает и отгружает целиком: отгрузку смотрите по status заказа (shipped) и событию order.shipped. У строк такого заказа status пустой (null), shipped_qty — 0. Адрес доставки заказа — delivery.address: из тела «Принять», а без него — адрес компании из кабинета.

Ключ, у которого есть только право exchange, видит здесь лишь заказы из КП.

Биржа: программа покупателя

Организация — транспортная компания, автосервис, магазин или разборка в роли покупателя — публикует заявку из своей программы, получает КП продавцов и оформляет заказ. Автовладельцу эти методы недоступны (403 FORBIDDEN_ROLE): его заказы оформляются в кабинете.

Что нужно

  • Ключ с правом exchange — выпускается в «Кабинете», вкладка «API» (панель «Подключение программы»).
  • Принятые документы площадки: без них создание заявки, «Принять» и заказ боевым ключом отвечают 403 LEGAL_NEEDS.
  • Заявки видит вся компания: созданные сотрудниками в кабинете программа тоже получает, и наоборот.

Цикл обмена

  1. POST /buyer/exchange/requests — марка машины обязательна, позиции описываются словами (артикул не нужен). Свой номер заявки — reference, по нему потом ищете. Коды полей (срок, доставка, оплата, группа и рынок машины, регион) — в GET /dictionary/exchange.
  2. Ждите exchange.proposal.received и exchange.proposal.replaced или опрашивайте GET /buyer/exchange/proposals?exchange_request_id=. Без since_seq приходят действующие КП, с since_seq — все статусы, включая снятые и заменённые. С sort=value площадка сортирует все КП заявки по стоимости и отдаёт первые limit; курсора у этого режима нет, has_more: true — КП больше, чем limit.
  3. POST /buyer/exchange/proposals/{id}/accept — «Принять». Без lines площадка соберёт тот же план, что кнопка в кабинете: по одному варианту на позицию, самому дешёвому за штуку, в нужном количестве. С lines вы выбираете строки сами, в том числе два варианта на одну позицию (2 + 2 шт. при потребности 4). accept_terms: true обязателен. Адрес доставки заказа — delivery.address в теле «Принять»; без него в заказ попадает адрес компании из кабинета, а не pickup_address заявки. Способ доставки и оплата без полей delivery.type и payment берутся из заявки.
  4. В ответе — заказы со строками. Дальше они живут в «Заказах покупателя» с source: "exchange".
  5. Ненужное КП — reject, заявку целиком — close, правки — update. Повтор этих вызовов отвечает 200 и заголовком Idempotent-Replayed: state.
# 1. Заявка на биржу: Idempotency-Key обязателен
curl -s -X POST $K247/buyer/exchange/requests -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: zayavka-4471" \
  -d '{"reference":"ТК-Север-4471","vehicle":{"brand":"MAN","model":"TGX","year":"2016","vin":"WMA06XZZ0FP000001"},"items":[{"name":"Фильтр масляный","qty":2}],"deadline":"today"}'

# 2. КП по заявке, самые дешёвые сверху
curl -s "$K247/buyer/exchange/proposals?exchange_request_id=r_8c1d2e3f4a&sort=value" -H "Authorization: Bearer $K247_TOKEN"

# 3. Принять целиком (без lines) или выбранными строками
curl -s -X POST $K247/buyer/exchange/proposals/o_6a7b8c9d0e/accept -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: accept-4471-1" \
  -d '{"lines":[{"proposal_line_id":"ol_92b3c4d5e6","qty":2}],"accept_terms":true}'

Сколько можно купить

Больше, чем указано в позиции заявки, купить нельзя. Если позицию успели купить у другого продавца или другим ключом — 409 ALREADY_BOUGHT; если выбранные строки в сумме дают больше нужного — 409 NEED_EXCEEDED с оставшейся потребностью в details. Нужно больше — исправьте количество в заявке (update). Купленную позицию менять и удалять нельзя, а переименование или удаление позиции закрывает строки КП по ней — продавцы получат exchange.request.updated.

Пределы

Не больше 60 заявок в час на компанию (429 RATE_LIMITED) и 300 действующих заявок, созданных ключами (409 LIMIT_EXCEEDED). У тестового ключа — пятая часть и не больше 20 действующих. Телефоны, почта и ссылки в заявке запрещены (422 CONTACTS_FORBIDDEN): договариваться нужно через площадку.

Что видно о продавце

Код, город, регион, рейтинг и доля отказов — названия компании и её контактов в API нет ни до, ни после заказа: они в документах сделки и в кабинете. Склад в строке заказа — город и обезличенный код вида «ТТ-014-2» (warehouse_code), не название и адрес склада. Внутренних кодов позиций продавца в КП и в заказе покупателя тоже нет. В текстах КП реквизиты, контакты, название и адрес продавца, фамилии его сотрудников и предложения рассчитаться мимо площадки, в названиях позиций — реквизиты и контакты заменены на «[скрыто]».

Отладка без живых продавцов — «Песочница биржи».

Вебхуки

Вебхук — адрес вашей программы, на который К-247 сам отправляет событие, как только что-то изменилось: пришёл заказ, продавец отказал по позиции, загрузка прайса остановлена. Опрашивать API по since_seq можно реже — вебхук подскажет, когда пора.

Как подключить

  1. Поднимите приёмник: адрес https://, порт 443, 8443, 80 или 8080. Он проверяет подпись и отвечает 2xx за 10 секунд.
  2. Создайте подписку запросом POST /webhooks или в кабинете: «Прайс и подключение» → «Для программиста» → «Вебхуки». Сохраните секрет whsec_… — он показывается один раз.
  3. Отправьте проверочное событие ping: POST /webhooks/{id}/test или кнопкой в кабинете. Итог — в last_delivery и в журнале доставок кабинета.

Окружение подписки — окружение ключа: подписка тестового ключа получает события песочницы (тестовые заказы, проверочные загрузки), боевого — настоящие. У компании до 5 подписок в каждом окружении. На тестовом стенде площадки адрес может быть http://, на k-247.ru — только https://.

События

СобытиеКогдаЧто в data
order.createdНовый заказПродавцу — по его позициям, покупателю — по его заказу
order.line_confirmedПродавец подтвердил позицииВ data.line_ids — подтверждённые строки
order.line_rejectedОтказ по позициямПричина — data.reason: out_of_stock, partial_stock…
order.line_cancelledПозиции отмененыПокупателем, менеджером или по сроку подтверждения (sla_timeout)
order.shippedЗаказ отгруженСтатус заказа — data.status
import.finishedЗагрузка прайса завершенаdata.status: published, failed или rejected, счётчики строк
import.heldЗагрузка остановлена для проверкиПричины — data.held_reason; подтвердите или отклоните загрузку
price.staleПрайс устарелИсточник не обновлялся дольше срока — позиции показываются «под заказ»
exchange.request.createdНовая заявка на биржеПродавцу, которому заявка видна по правилам ленты и фильтру подписки options
exchange.request.updatedЗаявка измененаdata.reason: edited или items_closed, закрытые строки КП
exchange.request.closedЗаявка закрытаdata.reason: bought, buyer_closed, expired, manager_closed
exchange.proposal.receivedПришло КППокупателю: КП продавца, код продавца
exchange.proposal.replacedКП замененоПокупателю: новое КП и заменённое
exchange.proposal.withdrawnКП снятоПокупателю: КП снято продавцом
exchange.proposal.acceptedКП принятоПродавцу: заказы и купленные строки, data.scope: full или partial
exchange.proposal.rejectedКП отклоненоПродавцу: итоговый статус КП
*Все события, кроме биржиНовые типы событий тоже будут приходить — неизвестный тип просто пропустите; события биржи — только по exchange.*
order.*Все события заказовШаблон по префиксу
import.*Все события загрузки прайсаШаблон по префиксу
exchange.*Все события биржиНужно право ключа exchange

Продавцу приходят order.* по его заказам, import.* и price.stale по его прайсам; покупателю — order.* по его заказам.

Конверт и заголовки

Площадка отправляет POST с JSON-конвертом. data минимален: идентификаторы и статусы, полные данные — через GET.

{
  "id": "evt_01J8Z6A1B2C3",
  "type": "order.line_rejected",
  "created_at": "2026-09-15T09:40:00.000Z",
  "env": "live",
  "api_version": "1",
  "data": {
    "order_id": "ord_5e1a2b3c4d",
    "number": "2609-1190",
    "status": "awaiting_confirmation",
    "line_ids": [
      "l_9a8b7c6d5e"
    ],
    "reason": "out_of_stock"
  }
}
ЗаголовокЗначение
X-K247-EventТип события: order.line_rejected
X-K247-Event-IdСовпадает с id конверта
X-K247-Delivery-AttemptНомер попытки, с 1
X-K247-Signaturet=<unix>,v1=<hex HMAC-SHA256(secret, t + "." + тело)>
User-AgentK247-Webhooks/1

Повторы и выключение

Успех — любой ответ 2xx за 10 секунд. Иначе повтор через 1 мин, 5 мин, 30 мин, 2 ч, 6 ч и 24 ч — всего 7 попыток. Редиректы не выполняются: 3xx считается неуспехом, укажите конечный адрес. Порядок событий не гарантируется, дубли возможны: храните обработанные id не меньше 72 часов.

После 100 неудачных доставок подряд подписка выключается, сотрудникам компании приходит уведомление. Включите её снова в кабинете, когда приёмник заработает. Проверочный ping доходит и до выключенной подписки.

Проверка подписи на Node.js

const crypto = require('crypto');

// rawBody — тело запроса байтами, до JSON.parse; header — значение X-K247-Signature
function verifyK247(rawBody, header, secret) {
  const parts = Object.fromEntries(String(header || '').split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || !/^[0-9a-f]{64}$/.test(parts.v1 || '')) return false;
  if (Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(t + '.').update(rawBody).digest();
  return crypto.timingSafeEqual(expected, Buffer.from(parts.v1, 'hex'));
}

Приёмник на Node.js

const http = require('http');

const seen = new Map(); // id события → время обработки; храните не меньше 72 часов (лучше — в своей базе)

http.createServer((req, res) => {
  const chunks = [];
  req.on('data', (c) => chunks.push(c));
  req.on('end', () => {
    const raw = Buffer.concat(chunks);
    if (!verifyK247(raw, req.headers['x-k247-signature'], process.env.K247_WEBHOOK_SECRET)) { res.writeHead(401); return res.end(); }
    const event = JSON.parse(raw);
    res.writeHead(200); // ответить быстро: 10 секунд на ответ, обработку — в очередь своей программы
    res.end();
    if (seen.has(event.id)) return; // повтор той же доставки
    seen.set(event.id, Date.now());
    // event.type: order.created, order.line_rejected… полные данные — GET /seller/orders/{event.data.order_id}
  });
}).listen(8080);

Проверка подписи на PHP

<?php
// $raw — тело запроса как есть; секрет подписки хранится у вас
function verify_k247(string $raw, string $header, string $secret): bool {
    $parts = [];
    foreach (explode(',', $header) as $p) { [$k, $v] = array_pad(explode('=', $p, 2), 2, ''); $parts[$k] = $v; }
    if (!ctype_digit($parts['t'] ?? '') || !preg_match('/^[0-9a-f]{64}$/', $parts['v1'] ?? '')) return false;
    if (abs(time() - (int)$parts['t']) > 300) return false;
    $expected = hash_hmac('sha256', $parts['t'] . '.' . $raw, $secret);
    return hash_equals($expected, $parts['v1']);
}

$raw = file_get_contents('php://input');
if (!verify_k247($raw, $_SERVER['HTTP_X_K247_SIGNATURE'] ?? '', getenv('K247_WEBHOOK_SECRET'))) { http_response_code(401); exit; }
$event = json_decode($raw, true);
// повтор той же доставки: id уже обработан — ответить 200 и ничего не делать
http_response_code(200);

Список подписок — GET /webhooks, удаление — DELETE /webhooks/{id}.

Песочница

Отдельного адреса нет: песочница — это тестовый ключ k247_test_… на том же https://k-247.ru/api/v1. Поле env в ответе ping показывает, какой ключ используется. Тестовые действия не трогают витрину, остатки, резервы, балансы и рейтинг отказов.

ДействиеБоевой ключТестовый ключ
ping, справочники, buyer/searchРеальные данныеРеальные данные, только чтение
pricelists, offers/batch, DELETE offersПубликацияРазбор и отчёт, dry_run, без записи
PUT warehousesЗапись403 FORBIDDEN_ENV
sandbox/orders403 FORBIDDEN_ENVТестовый заказ на свою позицию
seller/orders, confirm, reject, shipНастоящие заказыТолько тестовые заказы своей компании, без денег и остатков
buyer/ordersНастоящие заказыТестовый заказ служебному продавцу: робот подтверждает и отгружает
Журнал в кабинетеОкружение liveОкружение test
ЛимитыПолныеПятая часть, в поиске limit до 20
Пределы на компанию—Тестовых заказов — 50 действующих (409 LIMIT_EXCEEDED) и 200 за сутки (429 RATE_LIMITED), в заказе покупателя — до 20 позиций; тестовых заявок биржи — 20 действующих и 100 за сутки

Тестовые заказы удаляются через 7 дней после создания.

Перед тестовым заказом

Тестовый заказ оформляется на вашу опубликованную позицию, а тестовый ключ позиций не создаёт: offers/batch и pricelists с ним только проверяют данные. Поэтому сначала нужна хотя бы одна позиция на витрине: загрузите прайс в кабинете (файлом, по ссылке или по почте) или отправьте пакет боевым ключом. Если позиций нет, шаг 3 ответит 422 BUSINESS_RULE.

Сценарий из пяти команд

# 0. Тестовый ключ из кабинета
export K247_TOKEN=k247_test_...
export K247=https://k-247.ru/api/v1

# 1. Ключ работает, env = test
curl -s $K247/ping -H "Authorization: Bearer $K247_TOKEN"

# 2. Пакет предложений: разбор без записи, dry_run = true
curl -s -X POST $K247/seller/offers/batch -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: sandbox-batch-1" -d '{"offers":[{"brand":"FEBI BILSTEIN","article":"01-089","name":"Термостат","price_kop":123450,"qty":12,"term_days_min":1,"term_days_max":3,"external_id":"A-778","updated_at":"2026-09-15T08:59:00.000Z"}]}'

# 3. Тестовый заказ на свою позицию: запомните order_id и line_id из ответа
curl -s -X POST $K247/sandbox/orders -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: sandbox-order-1" -d '{}'

# 4. Подтвердить все строки
curl -s -X POST $K247/seller/orders/ORDER_ID/confirm -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{}'

# 5. Отгрузить
curl -s -X POST $K247/seller/orders/ORDER_ID/ship -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"track":"CDEK 1234567890","upd_number":"УПД-1"}'

Тот же сценарий на Node.js — в разделе «Заказы продавца». Тестовый заказ можно создать и кнопкой «Создать тестовый заказ» в кабинете.

Программа покупателя: заказ тестовым ключом

Заказ по прайсу оформляется тем же POST /buyer/orders с offer_key из поиска, как в разделе «Заказы покупателя». С тестовым ключом заказ приходит с test: true, продавец в нём — служебный робот ТЕСТ-000: реальный продавец такого заказа не видит, деньги и остатки не двигаются. Через 30–60 секунд робот подтверждает строки (order.line_confirmed), строку с qty = 13 отклоняет с причиной out_of_stock (order.line_rejected), ещё через 30–60 секунд отгружает подтверждённое (order.shipped). Тестовые заказы считаются по классу лимита default («Лимиты»), поэтому несколько заказов подряд не упираются в 429.

Коллекции Postman нет: Postman и Swagger Editor импортируют спецификацию /api/openapi.json напрямую.

Песочница биржи

Тестовым ключом можно пройти весь цикл биржи без живых людей, денег и остатков. Тестовые заявки и КП не видны ни покупателям, ни продавцам, не двигают баланс, рейтинг и долю отказов, не шлют писем и нумеруются отдельно («ТЕСТ-З-2609-7»). Роботы ходят тактами: шаг занимает 30 секунд, а такт проверяется раз в 30 секунд, поэтому ответ приходит через 30–60 секунд. Закрытые тестовые заявки убираются через 7 дней.

Программа продавца: робот-покупатель

Тестовую заявку создаёт метод POST /sandbox/exchange/requests (или кнопка «Создать тестовую заявку» в кабинете). Заявка приходит только вашей компании: проверка реквизитов, биржа по типу компании и регион в песочнице не сверяются. Сценарий робота задаётся полем scenario и отрабатывает через такт после вашего первого КП. У служебного покупателя нет адреса в кабинете, поэтому в заказе робота адрес доставки delivery.address — «Тестовый адрес, 1».

scenarioЧто делает робот-покупательКакие события придут
acceptПринимает КП целикомexchange.proposal.accepted (full), order.created
accept_partialПокупает только первую позициюexchange.proposal.accepted (partial), order.created
rejectОтклоняет КП, и каждое следующее тожеexchange.proposal.rejected
closeСнимает заявку без покупкиexchange.request.closed (buyer_closed)
expireСтавит срок через такт — заявку закрывает таймерexchange.request.closed (expired)
competeРобот-продавец ТЕСТ-000 присылает КП на 10 % дешевле, и робот покупает у негоmy_rank = 2, затем exchange.request.updated (items_closed) и exchange.request.closed (bought)

Девять команд от ключа до отгрузки

# 0. Тестовый ключ с правом «Биржа»
export K247_TOKEN=k247_test_...
export K247=https://k-247.ru/api/v1

# 1. Ключ работает: env = test, capabilities.exchange.seller = true
curl -s $K247/ping -H "Authorization: Bearer $K247_TOKEN"

# 2. Подписка на события (на стенде допустим http, в бою — только https)
curl -s -X POST $K247/webhooks -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"url":"https://erp.shop.ru/k247/hook","events":["exchange.*","order.*"]}'

# 3. Робот-покупатель создаёт тестовую заявку для вашей компании
curl -s -X POST $K247/sandbox/exchange/requests -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: sbx-req-1" -d '{"scenario":"accept"}'

# 4. Заявка в ленте
curl -s "$K247/seller/exchange/requests?since_seq=0" -H "Authorization: Bearer $K247_TOKEN"

# 5. Отправить КП (повтор с тем же Idempotency-Key вернёт то же КП)
curl -s -X POST $K247/seller/exchange/requests/REQUEST_ID/proposals -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: sbx-kp-1" \
  -d '{"lines":[{"item_id":"ITEM_ID","brand":"MANN","article":"W 950/26","price_kop":123450,"qty":2,"term":"in_stock"}]}'

# 6. Через 30-60 с: вебхуки exchange.proposal.accepted и order.created
# 7. Заказ из КП
curl -s "$K247/seller/orders?source=exchange" -H "Authorization: Bearer $K247_TOKEN"

# 8. Подтверждение недоступно — ожидаемые 409 EXCHANGE_ORDER
curl -s -X POST $K247/seller/orders/ORDER_ID/confirm -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{}'

# 9. Отгрузить
curl -s -X POST $K247/seller/orders/ORDER_ID/ship -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"track":"CDEK 1234567890"}'

Замену КП в песочнице проверяйте не раньше чем через 5 минут после отправки: предел замен действует и для тестового ключа.

Программа покупателя: роботы-продавцы

Отдельного метода не нужно: создайте обычную заявку тестовым ключом и добавьте поле sandbox. На неё отвечают роботы ТЕСТ-000 и ТЕСТ-001. Строки их КП: бренд K247-TEST, артикул TEST-<номер позиции>, цена 1 000 ₽ × номер позиции. Позиция на 13 шт. остаётся без строки (КП покрывает не всё), позиция на 12 шт. получает два варианта: второй на 20 % дороже и со сроком «2-3 дня».

sandbox.scenarioЧто делают роботы-продавцыКакие события придут
standardЧерез такт КП от ТЕСТ-000, ещё через такт — замена на 5 % дешевлеexchange.proposal.received, затем exchange.proposal.replaced
two_sellersКП от ТЕСТ-000 и ТЕСТ-001; второй дешевле по первой позиции и молчит по остальнымдва exchange.proposal.received
withdrawКП приходит и через такт снимаетсяexchange.proposal.received, exchange.proposal.withdrawn
no_answerКП не приходит вовсе—
expireКП приходит, а заявка закрывается по срокуexchange.proposal.received, exchange.request.closed (expired)
# Тестовая заявка покупателя: сценарий задаётся полем sandbox
curl -s -X POST $K247/buyer/exchange/requests -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: sbx-buy-1" \
  -d '{"vehicle":{"brand":"MAN","model":"TGX"},"items":[{"name":"Фильтр масляный","qty":2}],"sandbox":{"scenario":"two_sellers"}}'

# Через 30-60 с: exchange.proposal.received; КП роботов
curl -s "$K247/buyer/exchange/proposals?exchange_request_id=REQUEST_ID" -H "Authorization: Bearer $K247_TOKEN"

# Принять и дождаться order.created, затем через 30-60 с order.shipped
curl -s -X POST $K247/buyer/exchange/proposals/PROPOSAL_ID/accept -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: sbx-acc-1" -d '{"accept_terms":true}'

После «Принять» появляется тестовый заказ: продавцом в нём числится служебная компания площадки, деньги и остатки не двигаются, а через такт заказ отгружается — придёт order.shipped. Тестовых заявок на компанию — не больше 20 действующих для каждой стороны.

Заказы по прайсу в песочнице описаны в разделе «Песочница»: там робот подтверждает строки, отклоняет строку с qty = 13 и отгружает.

Справочник методов

Все методы из OpenAPI с примерами запросов и ответов. В примерах curl подставьте свой ключ в $K247_TOKEN и идентификаторы своих объектов.

Общие

GET/api/v1/ping

Проверка ключа, окружения, прав и лимитов

Право: не нужноЛимит: default

Ответ 200 Ключ действителен

{
  "ok": true,
  "env": "test",
  "company": {
    "seller_code": "ТТ-014",
    "type": "shop",
    "verified": true,
    "city": "Санкт-Петербург",
    "confirm_orders": true
  },
  "key": {
    "prefix": "k247_test_8f3kq2mz",
    "scopes": [
      "offers",
      "orders"
    ],
    "created_at": "2026-09-15T07:00:00.000Z",
    "exchange_region": "own"
  },
  "rate_limit": {
    "default": {
      "limit": 24,
      "remaining": 11
    },
    "search": {
      "limit": 12,
      "remaining": 6
    },
    "bulk": {
      "limit": 2,
      "remaining": 1
    }
  },
  "server_time": "2026-09-15T09:12:00.000Z",
  "api_version": "1.1",
  "capabilities": {
    "exchange": {
      "enabled": true,
      "seller": true,
      "buyer": true,
      "orders": "all",
      "counter_offer": false
    }
  },
  "legal_needs": []
}

Ошибки: 401, 403, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/ping -H "Authorization: Bearer $K247_TOKEN"

Справочники

GET/api/v1/dictionary/fields

Поля прайса v1 (CSV/XLSX/offers.batch)

Право: не нужноЛимит: default

Ответ 200 Спецификация полей

{
  "version": "1",
  "fields": [
    {
      "key": "brand",
      "required": true,
      "type": "string",
      "max_length": 60,
      "headers": [
        "бренд",
        "производитель",
        "brand"
      ]
    },
    {
      "key": "price_kop",
      "required": true,
      "type": "integer",
      "min": 100,
      "max": 10000000000,
      "file_key": "price",
      "file_type": "money_rub"
    }
  ],
  "file": {
    "formats": [
      "csv",
      "xlsx",
      "zip"
    ],
    "encodings": [
      "utf-8",
      "windows-1251",
      "utf-16"
    ],
    "delimiters": [
      ";",
      "\t",
      ",",
      "|"
    ],
    "max_file_mb": 100,
    "max_rows": 500000,
    "docpart_five_columns": [
      "Производитель",
      "Артикул",
      "Наименование",
      "Количество",
      "Цена"
    ]
  }
}

Ошибки: 401, 403, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/dictionary/fields -H "Authorization: Bearer $K247_TOKEN"

GET/api/v1/dictionary/brands

Канонические бренды и синонимы; бренды по артикулу

Право: не нужноЛимит: search

Параметры

ИмяГдеОбяз.ТипОписание
qqueryнетstringПрефикс бренда или синонима
articlequeryнетstringБренды, у которых есть предложения или кроссы по артикулу
limitqueryнетinteger
cursorqueryнетstringНепрозрачный курсор из next_cursor

Ответ 200 Список брендов

{
  "items": [
    {
      "brand_id": 17,
      "name": "FEBI BILSTEIN",
      "group": null,
      "country": "DE",
      "kind": "aftermarket",
      "aliases": [
        "FEBI",
        "FEBIBILSTEIN"
      ]
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s "https://k-247.ru/api/v1/dictionary/brands?q=feb&limit=20" -H "Authorization: Bearer $K247_TOKEN"

GET/api/v1/dictionary/statuses

Коды статусов заказа, строки, причин отказа, импорта, событий

Право: не нужноЛимит: default

Ответ 200 Справочник

{
  "order_statuses": [
    {
      "code": "awaiting_confirmation",
      "name": "Ожидает подтверждения"
    },
    {
      "code": "cancelled",
      "name": "Отменён"
    }
  ],
  "line_statuses": [
    {
      "code": "confirmed",
      "name": "Подтверждена"
    }
  ],
  "reject_reasons": [
    {
      "code": "out_of_stock",
      "name": "Нет в наличии",
      "counts_as_refusal": true
    }
  ],
  "import_statuses": [
    "awaiting_mapping",
    "queued",
    "running",
    "held",
    "publishing",
    "published",
    "failed",
    "rejected"
  ],
  "webhook_events": [
    "order.created",
    "order.line_confirmed",
    "order.line_rejected",
    "order.line_cancelled",
    "order.shipped",
    "import.finished",
    "import.held",
    "price.stale"
  ]
}

Ошибки: 401, 403, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/dictionary/statuses -H "Authorization: Bearer $K247_TOKEN"

GET/api/v1/dictionary/exchange

Справочник биржи: коды перечислений и подписи, регионы, фотохостинги, пределы

Право: не нужноЛимит: default

Без права ключа. На вход методы биржи принимают код или подпись (без учёта регистра, «ё» и вида тире). limits — пределы окружения ключа: у test-ключа requests_per_hour — 0,2 от боевого (целое, не меньше 1), active_api_requests — 20.

Ответ 200 Справочник

{
  "version": "1",
  "exchanges": [
    {
      "code": "new",
      "name": "Новые запчасти"
    },
    {
      "code": "used",
      "name": "Б/у запчасти"
    },
    {
      "code": "all",
      "name": "Все варианты"
    }
  ],
  "vgroups": [
    {
      "code": "car",
      "name": "Легковые",
      "markets": true
    },
    {
      "code": "truck",
      "name": "Грузовые",
      "markets": true
    },
    {
      "code": "special",
      "name": "Спецтехника",
      "markets": false
    },
    {
      "code": "equip",
      "name": "Доп. оборудование",
      "markets": false
    },
    {
      "code": "other",
      "name": "Иное",
      "markets": false
    }
  ],
  "markets": [
    {
      "code": "eu",
      "name": "Европа"
    },
    {
      "code": "cn",
      "name": "Китай"
    },
    {
      "code": "jp",
      "name": "Япония"
    },
    {
      "code": "kr",
      "name": "Корея"
    },
    {
      "code": "us",
      "name": "США"
    },
    {
      "code": "ru",
      "name": "Россия"
    }
  ],
  "deadlines": [
    {
      "code": "today",
      "name": "сегодня",
      "expires": "end_of_day_msk"
    },
    {
      "code": "days_2_3",
      "name": "2-3 дня",
      "hours": 72
    },
    {
      "code": "week",
      "name": "неделя",
      "hours": 168
    }
  ],
  "priorities": [
    {
      "code": "fast",
      "name": "быстрее"
    },
    {
      "code": "cheap",
      "name": "дешевле"
    },
    {
      "code": "any",
      "name": "все"
    }
  ],
  "pickups": [
    {
      "code": "delivery",
      "name": "доставка"
    },
    {
      "code": "pickup",
      "name": "самовывоз"
    }
  ],
  "deliveries": [
    {
      "code": "main",
      "name": "основной"
    },
    {
      "code": "pickup",
      "name": "самовывоз"
    },
    {
      "code": "express",
      "name": "экспресс (2 часа)"
    }
  ],
  "payments": [
    {
      "code": "balance",
      "name": "с баланса"
    },
    {
      "code": "invoice",
      "name": "счёт"
    },
    {
      "code": "card_on_delivery",
      "name": "картой при получении"
    }
  ],
  "vats": [
    {
      "code": "vat22",
      "name": "НДС 22%"
    },
    {
      "code": "vat5",
      "name": "НДС 5%"
    },
    {
      "code": "no_vat",
      "name": "Без НДС"
    }
  ],
  "terms": [
    {
      "code": "in_stock",
      "name": "В наличии",
      "days": 0
    },
    {
      "code": "hours_3",
      "name": "через 3 часа",
      "days": 0
    },
    {
      "code": "tomorrow_am",
      "name": "завтра с утра",
      "days": 1
    },
    {
      "code": "days_2_3",
      "name": "2-3 дня",
      "days": 3
    },
    {
      "code": "days_4_5",
      "name": "4-5 дней",
      "days": 5
    },
    {
      "code": "week",
      "name": "через неделю",
      "days": 7
    },
    {
      "code": "on_order",
      "name": "под заказ",
      "days": null
    }
  ],
  "regions": [
    "Москва",
    "Самарская область",
    "Санкт-Петербург"
  ],
  "request_statuses": [
    "active",
    "done",
    "closed",
    "expired"
  ],
  "proposal_statuses": [
    "draft",
    "sent",
    "partial",
    "accepted",
    "rejected",
    "superseded",
    "withdrawn",
    "closed",
    "done",
    "returned"
  ],
  "line_statuses": [
    "pending",
    "accepted",
    "rejected",
    "closed"
  ],
  "max_proposals": {
    "min": 0,
    "max": 50,
    "cabinet": [
      0
    ]
  },
  "photo_hosts": [
    "disk.yandex.ru",
    "cloud.mail.ru",
    "drive.google.com",
    "imgur.com"
  ],
  "limits": {
    "items_per_request": 100,
    "lines_per_proposal": 200,
    "variants_per_item": 20,
    "accept_lines": 200,
    "requests_per_hour": 60,
    "active_api_requests": 300,
    "replace_min_sec": 300,
    "replaces_per_request": 10,
    "sandbox_active_requests": 20
  }
}

Ошибки: 401, 403, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/dictionary/exchange -H "Authorization: Bearer $K247_TOKEN"

Продавец

POST/api/v1/seller/pricelists

Загрузка файла прайса в конвейер импорта

Право: offersIdempotency-Key: обязателенЛимит: bulk

Загрузка файла прайса в конвейер импорта Юридические ворота (M.2.5): боевой ключ компании, администратор которой не принял документы площадки, — 403 LEGAL_NEEDS.

Параметры

ИмяГдеОбяз.ТипОписание
Idempotency-KeyзаголовокдаstringПовтор с тем же телом возвращает сохранённый ответ (заголовок Idempotent-Replayed: true), с другим телом — 409 Ключ хранится idempotencyTtlHours (24 ч). Позже тот же ключ — новый запрос: создаётся новая заявка, КП или заказ, прежний объект не возвращается. Ключ, которым уже выполнена другая операция в пределах срока, — 409 IDEMPOTENCY_CONFLICT.
modequeryнетstring: replace | upsertРежим
warehousequeryнетstringКод склада по умолчанию
source_idqueryнетstringИсточник компании kind=file|api
filenamequeryнетstringИмя файла для сырого тела

Тело запроса multipart/form-data

ПолеОбяз.ТипОписание
fileдаstring
modeнетstring: replace | upsert
warehouseнетstring
profileнетstringJSON профиля колонок
{
  "file": "price.csv (файл прайса: CSV, XLSX, XLS, XML или ZIP)",
  "mode": "replace",
  "warehouse": "MAIN"
}

Ответ 202 Импорт поставлен в очередь

{
  "import_id": "imp_7d2f1c9a0b",
  "status": "queued",
  "env": "live",
  "mode": "replace",
  "source_id": "src_91ab",
  "status_url": "/api/v1/seller/imports/imp_7d2f1c9a0b"
}

Ошибки: 400, 401, 403, 404, 409, 413, 415, 429, 500, 503

Пример curl

curl -s -X POST "https://k-247.ru/api/v1/seller/pricelists?mode=replace&warehouse=MAIN" -H "Authorization: Bearer $K247_TOKEN" -H "Idempotency-Key: price-2026-09-15" -F "file=@price.csv"

GET/api/v1/seller/imports

Импорты компании

Право: offersЛимит: default

Параметры

ИмяГдеОбяз.ТипОписание
statusqueryнетstringСтатусы через запятую
sincequeryнетstringОбъекты с updated_at строго больше (для людей и отладки; для синхронизации используйте since_seq — время ставится до коммита и не гарантирует полноты)
limitqueryнетinteger
cursorqueryнетstringНепрозрачный курсор из next_cursor

Ответ 200 Список

{
  "items": [
    {
      "import_id": "imp_7d2f1c9a0b",
      "status": "published",
      "phase": "zero",
      "env": "live",
      "channel": "api",
      "mode": "replace",
      "source_id": "src_91ab",
      "filename": "price.csv",
      "rows_total": 48210,
      "counters": {
        "added": 120,
        "updated": 3400,
        "unchanged": 44580,
        "adopted": 0,
        "zeroed": 15,
        "skipped": 110,
        "unknown_brands": 4,
        "skipped_newer": 0
      },
      "held_reason": [],
      "created_at": "2026-09-15T09:00:00.000Z",
      "finished_at": "2026-09-15T09:01:40.000Z"
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s "https://k-247.ru/api/v1/seller/imports?status=held&limit=20" -H "Authorization: Bearer $K247_TOKEN"

GET/api/v1/seller/imports/{id}

Статус и отчёт импорта

Право: offersЛимит: default

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
errors_offsetqueryнетintegerСмещение списка ошибок

Ответ 200 Отчёт

{
  "import_id": "imp_7d2f1c9a0b",
  "status": "held",
  "phase": "check",
  "env": "live",
  "channel": "api",
  "mode": "replace",
  "source_id": "src_91ab",
  "filename": "price.csv",
  "rows_total": 48210,
  "counters": {
    "added": 120,
    "updated": 3400,
    "unchanged": 44580,
    "adopted": 0,
    "zeroed": 15,
    "skipped": 110,
    "unknown_brands": 4,
    "skipped_newer": 0
  },
  "held_reason": [
    {
      "code": "VANISHED",
      "value": 42.1,
      "limit": 30,
      "message": "Пропало 42,1 % позиций (порог 30 %)"
    }
  ],
  "created_at": "2026-09-15T09:00:00.000Z",
  "finished_at": null,
  "format": "csv",
  "encoding": "windows-1251",
  "delimiter": ";",
  "rows_valid": 48100,
  "progress": 48210,
  "checks": {
    "vanished_pct": 42.1,
    "price_change_share": 3.2
  },
  "errors_total": 110,
  "errors": [
    {
      "row": 17,
      "field": "brand",
      "code": "BRAND_UNKNOWN",
      "message": "Бренд «Febi Bilst.» не найден в справочнике",
      "raw": "Febi Bilst.;01-089;Термостат;1234,50;12"
    }
  ],
  "unknown_brands": [
    {
      "sample": "Febi Bilst.",
      "rows": 12
    }
  ],
  "started_at": "2026-09-15T09:00:02.000Z",
  "held_expires_at": "2026-09-16T09:00:40.000Z",
  "error": null
}

Ошибки: 401, 403, 404, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/seller/imports/imp_7d2f1c9a0b -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/seller/imports/{id}/approve

Опубликовать импорт в статусе held

Право: offersIdempotency-Key: рекомендуетсяЛимит: default

Повтор approve уже одобренного — 200 с Idempotent-Replayed: state. Публикация пропускает строки, изменённые другим источником после разбора (counters.skipped_newer). Новый импорт того же источника переводит held в rejected (IMPORT_SUPERSEDED). Юридические ворота (M.2.5): боевой ключ компании, администратор которой не принял документы площадки, — 403 LEGAL_NEEDS.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-Keyзаголовокнетstring

Тело запроса application/json

ПолеОбяз.ТипОписание
commentнетstring
{
  "comment": "Сезонная распродажа, так и задумано"
}

Ответ 200 Импорт публикуется

{
  "import_id": "imp_7d2f1c9a0b",
  "status": "publishing",
  "phase": "zero",
  "env": "live",
  "channel": "api",
  "mode": "replace",
  "source_id": "src_91ab",
  "filename": "price.csv",
  "rows_total": 48210,
  "counters": {
    "added": 120,
    "updated": 3400,
    "unchanged": 44580,
    "adopted": 0,
    "zeroed": 15,
    "skipped": 110,
    "unknown_brands": 4,
    "skipped_newer": 0
  },
  "held_reason": [],
  "created_at": "2026-09-15T09:00:00.000Z",
  "finished_at": null
}

Ошибки: 401, 403, 404, 409, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/seller/imports/imp_7d2f1c9a0b/approve -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"comment":"Сезонная распродажа"}'

POST/api/v1/seller/imports/{id}/reject

Отклонить импорт в статусе held

Право: offersIdempotency-Key: рекомендуетсяЛимит: default

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-Keyзаголовокнетstring

Тело запроса application/json

ПолеОбяз.ТипОписание
commentнетstring
{
  "comment": "Выгрузка неполная"
}

Ответ 200 Импорт отклонён

{
  "import_id": "imp_7d2f1c9a0b",
  "status": "rejected",
  "phase": "zero",
  "env": "live",
  "channel": "api",
  "mode": "replace",
  "source_id": "src_91ab",
  "filename": "price.csv",
  "rows_total": 48210,
  "counters": {
    "added": 120,
    "updated": 3400,
    "unchanged": 44580,
    "adopted": 0,
    "zeroed": 15,
    "skipped": 110,
    "unknown_brands": 4,
    "skipped_newer": 0
  },
  "held_reason": [],
  "created_at": "2026-09-15T09:00:00.000Z",
  "finished_at": "2026-09-15T09:01:40.000Z"
}

Ошибки: 401, 403, 404, 409, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/seller/imports/imp_7d2f1c9a0b/reject -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"comment":"Выгрузка неполная"}'

POST/api/v1/seller/offers/batch

Синхронный upsert до 1000 предложений

Право: offersIdempotency-Key: обязателенЛимит: bulk

Синхронный upsert до 1000 предложений Юридические ворота (M.2.5): боевой ключ компании, администратор которой не принял документы площадки, — 403 LEGAL_NEEDS.

Параметры

ИмяГдеОбяз.ТипОписание
Idempotency-KeyзаголовокдаstringПовтор с тем же телом возвращает сохранённый ответ (заголовок Idempotent-Replayed: true), с другим телом — 409 Ключ хранится idempotencyTtlHours (24 ч). Позже тот же ключ — новый запрос: создаётся новая заявка, КП или заказ, прежний объект не возвращается. Ключ, которым уже выполнена другая операция в пределах срока, — 409 IDEMPOTENCY_CONFLICT.

Тело запроса application/json

ПолеОбяз.ТипОписание
warehouseнетstring
offersдамассив object
{
  "warehouse": "MAIN",
  "offers": [
    {
      "brand": "FEBI BILSTEIN",
      "article": "01-089",
      "name": "Термостат",
      "price_kop": 123450,
      "qty": 12,
      "term_days_min": 1,
      "term_days_max": 3,
      "external_id": "A-778",
      "updated_at": "2026-09-15T08:59:00.000Z"
    },
    {
      "external_id": "A-779",
      "qty": 0
    }
  ]
}

Ответ 200 Результат построчно

{
  "dry_run": false,
  "received": 3,
  "created": 1,
  "updated": 1,
  "unchanged": 0,
  "skipped": 1,
  "results": [
    {
      "index": 0,
      "status": "created",
      "offer_id": "p_3c1f9e2a7b",
      "external_id": "A-778"
    },
    {
      "index": 1,
      "status": "updated",
      "offer_id": "p_77aa01bc3d",
      "external_id": "A-779"
    },
    {
      "index": 2,
      "status": "skipped",
      "offer_id": null,
      "external_id": "A-780",
      "code": "PRICE_BELOW_MIN",
      "field": "price_kop",
      "message": "Цена меньше 1 ₽"
    }
  ],
  "errors": []
}

Ошибки: 400, 401, 403, 404, 409, 413, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/seller/offers/batch -H "Authorization: Bearer $K247_TOKEN" -H "Idempotency-Key: stock-20260915-0900" -H "Content-Type: application/json" -d '{"warehouse":"MAIN","offers":[{"external_id":"A-779","qty":0,"updated_at":"2026-09-15T08:59:00.000Z"}]}'

GET/api/v1/seller/offers

Предложения компании постранично (синхронизация по since)

Право: offersЛимит: default

Сортировка change_seq ASC при since_seq (синхронизация), иначе updated_at ASC.

Параметры

ИмяГдеОбяз.ТипОписание
since_seqqueryнетintegerОбъекты с change_seq строго больше; сортировка change_seq ASC. Опрос без потерь: сохранить change_seq последнего элемента.
sincequeryнетstringОбъекты с updated_at строго больше (для людей и отладки; для синхронизации используйте since_seq — время ставится до коммита и не гарантирует полноты)
warehousequeryнетstring
external_idqueryнетstring
articlequeryнетstring
stalequeryнетinteger: 0 | 1
limitqueryнетinteger
cursorqueryнетstringНепрозрачный курсор из next_cursor

Ответ 200 Страница

{
  "items": [
    {
      "offer_id": "p_3c1f9e2a7b",
      "external_id": "A-778",
      "brand": "FEBI BILSTEIN",
      "brand_id": 17,
      "article": "01-089",
      "article_norm": "01089",
      "name": "Термостат",
      "price_kop": 123450,
      "currency": "RUB",
      "qty": 12,
      "qty_reserved": 2,
      "qty_available": 10,
      "multiplicity": 1,
      "min_order": 1,
      "term_days_min": 1,
      "term_days_max": 3,
      "probability": 95,
      "warehouse": "СПб-1",
      "condition": "new",
      "vat": "НДС 22%",
      "returnable": true,
      "source_id": "src_api01",
      "source_kind": "api",
      "visible": true,
      "stale": false,
      "withdrawn": false,
      "change_seq": 184223,
      "updated_at": "2026-09-15T09:00:01.000Z",
      "source_updated_at": "2026-09-15T08:59:00.000Z"
    }
  ],
  "next_cursor": "eyJzIjoxODQyMjN9",
  "has_more": true
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s "https://k-247.ru/api/v1/seller/offers?since_seq=184000&limit=200" -H "Authorization: Bearer $K247_TOKEN"

DELETE/api/v1/seller/offers/{external_id}

Снять позицию по внешнему коду (остаток 0, отметка withdrawn)

Право: offersЛимит: default

Строка не удаляется: stock = 0 и withdrawn = true; снятая позиция покупателю не показывается, следующий batch или импорт с любым qty её возвращает. external_id в пути — percent-encoded (может содержать / и не-ASCII), 1–64 символа без управляющих. Test-ключ — dry_run.

Параметры

ИмяГдеОбяз.ТипОписание
external_idпутьдаstring
warehousequeryнетstringКод склада; обязателен, если позиция с этим external_id есть на нескольких складах (иначе 409 AMBIGUOUS_OFFER)

Ответ 200 Позиция обнулена

{
  "offer_id": "p_3c1f9e2a7b",
  "external_id": "A-778",
  "warehouse": "MAIN",
  "qty": 0,
  "withdrawn": true
}

Ошибки: 401, 403, 404, 409, 429, 500, 503

Пример curl

curl -s -X DELETE "https://k-247.ru/api/v1/seller/offers/A-778?warehouse=MAIN" -H "Authorization: Bearer $K247_TOKEN"

GET/api/v1/seller/warehouses

Склады компании

Право: offersЛимит: default

Ответ 200 Склады

{
  "items": [
    {
      "code": "MAIN",
      "name": "Санкт-Петербург",
      "city": "Санкт-Петербург",
      "city_id": 2,
      "region": "Санкт-Петербург",
      "country": "RU",
      "address": "ул. Софийская, 8",
      "is_default": true,
      "active": true,
      "delivery_terms": {
        "extra_days": 0,
        "note": ""
      },
      "offers": 1240
    }
  ]
}

Ошибки: 401, 403, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/seller/warehouses -H "Authorization: Bearer $K247_TOKEN"

PUT/api/v1/seller/warehouses

Полный список складов (upsert по коду)

Право: offersЛимит: default

Тело запроса application/json

ПолеОбяз.ТипОписание
itemsдамассив object
{
  "items": [
    {
      "code": "MAIN",
      "name": "Основной",
      "city": "Санкт-Петербург",
      "region": "Санкт-Петербург",
      "country": "RU",
      "address": "ул. Софийская, 8",
      "is_default": true,
      "delivery_terms": {
        "extra_days": 0
      }
    }
  ]
}

Ответ 200 Склады

{
  "items": [
    {
      "code": "MAIN",
      "name": "Санкт-Петербург",
      "city": "Санкт-Петербург",
      "city_id": 2,
      "region": "Санкт-Петербург",
      "country": "RU",
      "address": "ул. Софийская, 8",
      "is_default": true,
      "active": true,
      "delivery_terms": {
        "extra_days": 0,
        "note": ""
      },
      "offers": 1240
    }
  ]
}

Ошибки: 400, 401, 403, 409, 429, 500, 503

Пример curl

curl -s -X PUT https://k-247.ru/api/v1/seller/warehouses -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"items":[{"code":"MAIN","name":"Основной","city":"Санкт-Петербург","country":"RU","is_default":true}]}'

GET/api/v1/seller/orders

Заказы продавца (опрос по since)

Право: orders | exchangeЛимит: default

Live-ключ — заказы test=false. Test-ключ — только test-заказы песочницы своей компании (покупатель c_sandbox). Сортировка change_seq ASC при since_seq, иначе updated_at ASC. Ключ с правом exchange без orders видит только заказы из КП (source exchange), остальные — 404.

Параметры

ИмяГдеОбяз.ТипОписание
statusqueryнетstringКоды статусов через запятую; awaiting_confirmation — есть строки на подтверждении
sourcequeryнетstring: shop | exchangeshop — заказы магазина, exchange — заказы из КП биржи
since_seqqueryнетintegerОбъекты с change_seq строго больше; сортировка change_seq ASC. Опрос без потерь: сохранить change_seq последнего элемента.
sincequeryнетstringОбъекты с updated_at строго больше (для людей и отладки; для синхронизации используйте since_seq — время ставится до коммита и не гарантирует полноты)
limitqueryнетinteger
cursorqueryнетstringНепрозрачный курсор из next_cursor

Ответ 200 Страница

{
  "items": [
    {
      "order_id": "ord_5e1a2b3c4d",
      "number": "2609-1190",
      "status": "awaiting_confirmation",
      "status_name": "Ожидает подтверждения",
      "test": false,
      "created_at": "2026-09-15T09:05:00.000Z",
      "updated_at": "2026-09-15T09:05:00.000Z",
      "change_seq": 184230,
      "confirm_deadline_at": "2026-09-15T11:05:00.000Z",
      "auto_cancel_at": "2026-09-15T13:05:00.000Z",
      "buyer": {
        "code": "Покупатель",
        "city": "Санкт-Петербург"
      },
      "delivery": {
        "type": "основной",
        "address": "ул. Салова, 27",
        "express": false
      },
      "payment": "с баланса",
      "comment": "",
      "currency": "RUB",
      "total_kop": 246900,
      "delivery_kop": 0,
      "accounting_total_rub": 2469,
      "lines": [
        {
          "line_id": "l_9a8b7c6d5e",
          "offer_id": "p_3c1f9e2a7b",
          "external_id": "A-778",
          "brand": "FEBI BILSTEIN",
          "article": "01-089",
          "name": "Термостат",
          "qty": 2,
          "confirmed_qty": null,
          "shipped_qty": 0,
          "price_kop": 123450,
          "sum_kop": 246900,
          "status": "awaiting_confirmation",
          "reject_reason_code": null,
          "term_days_min": 1,
          "term_days_max": 3,
          "term_days_confirmed": null,
          "warehouse": "СПб-1",
          "external_order_ref": null,
          "plate": ""
        }
      ],
      "documents": {
        "invoice": "",
        "upd": "",
        "ksf": ""
      }
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s "https://k-247.ru/api/v1/seller/orders?status=awaiting_confirmation&since_seq=184000" -H "Authorization: Bearer $K247_TOKEN"

GET/api/v1/seller/orders/{id}

Заказ продавца

Право: orders | exchangeЛимит: default

Заказ продавца Ключ с правом exchange без orders видит только заказы из КП (source exchange), остальные — 404.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring

Ответ 200 Заказ

{
  "order_id": "ord_5e1a2b3c4d",
  "number": "2609-1190",
  "status": "awaiting_confirmation",
  "status_name": "Ожидает подтверждения",
  "test": false,
  "created_at": "2026-09-15T09:05:00.000Z",
  "updated_at": "2026-09-15T09:05:00.000Z",
  "change_seq": 184230,
  "confirm_deadline_at": "2026-09-15T11:05:00.000Z",
  "auto_cancel_at": "2026-09-15T13:05:00.000Z",
  "buyer": {
    "code": "Покупатель",
    "city": "Санкт-Петербург"
  },
  "delivery": {
    "type": "основной",
    "address": "ул. Салова, 27",
    "express": false
  },
  "payment": "с баланса",
  "comment": "",
  "currency": "RUB",
  "total_kop": 246900,
  "delivery_kop": 0,
  "accounting_total_rub": 2469,
  "lines": [
    {
      "line_id": "l_9a8b7c6d5e",
      "offer_id": "p_3c1f9e2a7b",
      "external_id": "A-778",
      "brand": "FEBI BILSTEIN",
      "article": "01-089",
      "name": "Термостат",
      "qty": 2,
      "confirmed_qty": null,
      "shipped_qty": 0,
      "price_kop": 123450,
      "sum_kop": 246900,
      "status": "awaiting_confirmation",
      "reject_reason_code": null,
      "term_days_min": 1,
      "term_days_max": 3,
      "term_days_confirmed": null,
      "warehouse": "СПб-1",
      "external_order_ref": null,
      "plate": ""
    }
  ],
  "documents": {
    "invoice": "",
    "upd": "",
    "ksf": ""
  }
}

Ошибки: 401, 403, 404, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/seller/orders/ord_5e1a2b3c4d -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/seller/orders/{id}/confirm

Подтверждение целиком или построчно

Право: orders | exchangeIdempotency-Key: рекомендуетсяЛимит: default

Идемпотентность по состоянию: если все переданные строки уже в целевом статусе с теми же параметрами, ответ 200 с текущим заказом и заголовком Idempotent-Replayed: state; другой статус или другие параметры — 409 INVALID_STATE, ничего не меняется. Ключ с правом exchange без orders видит только заказы из КП (source exchange), остальные — 404. Заказ из КП биржи не подтверждается и не отклоняется: 409 EXCHANGE_ORDER, в details — {source, proposal_id, actions}.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-Keyзаголовокнетstring

Тело запроса application/json

ПолеОбяз.ТипОписание
linesнетмассив object
{
  "lines": [
    {
      "line_id": "l_9a8b7c6d5e",
      "qty": 1,
      "term_days": 2,
      "external_order_ref": "ЗК-5512"
    }
  ]
}

Ответ 200 Заказ после подтверждения

{
  "order_id": "ord_5e1a2b3c4d",
  "number": "2609-1190",
  "status": "assembling",
  "status_name": "В сборке",
  "test": false,
  "created_at": "2026-09-15T09:05:00.000Z",
  "updated_at": "2026-09-15T09:05:00.000Z",
  "change_seq": 184230,
  "confirm_deadline_at": "2026-09-15T11:05:00.000Z",
  "auto_cancel_at": "2026-09-15T13:05:00.000Z",
  "buyer": {
    "code": "Покупатель",
    "city": "Санкт-Петербург"
  },
  "delivery": {
    "type": "основной",
    "address": "ул. Салова, 27",
    "express": false
  },
  "payment": "с баланса",
  "comment": "",
  "currency": "RUB",
  "total_kop": 123450,
  "delivery_kop": 0,
  "accounting_total_rub": 1234.5,
  "lines": [
    {
      "line_id": "l_9a8b7c6d5e",
      "offer_id": "p_3c1f9e2a7b",
      "external_id": "A-778",
      "brand": "FEBI BILSTEIN",
      "article": "01-089",
      "name": "Термостат",
      "qty": 2,
      "confirmed_qty": 1,
      "shipped_qty": 0,
      "price_kop": 123450,
      "sum_kop": 123450,
      "status": "confirmed",
      "reject_reason_code": "partial_stock",
      "term_days_min": 1,
      "term_days_max": 3,
      "term_days_confirmed": 2,
      "warehouse": "СПб-1",
      "external_order_ref": "ЗК-5512",
      "plate": ""
    }
  ],
  "documents": {
    "invoice": "",
    "upd": "",
    "ksf": ""
  }
}

Ошибки: 400, 401, 403, 404, 409, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/seller/orders/ord_5e1a2b3c4d/confirm -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"lines":[{"line_id":"l_9a8b7c6d5e","qty":1,"term_days":2}]}'

POST/api/v1/seller/orders/{id}/reject

Отказ по заказу или строкам

Право: orders | exchangeIdempotency-Key: рекомендуетсяЛимит: default

Идемпотентность по состоянию: если все переданные строки уже в целевом статусе с теми же параметрами, ответ 200 с текущим заказом и заголовком Idempotent-Replayed: state; другой статус или другие параметры — 409 INVALID_STATE, ничего не меняется. Ключ с правом exchange без orders видит только заказы из КП (source exchange), остальные — 404. Заказ из КП биржи не подтверждается и не отклоняется: 409 EXCHANGE_ORDER, в details — {source, proposal_id, actions}.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-Keyзаголовокнетstring

Тело запроса application/json

ПолеОбяз.ТипОписание
reasonнетstringЗначения: out_of_stock, price_changed, discontinued, wrong_item, cannot_deliver, duplicate, other
commentнетstring
linesнетмассив object
{
  "lines": [
    {
      "line_id": "l_9a8b7c6d5e",
      "reason": "out_of_stock",
      "comment": "Последнюю продали в рознице"
    }
  ]
}

Ответ 200 Заказ после отказа

{
  "order_id": "ord_5e1a2b3c4d",
  "number": "2609-1190",
  "status": "cancelled",
  "status_name": "Отменён",
  "test": false,
  "created_at": "2026-09-15T09:05:00.000Z",
  "updated_at": "2026-09-15T09:05:00.000Z",
  "change_seq": 184230,
  "confirm_deadline_at": "2026-09-15T11:05:00.000Z",
  "auto_cancel_at": "2026-09-15T13:05:00.000Z",
  "buyer": {
    "code": "Покупатель",
    "city": "Санкт-Петербург"
  },
  "delivery": {
    "type": "основной",
    "address": "ул. Салова, 27",
    "express": false
  },
  "payment": "с баланса",
  "comment": "",
  "currency": "RUB",
  "total_kop": 0,
  "delivery_kop": 0,
  "accounting_total_rub": 0,
  "lines": [
    {
      "line_id": "l_9a8b7c6d5e",
      "offer_id": "p_3c1f9e2a7b",
      "external_id": "A-778",
      "brand": "FEBI BILSTEIN",
      "article": "01-089",
      "name": "Термостат",
      "qty": 2,
      "confirmed_qty": null,
      "shipped_qty": 0,
      "price_kop": 123450,
      "sum_kop": 0,
      "status": "rejected",
      "reject_reason_code": "out_of_stock",
      "term_days_min": 1,
      "term_days_max": 3,
      "term_days_confirmed": null,
      "warehouse": "СПб-1",
      "external_order_ref": null,
      "plate": ""
    }
  ],
  "documents": {
    "invoice": "",
    "upd": "",
    "ksf": ""
  }
}

Ошибки: 400, 401, 403, 404, 409, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/seller/orders/ord_5e1a2b3c4d/reject -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"lines":[{"line_id":"l_9a8b7c6d5e","reason":"out_of_stock"}]}'

POST/api/v1/seller/orders/{id}/ship

Отгрузка, в том числе частичная по строкам

Право: orders | exchangeIdempotency-Key: рекомендуетсяЛимит: default

Идемпотентность по состоянию: если все переданные строки уже в целевом статусе с теми же параметрами, ответ 200 с текущим заказом и заголовком Idempotent-Replayed: state; другой статус или другие параметры — 409 INVALID_STATE, ничего не меняется. Ключ с правом exchange без orders видит только заказы из КП (source exchange), остальные — 404. Заказ из КП биржи (source exchange) отгружается целиком: lines на отгрузку не влияют, но переданные line_id должны быть из этого заказа, иначе 400 VALIDATION_FAILED; у строк такого заказа status — null, shipped_qty — 0, отгрузка видна по status заказа (shipped).

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-Keyзаголовокнетstring

Тело запроса application/json

ПолеОбяз.ТипОписание
linesнетмассив stringСтроки для частичной отгрузки. У заказа из КП биржи (source exchange) на отгрузку не влияют — отгружается весь заказ, но каждый line_id должен быть из этого заказа, иначе 400 VALIDATION_FAILED.
trackнетstring
upd_numberнетstring
{
  "lines": [
    "l_9a8b7c6d5e"
  ],
  "track": "CDEK 1234567890",
  "upd_number": "УПД-2609-1190"
}

Ответ 200 Заказ после отгрузки

{
  "order_id": "ord_5e1a2b3c4d",
  "number": "2609-1190",
  "status": "shipped",
  "status_name": "Отгружен",
  "test": false,
  "created_at": "2026-09-15T09:05:00.000Z",
  "updated_at": "2026-09-15T09:05:00.000Z",
  "change_seq": 184230,
  "confirm_deadline_at": "2026-09-15T11:05:00.000Z",
  "auto_cancel_at": "2026-09-15T13:05:00.000Z",
  "buyer": {
    "code": "Покупатель",
    "city": "Санкт-Петербург"
  },
  "delivery": {
    "type": "основной",
    "address": "ул. Салова, 27",
    "express": false
  },
  "payment": "с баланса",
  "comment": "",
  "currency": "RUB",
  "total_kop": 246900,
  "delivery_kop": 0,
  "accounting_total_rub": 2469,
  "lines": [
    {
      "line_id": "l_9a8b7c6d5e",
      "offer_id": "p_3c1f9e2a7b",
      "external_id": "A-778",
      "brand": "FEBI BILSTEIN",
      "article": "01-089",
      "name": "Термостат",
      "qty": 2,
      "confirmed_qty": 2,
      "shipped_qty": 2,
      "price_kop": 123450,
      "sum_kop": 246900,
      "status": "shipped",
      "reject_reason_code": null,
      "term_days_min": 1,
      "term_days_max": 3,
      "term_days_confirmed": null,
      "warehouse": "СПб-1",
      "external_order_ref": null,
      "plate": ""
    }
  ],
  "documents": {
    "invoice": "",
    "upd": "",
    "ksf": ""
  }
}

Ошибки: 400, 401, 403, 404, 409, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/seller/orders/ord_5e1a2b3c4d/ship -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"track":"CDEK 1234567890"}'

Биржа: продавец

GET/api/v1/seller/exchange/requests

Лента заявок покупателей (набор — как у сотрудника в кабинете), синхронизация по since_seq

Право: exchangeЛимит: default

Набор: заявки не своей компании, видимые продавцу (реквизиты проверены, биржа по типу компании, регион заявки = регион компании у ключа own) или с КП компании в любом статусе; test-ключ — только тестовые заявки робота для своей компании. Без since_seq и status — действующие заявки и заявки с КП компании; с since_seq — весь набор в любом статусе. Покупатель — псевдоним buyer.ref; госномера и адреса получения нет, свободный текст очищен от контактов.

Параметры

ИмяГдеОбяз.ТипОписание
statusqueryнетstringЧерез запятую: active, done, closed, expired или all
exchangequeryнетstring: new | used
vgroupqueryнетstringГруппа техники из справочника. Заявки группы «Иное» (other) проходят фильтр любой группы; vgroup=other — только они Значения: car, truck, special, equip, other
marketqueryнетstringРынок из справочника. Фильтр проходят и заявки с рынком «Иное» — легковые и грузовые без рынка (market = null), и заявки группы «Иное» Значения: eu, cn, jp, kr, us, ru
brandqueryнетstringМарка без учёта регистра
regionqueryнетstring: own | allown — сузить ленту ключа all; all ключом own — 400
answeredqueryнетstring: true | falsetrue — только с КП компании, false — без КП
since_seqqueryнетintegerОбъекты с change_seq строго больше; сортировка change_seq ASC. Опрос без потерь: сохранить change_seq последнего элемента.
sincequeryнетstringОбъекты с updated_at строго больше (для людей и отладки; для синхронизации используйте since_seq — время ставится до коммита и не гарантирует полноты)
limitqueryнетinteger
cursorqueryнетstringНепрозрачный курсор из next_cursor

Ответ 200 Страница заявок

{
  "items": [
    {
      "exchange_request_id": "r_8c1d2e3f4a",
      "number": "2609-412",
      "status": "active",
      "exchange": "new",
      "test": false,
      "created_at": "2026-09-22T07:10:00.000Z",
      "updated_at": "2026-09-22T07:42:11.000Z",
      "revision": 0,
      "revised_at": null,
      "expires_at": "2026-09-22T20:59:59.000Z",
      "deadline": "today",
      "deadline_name": "сегодня",
      "priority": "fast",
      "priority_name": "быстрее",
      "vehicle": {
        "vgroup": "truck",
        "market": "eu",
        "category": "Грузовые / Европа",
        "brand": "MAN",
        "model": "TGX",
        "year": "2016",
        "vin": "WMA06XZZ0FP000001"
      },
      "buyer": {
        "ref": "П-7F3A9C",
        "city": "Санкт-Петербург"
      },
      "city": "Санкт-Петербург",
      "region": "Санкт-Петербург",
      "delivery": "main",
      "delivery_name": "основной",
      "pickup": "delivery",
      "pickup_name": "доставка",
      "payment": "invoice",
      "payment_name": "счёт",
      "comment": "Машина на линии, звонить [скрыто]",
      "items": [
        {
          "item_id": "i_1a2b3c4d5e",
          "position": 1,
          "name": "Фильтр масляный",
          "qty": 2,
          "comment": "по VIN",
          "open": true
        },
        {
          "item_id": "i_2b3c4d5e6f",
          "position": 2,
          "name": "Ремень приводной",
          "qty": 1,
          "comment": "",
          "open": true
        }
      ],
      "max_proposals": 5,
      "competition": {
        "proposals": 3,
        "sellers": 3,
        "limit": 5,
        "slots_left": 2,
        "my_rank": 2,
        "my_value_kop": 452900,
        "basis": "accept_plan"
      },
      "my_proposals": [
        {
          "proposal_id": "o_5f6a7b8c9d",
          "number": "ТТ-014-00131",
          "status": "sent",
          "value_kop": 452900
        }
      ],
      "current_proposal_id": "o_5f6a7b8c9d",
      "can_propose": true,
      "cannot_propose_reason": null,
      "replace_available_at": null,
      "unread_messages": 1,
      "change_seq": 184300
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s "https://k-247.ru/api/v1/seller/exchange/requests?since_seq=0&limit=100" -H "Authorization: Bearer $K247_TOKEN"

GET/api/v1/seller/exchange/requests/{id}

Заявка набора продавца в любом статусе

Право: exchangeЛимит: default

Заявки нет, она другого окружения или не входит в набор — 404 (недоступное не отличается от несуществующего).

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring

Ответ 200 Заявка

{
  "exchange_request_id": "r_8c1d2e3f4a",
  "number": "2609-412",
  "status": "active",
  "exchange": "new",
  "test": false,
  "created_at": "2026-09-22T07:10:00.000Z",
  "updated_at": "2026-09-22T07:42:11.000Z",
  "revision": 0,
  "revised_at": null,
  "expires_at": "2026-09-22T20:59:59.000Z",
  "deadline": "today",
  "deadline_name": "сегодня",
  "priority": "fast",
  "priority_name": "быстрее",
  "vehicle": {
    "vgroup": "truck",
    "market": "eu",
    "category": "Грузовые / Европа",
    "brand": "MAN",
    "model": "TGX",
    "year": "2016",
    "vin": "WMA06XZZ0FP000001"
  },
  "buyer": {
    "ref": "П-7F3A9C",
    "city": "Санкт-Петербург"
  },
  "city": "Санкт-Петербург",
  "region": "Санкт-Петербург",
  "delivery": "main",
  "delivery_name": "основной",
  "pickup": "delivery",
  "pickup_name": "доставка",
  "payment": "invoice",
  "payment_name": "счёт",
  "comment": "Машина на линии, звонить [скрыто]",
  "items": [
    {
      "item_id": "i_1a2b3c4d5e",
      "position": 1,
      "name": "Фильтр масляный",
      "qty": 2,
      "comment": "по VIN",
      "open": true
    },
    {
      "item_id": "i_2b3c4d5e6f",
      "position": 2,
      "name": "Ремень приводной",
      "qty": 1,
      "comment": "",
      "open": true
    }
  ],
  "max_proposals": 5,
  "competition": {
    "proposals": 3,
    "sellers": 3,
    "limit": 5,
    "slots_left": 2,
    "my_rank": 2,
    "my_value_kop": 452900,
    "basis": "accept_plan"
  },
  "my_proposals": [
    {
      "proposal_id": "o_5f6a7b8c9d",
      "number": "ТТ-014-00131",
      "status": "sent",
      "value_kop": 452900
    }
  ],
  "current_proposal_id": "o_5f6a7b8c9d",
  "can_propose": true,
  "cannot_propose_reason": null,
  "replace_available_at": null,
  "unread_messages": 1,
  "change_seq": 184300
}

Ошибки: 401, 403, 404, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/seller/exchange/requests/r_8c1d2e3f4a -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/seller/exchange/requests/{id}/proposals

Отправить КП покупателю (mode send) или сохранить черновик (mode draft)

Право: exchangeIdempotency-Key: обязателенЛимит: default

Те же проверки, что у кнопок кабинета: доступ к заявке, срок, лимит КП (считает продавцов), строки по позициям заявки, строка по выкупленной позиции — 409 ALREADY_BOUGHT, контакты в тексте — 422 CONTACTS_FORBIDDEN. Повторная отправка заменяет КП sent (superseded); частично купленное КП живёт рядом. Замена через API — не чаще раза в 5 минут и не больше 10 на заявку (409 PROPOSAL_REPLACE_LIMIT). Юридические ворота: боевой ключ компании без принятых документов — 403 LEGAL_NEEDS. Предел замен (M.10.2) считает повторные отправки КП компании по заявке: замену действующего КП (superseded) и отправку после снятия своего отправленного КП (withdrawn). Снятые черновики, которые не отправлялись, не считаются. Тексты КП (комментарий, строки, путь ссылки на фото) не должны содержать контактов и сведений о продавце: реквизитов, названия, адреса компании и складов, фамилий сотрудников — 422 CONTACTS_FORBIDDEN с kind в details.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-KeyзаголовокдаstringПовтор с тем же телом возвращает сохранённый ответ (заголовок Idempotent-Replayed: true), с другим телом — 409 Ключ хранится idempotencyTtlHours (24 ч). Позже тот же ключ — новый запрос: создаётся новая заявка, КП или заказ, прежний объект не возвращается. Ключ, которым уже выполнена другая операция в пределах срока, — 409 IDEMPOTENCY_CONFLICT.

Тело запроса application/json

ПолеОбяз.ТипОписание
modeнетstring: send | draft
replaces_proposal_idнетstringЗащита от гонок: должен совпасть с current_proposal_id заявки
commentнетstring
linesдамассив objectНе больше 20 вариантов на позицию
{
  "mode": "send",
  "comment": "Отгрузка со склада СПб в день оплаты",
  "lines": [
    {
      "item_id": "i_1a2b3c4d5e",
      "brand": "MANN",
      "article": "W 950/26",
      "oem": "51.05504-0108",
      "price_kop": 123450,
      "qty": 2,
      "vat": "vat22",
      "term": "in_stock",
      "stock": 12,
      "comment": "оригинальная упаковка",
      "external_id": "A-778"
    },
    {
      "item_id": "i_1a2b3c4d5e",
      "brand": "FILTRON",
      "article": "OP 592/7",
      "price_kop": 98000,
      "qty": 2,
      "vat": "vat22",
      "term_days": 2
    },
    {
      "item_id": "i_2b3c4d5e6f",
      "brand": "Contitech",
      "article": "6PK1873",
      "price_kop": 210000,
      "qty": 1,
      "vat": "vat22",
      "term": "in_stock",
      "photo_url": "https://disk.yandex.ru/i/Ab3dE6fG"
    }
  ]
}

Ответ 201 КП продавца

{
  "proposal_id": "o_6a7b8c9d0e",
  "number": "ТТ-014-00132",
  "exchange_request_id": "r_8c1d2e3f4a",
  "status": "sent",
  "test": false,
  "via": "api",
  "replaces_proposal_id": null,
  "created_at": "2026-09-22T07:45:02.000Z",
  "updated_at": "2026-09-22T07:45:02.000Z",
  "comment": "Отгрузка со склада СПб в день оплаты",
  "value_kop": 406000,
  "lines_sum_kop": 652900,
  "coverage": {
    "items_open": 2,
    "items_offered": 2,
    "items_full": 2
  },
  "lines": [
    {
      "proposal_line_id": "ol_91a2b3c4d5",
      "item_id": "i_1a2b3c4d5e",
      "brand": "MANN",
      "article": "W 950/26",
      "oem": "51.05504-0108",
      "price_kop": 123450,
      "qty": 2,
      "sum_kop": 246900,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "in_stock",
      "term_name": "В наличии",
      "term_days": 0,
      "availability": "in_stock",
      "stock": 12,
      "comment": "оригинальная упаковка",
      "photo_url": null,
      "external_id": "A-778",
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 2,
      "counter_price_kop": null
    },
    {
      "proposal_line_id": "ol_92b3c4d5e6",
      "item_id": "i_1a2b3c4d5e",
      "brand": "FILTRON",
      "article": "OP 592/7",
      "oem": "",
      "price_kop": 98000,
      "qty": 2,
      "sum_kop": 196000,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "days_2_3",
      "term_name": "2-3 дня",
      "term_days": 2,
      "availability": "on_order",
      "stock": 0,
      "comment": "",
      "photo_url": null,
      "external_id": null,
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 2,
      "counter_price_kop": null
    },
    {
      "proposal_line_id": "ol_93c4d5e6f7",
      "item_id": "i_2b3c4d5e6f",
      "brand": "Contitech",
      "article": "6PK1873",
      "oem": "",
      "price_kop": 210000,
      "qty": 1,
      "sum_kop": 210000,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "in_stock",
      "term_name": "В наличии",
      "term_days": 0,
      "availability": "in_stock",
      "stock": 0,
      "comment": "",
      "photo_url": "https://disk.yandex.ru/i/Ab3dE6fG",
      "external_id": null,
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 1,
      "counter_price_kop": null
    }
  ],
  "orders": [],
  "actions": [
    "withdraw"
  ],
  "counter": null,
  "competition": {
    "proposals": 3,
    "sellers": 3,
    "limit": 5,
    "slots_left": 2,
    "my_rank": 1,
    "my_value_kop": 406000,
    "basis": "accept_plan"
  },
  "change_seq": 184311
}

Ошибки: 400, 401, 403, 404, 409, 413, 415, 422, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/seller/exchange/requests/r_8c1d2e3f4a/proposals -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: kp-2609-412-1" -d '{"comment":"Отгрузка в день оплаты","lines":[{"item_id":"i_1a2b3c4d5e","brand":"MANN","article":"W 950/26","price_kop":123450,"qty":2,"vat":"vat22","term":"in_stock","external_id":"A-778"},{"item_id":"i_2b3c4d5e6f","brand":"Contitech","article":"6PK1873","price_kop":210000,"qty":1,"photo_url":"https://disk.yandex.ru/i/Ab3dE6fG"}]}'

GET/api/v1/seller/exchange/proposals

КП компании в любом статусе, включая черновики и КП из кабинета (via cabinet)

Право: exchangeЛимит: default

Параметры

ИмяГдеОбяз.ТипОписание
exchange_request_idqueryнетstring
statusqueryнетstringЧерез запятую, по умолчанию все
since_seqqueryнетintegerОбъекты с change_seq строго больше; сортировка change_seq ASC. Опрос без потерь: сохранить change_seq последнего элемента.
sincequeryнетstringОбъекты с updated_at строго больше (для людей и отладки; для синхронизации используйте since_seq — время ставится до коммита и не гарантирует полноты)
limitqueryнетinteger
cursorqueryнетstringНепрозрачный курсор из next_cursor

Ответ 200 Страница КП (без competition)

{
  "items": [
    {
      "proposal_id": "o_6a7b8c9d0e",
      "number": "ТТ-014-00132",
      "exchange_request_id": "r_8c1d2e3f4a",
      "status": "sent",
      "test": false,
      "via": "api",
      "replaces_proposal_id": null,
      "created_at": "2026-09-22T07:45:02.000Z",
      "updated_at": "2026-09-22T07:45:02.000Z",
      "comment": "Отгрузка со склада СПб в день оплаты",
      "value_kop": 406000,
      "lines_sum_kop": 652900,
      "coverage": {
        "items_open": 2,
        "items_offered": 2,
        "items_full": 2
      },
      "lines": [
        {
          "proposal_line_id": "ol_91a2b3c4d5",
          "item_id": "i_1a2b3c4d5e",
          "brand": "MANN",
          "article": "W 950/26",
          "oem": "51.05504-0108",
          "price_kop": 123450,
          "qty": 2,
          "sum_kop": 246900,
          "vat": "vat22",
          "vat_name": "НДС 22%",
          "term": "in_stock",
          "term_name": "В наличии",
          "term_days": 0,
          "availability": "in_stock",
          "stock": 12,
          "comment": "оригинальная упаковка",
          "photo_url": null,
          "external_id": "A-778",
          "status": "pending",
          "bought_qty": 0,
          "left_qty": 2,
          "counter_price_kop": null
        },
        {
          "proposal_line_id": "ol_92b3c4d5e6",
          "item_id": "i_1a2b3c4d5e",
          "brand": "FILTRON",
          "article": "OP 592/7",
          "oem": "",
          "price_kop": 98000,
          "qty": 2,
          "sum_kop": 196000,
          "vat": "vat22",
          "vat_name": "НДС 22%",
          "term": "days_2_3",
          "term_name": "2-3 дня",
          "term_days": 2,
          "availability": "on_order",
          "stock": 0,
          "comment": "",
          "photo_url": null,
          "external_id": null,
          "status": "pending",
          "bought_qty": 0,
          "left_qty": 2,
          "counter_price_kop": null
        },
        {
          "proposal_line_id": "ol_93c4d5e6f7",
          "item_id": "i_2b3c4d5e6f",
          "brand": "Contitech",
          "article": "6PK1873",
          "oem": "",
          "price_kop": 210000,
          "qty": 1,
          "sum_kop": 210000,
          "vat": "vat22",
          "vat_name": "НДС 22%",
          "term": "in_stock",
          "term_name": "В наличии",
          "term_days": 0,
          "availability": "in_stock",
          "stock": 0,
          "comment": "",
          "photo_url": "https://disk.yandex.ru/i/Ab3dE6fG",
          "external_id": null,
          "status": "pending",
          "bought_qty": 0,
          "left_qty": 1,
          "counter_price_kop": null
        }
      ],
      "orders": [],
      "actions": [
        "withdraw"
      ],
      "counter": null,
      "change_seq": 184311
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s "https://k-247.ru/api/v1/seller/exchange/proposals?since_seq=0" -H "Authorization: Bearer $K247_TOKEN"

GET/api/v1/seller/exchange/proposals/{id}

КП компании

Право: exchangeЛимит: default

Чужое КП и КП другого окружения — 404.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring

Ответ 200 КП

{
  "proposal_id": "o_6a7b8c9d0e",
  "number": "ТТ-014-00132",
  "exchange_request_id": "r_8c1d2e3f4a",
  "status": "sent",
  "test": false,
  "via": "api",
  "replaces_proposal_id": null,
  "created_at": "2026-09-22T07:45:02.000Z",
  "updated_at": "2026-09-22T07:45:02.000Z",
  "comment": "Отгрузка со склада СПб в день оплаты",
  "value_kop": 406000,
  "lines_sum_kop": 652900,
  "coverage": {
    "items_open": 2,
    "items_offered": 2,
    "items_full": 2
  },
  "lines": [
    {
      "proposal_line_id": "ol_91a2b3c4d5",
      "item_id": "i_1a2b3c4d5e",
      "brand": "MANN",
      "article": "W 950/26",
      "oem": "51.05504-0108",
      "price_kop": 123450,
      "qty": 2,
      "sum_kop": 246900,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "in_stock",
      "term_name": "В наличии",
      "term_days": 0,
      "availability": "in_stock",
      "stock": 12,
      "comment": "оригинальная упаковка",
      "photo_url": null,
      "external_id": "A-778",
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 2,
      "counter_price_kop": null
    },
    {
      "proposal_line_id": "ol_92b3c4d5e6",
      "item_id": "i_1a2b3c4d5e",
      "brand": "FILTRON",
      "article": "OP 592/7",
      "oem": "",
      "price_kop": 98000,
      "qty": 2,
      "sum_kop": 196000,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "days_2_3",
      "term_name": "2-3 дня",
      "term_days": 2,
      "availability": "on_order",
      "stock": 0,
      "comment": "",
      "photo_url": null,
      "external_id": null,
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 2,
      "counter_price_kop": null
    },
    {
      "proposal_line_id": "ol_93c4d5e6f7",
      "item_id": "i_2b3c4d5e6f",
      "brand": "Contitech",
      "article": "6PK1873",
      "oem": "",
      "price_kop": 210000,
      "qty": 1,
      "sum_kop": 210000,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "in_stock",
      "term_name": "В наличии",
      "term_days": 0,
      "availability": "in_stock",
      "stock": 0,
      "comment": "",
      "photo_url": "https://disk.yandex.ru/i/Ab3dE6fG",
      "external_id": null,
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 1,
      "counter_price_kop": null
    }
  ],
  "orders": [],
  "actions": [
    "withdraw"
  ],
  "counter": null,
  "competition": {
    "proposals": 3,
    "sellers": 3,
    "limit": 5,
    "slots_left": 2,
    "my_rank": 1,
    "my_value_kop": 406000,
    "basis": "accept_plan"
  },
  "change_seq": 184311
}

Ошибки: 401, 403, 404, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/seller/exchange/proposals/o_6a7b8c9d0e -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/seller/exchange/proposals/{id}/withdraw

Снять КП

Право: exchangeIdempotency-Key: рекомендуетсяЛимит: default

Снять можно черновик и КП sent, по которому ничего не куплено: купленное — 409 INVALID_STATE, заменённое или закрытое — 409 PROPOSAL_NOT_ACTIVE. Уже снятое — 200 и Idempotent-Replayed: state. Юридические ворота не действуют. Снятие отправленного КП не обнуляет предел замен API (M.10.2): следующая отправка по этой заявке считается заменой — не раньше replace_min_sec после прошлой отправки и не больше replaces_per_request раз (409 PROPOSAL_REPLACE_LIMIT; в заявке can_propose: false, cannot_propose_reason: replace_limit, replace_available_at). Снятый черновик, который ни разу не отправлялся, заменой не считается.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-Keyзаголовокнетstring

Ответ 200 КП со статусом withdrawn

{
  "proposal_id": "o_6a7b8c9d0e",
  "number": "ТТ-014-00132",
  "exchange_request_id": "r_8c1d2e3f4a",
  "status": "withdrawn",
  "test": false,
  "via": "api",
  "replaces_proposal_id": null,
  "created_at": "2026-09-22T07:45:02.000Z",
  "updated_at": "2026-09-22T07:45:02.000Z",
  "comment": "Отгрузка со склада СПб в день оплаты",
  "value_kop": 406000,
  "lines_sum_kop": 652900,
  "coverage": {
    "items_open": 2,
    "items_offered": 2,
    "items_full": 2
  },
  "lines": [
    {
      "proposal_line_id": "ol_91a2b3c4d5",
      "item_id": "i_1a2b3c4d5e",
      "brand": "MANN",
      "article": "W 950/26",
      "oem": "51.05504-0108",
      "price_kop": 123450,
      "qty": 2,
      "sum_kop": 246900,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "in_stock",
      "term_name": "В наличии",
      "term_days": 0,
      "availability": "in_stock",
      "stock": 12,
      "comment": "оригинальная упаковка",
      "photo_url": null,
      "external_id": "A-778",
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 2,
      "counter_price_kop": null
    },
    {
      "proposal_line_id": "ol_92b3c4d5e6",
      "item_id": "i_1a2b3c4d5e",
      "brand": "FILTRON",
      "article": "OP 592/7",
      "oem": "",
      "price_kop": 98000,
      "qty": 2,
      "sum_kop": 196000,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "days_2_3",
      "term_name": "2-3 дня",
      "term_days": 2,
      "availability": "on_order",
      "stock": 0,
      "comment": "",
      "photo_url": null,
      "external_id": null,
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 2,
      "counter_price_kop": null
    },
    {
      "proposal_line_id": "ol_93c4d5e6f7",
      "item_id": "i_2b3c4d5e6f",
      "brand": "Contitech",
      "article": "6PK1873",
      "oem": "",
      "price_kop": 210000,
      "qty": 1,
      "sum_kop": 210000,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "in_stock",
      "term_name": "В наличии",
      "term_days": 0,
      "availability": "in_stock",
      "stock": 0,
      "comment": "",
      "photo_url": "https://disk.yandex.ru/i/Ab3dE6fG",
      "external_id": null,
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 1,
      "counter_price_kop": null
    }
  ],
  "orders": [],
  "actions": [],
  "counter": null,
  "competition": {
    "proposals": 3,
    "sellers": 3,
    "limit": 5,
    "slots_left": 2,
    "my_rank": 1,
    "my_value_kop": 406000,
    "basis": "accept_plan"
  },
  "change_seq": 184311
}

Ошибки: 401, 403, 404, 409, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/seller/exchange/proposals/o_6a7b8c9d0e/withdraw -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{}'

Покупатель

GET/api/v1/buyer/search

Поиск предложений: бренды по артикулу или предложения

Право: не нужноЛимит: search

Лимит search на ключ и на компанию. Test-ключ: 0,2 лимита и limit ≤ 20. Без article/q — листинг глубиной до 500 предложений (warnings: LISTING_DEPTH_LIMIT). Склад — только город и обезличенный warehouse_code.

Параметры

ИмяГдеОбяз.ТипОписание
qqueryнетstring
articlequeryнетstring
brandqueryнетstring
brand_idqueryнетstringОдин или несколько id через запятую
analogsqueryнетinteger: 0 | 1
modequeryнетstring: auto | brands | offersauto — артикул без бренда: список брендов (mode: brands), если брендов у артикула больше одного; при одном бренде сразу предложения (mode: offers). brands — список брендов при любом их числе. offers — сразу предложения. Второй шаг — ?article=<article_norm>&brand_id=<brand_id> пары из списка: предложения искомого товара (группа requested), затем аналоги (oem, analog), в том числе присланные поставщиками площадки
similarqueryнетinteger: 0 | 11 — в ответе mode: brands ещё похожие номера (similar): номера, которые начинаются с введённого (после нормализации), до 30 пар «бренд + номер». С mode=auto список приходит и при одном бренде, если похожие номера есть. Без параметра ответ прежний
price_min_kopqueryнетinteger
price_max_kopqueryнетinteger
term_maxqueryнетinteger
city_idqueryнетstringid через запятую
cityqueryнетstring
countryqueryнетstring
sellerqueryнетstringКод продавца ТТ-xxx
in_stockqueryнетinteger: 0 | 1
conditionqueryнетstring: new | used
freshqueryнетinteger: 0 | 1
sortqueryнетstringЗначения: relevance, price, price_desc, term, updated
limitqueryнетinteger
cursorqueryнетstringНепрозрачный курсор из next_cursor

Ответ 200 Бренды или предложения

{
  "mode": "offers",
  "query": {
    "brand_id": 17,
    "article_norm": "01089",
    "analogs": true
  },
  "total": 1,
  "total_exact": true,
  "groups": [
    {
      "kind": "requested",
      "brand_id": 17,
      "brand": "FEBI BILSTEIN",
      "article": "01-089",
      "article_norm": "01089",
      "offers": [
        {
          "offer_key": "eyJvIjoicF8zYzFmOWUyYTdiIiwicCI6MTIzNDUwfQ.m7Q2x9Lh0aV1c3kP",
          "name": "Термостат",
          "seller_code": "ТТ-014",
          "seller_rating": 4.1,
          "refusal_pct": 6.5,
          "city": "Санкт-Петербург",
          "region": "Санкт-Петербург",
          "country": "RU",
          "warehouse_code": "ТТ-014-1",
          "price_kop": 123450,
          "currency": "RUB",
          "qty": 10,
          "multiplicity": 1,
          "min_order": 1,
          "term_days_min": 1,
          "term_days_max": 3,
          "probability": 95,
          "condition": "new",
          "returnable": true,
          "vat": "НДС 22%",
          "availability": "in_stock",
          "stale": false,
          "price_age_hours": 3,
          "photo": null,
          "updated_at": "2026-09-15T08:00:00.000Z"
        }
      ]
    }
  ],
  "next_cursor": null,
  "has_more": false,
  "warnings": []
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s "https://k-247.ru/api/v1/buyer/search?article=01-089&brand_id=17" -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/buyer/quotes

Перепроверка до 50 offer_key

Право: не нужноЛимит: search

Перепроверка до 50 offer_key прямо сейчас: цена, доступный остаток, срок. Свежий offer_key выдаётся для ok и price_changed. Кратность и минимальная партия отдаются для сведения: заказ они не блокируют (M.6 п.4).

Тело запроса application/json

ПолеОбяз.ТипОписание
itemsдамассив object
{
  "items": [
    {
      "offer_key": "eyJvIjoicF8zYzFmOWUyYTdiIiwicCI6MTIzNDUwfQ.m7Q2x9Lh0aV1c3kP",
      "qty": 2
    }
  ]
}

Ответ 200 Актуальные условия

{
  "items": [
    {
      "offer_key": "eyJvIjoicF8zYzFmOWUyYTdiIiwicCI6MTIzNDUwfQ.m7Q2x9Lh0aV1c3kPN",
      "requested_offer_key": "eyJvIjoicF8zYzFmOWUyYTdiIiwicCI6MTIzNDUwfQ.m7Q2x9Lh0aV1c3kP",
      "status": "ok",
      "price_kop": 123450,
      "qty_available": 10,
      "qty_requested": 2,
      "multiplicity": 1,
      "min_order": 1,
      "term_days_min": 1,
      "term_days_max": 3,
      "availability": "in_stock",
      "expires_at": "2026-09-15T09:42:00.000Z"
    }
  ]
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/buyer/quotes -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"items":[{"offer_key":"<offer_key>","qty":2}]}'

GET/api/v1/buyer/orders

Заказы покупателя: по ids, reference или since

Право: orders | exchangeЛимит: default

Заказы компании своего окружения: из магазина (source shop) и из КП биржи (source exchange, поле exchange). Ключ только с правом exchange видит лишь заказы из КП. Синхронизация — since_seq.

Параметры

ИмяГдеОбяз.ТипОписание
idsqueryнетstringДо 50 id через запятую
referencequeryнетstring
sourcequeryнетstring: shop | exchangeИсточник заказа
since_seqqueryнетintegerОбъекты с change_seq строго больше; сортировка change_seq ASC. Опрос без потерь: сохранить change_seq последнего элемента.
sincequeryнетstringОбъекты с updated_at строго больше (для людей и отладки; для синхронизации используйте since_seq — время ставится до коммита и не гарантирует полноты)
statusqueryнетstring
limitqueryнетinteger
cursorqueryнетstringНепрозрачный курсор из next_cursor

Ответ 200 Страница

{
  "items": [
    {
      "order_id": "ord_6f2b3c4d5e",
      "number": "2609-1190",
      "status": "awaiting_confirmation",
      "status_name": "Ожидает подтверждения",
      "test": false,
      "created_at": "2026-09-15T09:05:00.000Z",
      "updated_at": "2026-09-15T09:05:00.000Z",
      "confirm_deadline_at": "2026-09-15T11:05:00.000Z",
      "auto_cancel_at": "2026-09-15T13:05:00.000Z",
      "source": "shop",
      "exchange": null,
      "requires_confirmation": true,
      "seller": {
        "code": "ТТ-014",
        "city": "Санкт-Петербург",
        "rating": 4.1
      },
      "delivery": {
        "type": "основной",
        "address": "ул. Салова, 27",
        "express": false
      },
      "payment": "счёт",
      "comment": "",
      "currency": "RUB",
      "total_kop": 246900,
      "delivery_kop": 0,
      "accounting_total_rub": 2469,
      "reference": "ТК-Север-4471",
      "lines": [
        {
          "line_id": "l_9a8b7c6d5e",
          "offer_id": "p_3c1f9e2a7b",
          "brand": "FEBI BILSTEIN",
          "article": "01-089",
          "oem": "",
          "name": "Термостат",
          "qty": 2,
          "confirmed_qty": null,
          "shipped_qty": 0,
          "price_kop": 123450,
          "sum_kop": 246900,
          "status": "awaiting_confirmation",
          "reject_reason_code": null,
          "term_days_min": 1,
          "term_days_max": 3,
          "term_days_confirmed": null,
          "warehouse": "СПб-1",
          "plate": "",
          "exchange": null
        }
      ],
      "documents": {
        "invoice": "",
        "upd": "",
        "ksf": ""
      },
      "change_seq": 184230
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s "https://k-247.ru/api/v1/buyer/orders?since_seq=184000" -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/buyer/orders

Заказ по offer_key (делится по продавцам)

Право: ordersIdempotency-Key: обязателенЛимит: bulk, тестовым ключом — default

Только организация (у автовладельца — 403 FORBIDDEN_ROLE); боевому ключу компании без принятых документов площадки — 403 LEGAL_NEEDS. Правила — как у корзины сайта; кратность и минимальная партия не проверяются. Test-ключ: заказ test = true на служебного продавца (реальный продавец его не видит), робот песочницы подтверждает строки, строку с qty = 13 отклоняет out_of_stock и через Δ отгружает подтверждённые. Лимит: боевой ключ — класс bulk, тестовый — класс default (x-rate-limit-class-test), чтобы тестовые заказы подряд не упирались в 429. Истёкший offer_key (старше quoteTtlMinutes) — 409 OFFER_KEY_EXPIRED: повторите поиск (GET /buyer/search), quotes истёкший ключ не продлевает.

Параметры

ИмяГдеОбяз.ТипОписание
Idempotency-KeyзаголовокдаstringПовтор с тем же телом возвращает сохранённый ответ (заголовок Idempotent-Replayed: true), с другим телом — 409 Ключ хранится idempotencyTtlHours (24 ч). Позже тот же ключ — новый запрос: создаётся новая заявка, КП или заказ, прежний объект не возвращается. Ключ, которым уже выполнена другая операция в пределах срока, — 409 IDEMPOTENCY_CONFLICT.

Тело запроса application/json

ПолеОбяз.ТипОписание
referenceнетstring
itemsдамассив object
deliveryнетobject
paymentнетstringКод справочника payments (GET /dictionary/exchange) или подпись, как у методов биржи. Без поля — с баланса, если тип компании её допускает (balancePaymentTypes), иначе счёт. Явная оплата с баланса у другого типа — 422 BUSINESS_RULE Значения: balance, invoice, card_on_delivery, с баланса, счёт, картой при получении
accept_termsдаboolean: trueОзнакомление с условиями возврата; обязательно true, иначе 422 BUSINESS_RULE (как у корзины сайта)
price_policyнетstring: strict | accept_lower
commentнетstringКомментарий к заказу, как в корзине сайта: продавец видит его в заказе
{
  "reference": "ТК-Север-4471",
  "items": [
    {
      "offer_key": "eyJvIjoi…",
      "qty": 2
    }
  ],
  "delivery": {
    "type": "основной",
    "address": "ул. Салова, 27",
    "express": false
  },
  "payment": "счёт",
  "accept_terms": true,
  "price_policy": "strict"
}

Ответ 201 Заказы созданы

{
  "reference": "ТК-Север-4471",
  "orders": [
    {
      "order_id": "ord_6f2b",
      "number": "2609-1191",
      "seller_code": "ТТ-014",
      "status": "awaiting_confirmation",
      "total_kop": 246900,
      "accounting_total_rub": 2469,
      "lines": [
        {
          "line_id": "l_1a2b",
          "brand": "FEBI BILSTEIN",
          "article": "01-089",
          "qty": 2,
          "price_kop": 123450,
          "status": "awaiting_confirmation"
        }
      ]
    }
  ]
}

Ошибки: 400, 401, 403, 409, 422, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/buyer/orders -H "Authorization: Bearer $K247_TOKEN" -H "Idempotency-Key: TK-4471" -H "Content-Type: application/json" -d '{"reference":"ТК-Север-4471","items":[{"offer_key":"<offer_key>","qty":2}],"payment":"invoice","accept_terms":true}'

GET/api/v1/buyer/orders/{id}

Заказ покупателя

Право: orders | exchangeЛимит: default

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring

Ответ 200 Заказ

{
  "order_id": "ord_6f2b3c4d5e",
  "number": "2609-1190",
  "status": "awaiting_confirmation",
  "status_name": "Ожидает подтверждения",
  "test": false,
  "created_at": "2026-09-15T09:05:00.000Z",
  "updated_at": "2026-09-15T09:05:00.000Z",
  "confirm_deadline_at": "2026-09-15T11:05:00.000Z",
  "auto_cancel_at": "2026-09-15T13:05:00.000Z",
  "source": "shop",
  "exchange": null,
  "requires_confirmation": true,
  "seller": {
    "code": "ТТ-014",
    "city": "Санкт-Петербург",
    "rating": 4.1
  },
  "delivery": {
    "type": "основной",
    "address": "ул. Салова, 27",
    "express": false
  },
  "payment": "счёт",
  "comment": "",
  "currency": "RUB",
  "total_kop": 246900,
  "delivery_kop": 0,
  "accounting_total_rub": 2469,
  "reference": "ТК-Север-4471",
  "lines": [
    {
      "line_id": "l_9a8b7c6d5e",
      "offer_id": "p_3c1f9e2a7b",
      "brand": "FEBI BILSTEIN",
      "article": "01-089",
      "oem": "",
      "name": "Термостат",
      "qty": 2,
      "confirmed_qty": null,
      "shipped_qty": 0,
      "price_kop": 123450,
      "sum_kop": 246900,
      "status": "awaiting_confirmation",
      "reject_reason_code": null,
      "term_days_min": 1,
      "term_days_max": 3,
      "term_days_confirmed": null,
      "warehouse": "СПб-1",
      "plate": "",
      "exchange": null
    }
  ],
  "documents": {
    "invoice": "",
    "upd": "",
    "ksf": ""
  },
  "change_seq": 184230
}

Ошибки: 401, 403, 404, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/buyer/orders/ord_6f2b3c4d5e -H "Authorization: Bearer $K247_TOKEN"

Биржа: покупатель

GET/api/v1/buyer/exchange/requests

Заявки компании: status, reference, since_seq

Право: exchangeЛимит: default

Заявки компании своего окружения, в том числе созданные сотрудниками в кабинете. Номер изменения заявки растёт и при новом КП — один опрос показывает, где появились КП.

Параметры

ИмяГдеОбяз.ТипОписание
statusqueryнетstringСписок через запятую: active, done, closed, expired или all
referencequeryнетstringСвой номер заявки покупателя
since_seqqueryнетintegerОбъекты с change_seq строго больше; сортировка change_seq ASC. Опрос без потерь: сохранить change_seq последнего элемента.
sincequeryнетstringОбъекты с updated_at строго больше (для людей и отладки; для синхронизации используйте since_seq — время ставится до коммита и не гарантирует полноты)
limitqueryнетinteger
cursorqueryнетstringНепрозрачный курсор из next_cursor

Ответ 200 Страница

{
  "items": [
    {
      "exchange_request_id": "r_9d2e3f4a5b",
      "number": "2609-412",
      "status": "active",
      "exchange": "new",
      "test": false,
      "via": "api",
      "reference": "ТК-Север-4471",
      "created_at": "2026-09-22T07:10:00.000Z",
      "updated_at": "2026-09-22T07:10:00.000Z",
      "revision": 0,
      "revised_at": null,
      "expires_at": "2026-09-22T20:59:59.000Z",
      "deadline": "today",
      "deadline_name": "сегодня",
      "priority": "fast",
      "priority_name": "быстрее",
      "vehicle": {
        "vgroup": "truck",
        "market": "eu",
        "category": "Грузовые / Европа",
        "brand": "MAN",
        "model": "TGX",
        "year": "2016",
        "vin": "WMA06XZZ0FP000001",
        "plate": "А123ВС78"
      },
      "city": "Санкт-Петербург",
      "region": "Санкт-Петербург",
      "delivery": "main",
      "delivery_name": "основной",
      "pickup": "delivery",
      "pickup_name": "доставка",
      "pickup_address": "ул. Салова, 27",
      "payment": "invoice",
      "payment_name": "счёт",
      "comment": "Машина на линии",
      "max_proposals": 5,
      "items": [
        {
          "item_id": "i_3c4d5e6f7a",
          "position": 1,
          "name": "Фильтр масляный",
          "qty": 2,
          "comment": "по VIN",
          "bought_qty": 0,
          "need_qty": 2,
          "open": true
        },
        {
          "item_id": "i_4d5e6f7a8b",
          "position": 2,
          "name": "Ремень приводной",
          "qty": 1,
          "comment": "",
          "bought_qty": 0,
          "need_qty": 1,
          "open": true
        }
      ],
      "proposals_summary": {
        "active": 0,
        "sellers": 0,
        "total": 0,
        "best_value_kop": null
      },
      "unread_messages": 0,
      "change_seq": 184290
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s "https://k-247.ru/api/v1/buyer/exchange/requests?status=active&limit=50" -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/buyer/exchange/requests

Создать заявку на бирже

Право: exchangeIdempotency-Key: обязателенЛимит: default

Только организация (у автовладельца — 403 FORBIDDEN_ROLE). Боевому ключу компании без принятых документов площадки — 403 LEGAL_NEEDS. Пределы: не больше exchangeApiRequestsPerHour заявок в час и exchangeApiActiveMax действующих заявок из программы на компанию; тестовым ключом — пятая часть часового предела, не больше 20 действующих и 100 за сутки (429 RATE_LIMITED).

Параметры

ИмяГдеОбяз.ТипОписание
Idempotency-KeyзаголовокдаstringПовтор с тем же телом возвращает сохранённый ответ (заголовок Idempotent-Replayed: true), с другим телом — 409 Ключ хранится idempotencyTtlHours (24 ч). Позже тот же ключ — новый запрос: создаётся новая заявка, КП или заказ, прежний объект не возвращается. Ключ, которым уже выполнена другая операция в пределах срока, — 409 IDEMPOTENCY_CONFLICT.

Тело запроса application/json

ПолеОбяз.ТипОписание
referenceнетstringСвой номер заявки покупателя: по нему ищет GET …?reference=
exchangeнетstring: new | used | all
vehicleдаobject
itemsдамассив object
deadlineнетstring: today | days_2_3 | week
priorityнетstring: fast | cheap | any
max_proposalsнетinteger
deliveryнетstring: main | pickup | express
pickupнетstring: delivery | pickup
pickup_addressнетstringАдрес получения (адрес доставки по этой заявке); без поля — адрес компании. Продавцу не отдаётся ни в API, ни в кабинете: адрес он видит только в заказе. При «Принять» без delivery.address становится адресом доставки заказа (кроме самовывоза)
paymentнетstringПо умолчанию — balance разрешённым типам компаний, иначе invoice Значения: balance, invoice, card_on_delivery
cityнетstring
regionнетstringРегион из справочника GET /dictionary/exchange
commentнетstring
sandboxнетobjectТолько тестовый ключ: сценарий роботов-продавцов песочницы (M.9.1)
{
  "reference": "ТК-Север-4471",
  "exchange": "new",
  "vehicle": {
    "vgroup": "truck",
    "market": "eu",
    "brand": "MAN",
    "model": "TGX",
    "year": "2016",
    "vin": "WMA06XZZ0FP000001",
    "plate": "А123ВС78"
  },
  "items": [
    {
      "name": "Фильтр масляный",
      "qty": 2,
      "comment": "по VIN"
    },
    {
      "name": "Ремень приводной",
      "qty": 1
    }
  ],
  "deadline": "today",
  "priority": "fast",
  "max_proposals": 5,
  "delivery": "main",
  "pickup": "delivery",
  "pickup_address": "ул. Салова, 27",
  "payment": "invoice",
  "city": "Санкт-Петербург",
  "region": "Санкт-Петербург",
  "comment": "Машина на линии"
}

Ответ 201 Заявка создана

{
  "exchange_request_id": "r_9d2e3f4a5b",
  "number": "2609-412",
  "status": "active",
  "exchange": "new",
  "test": false,
  "via": "api",
  "reference": "ТК-Север-4471",
  "created_at": "2026-09-22T07:10:00.000Z",
  "updated_at": "2026-09-22T07:10:00.000Z",
  "revision": 0,
  "revised_at": null,
  "expires_at": "2026-09-22T20:59:59.000Z",
  "deadline": "today",
  "deadline_name": "сегодня",
  "priority": "fast",
  "priority_name": "быстрее",
  "vehicle": {
    "vgroup": "truck",
    "market": "eu",
    "category": "Грузовые / Европа",
    "brand": "MAN",
    "model": "TGX",
    "year": "2016",
    "vin": "WMA06XZZ0FP000001",
    "plate": "А123ВС78"
  },
  "city": "Санкт-Петербург",
  "region": "Санкт-Петербург",
  "delivery": "main",
  "delivery_name": "основной",
  "pickup": "delivery",
  "pickup_name": "доставка",
  "pickup_address": "ул. Салова, 27",
  "payment": "invoice",
  "payment_name": "счёт",
  "comment": "Машина на линии",
  "max_proposals": 5,
  "items": [
    {
      "item_id": "i_3c4d5e6f7a",
      "position": 1,
      "name": "Фильтр масляный",
      "qty": 2,
      "comment": "по VIN",
      "bought_qty": 0,
      "need_qty": 2,
      "open": true
    },
    {
      "item_id": "i_4d5e6f7a8b",
      "position": 2,
      "name": "Ремень приводной",
      "qty": 1,
      "comment": "",
      "bought_qty": 0,
      "need_qty": 1,
      "open": true
    }
  ],
  "proposals_summary": {
    "active": 0,
    "sellers": 0,
    "total": 0,
    "best_value_kop": null
  },
  "unread_messages": 0,
  "change_seq": 184290
}

Ошибки: 400, 401, 403, 409, 422, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/buyer/exchange/requests -H "Authorization: Bearer $K247_TOKEN" -H "Idempotency-Key: REQ-4471" -H "Content-Type: application/json" -d '{"reference":"ТК-Север-4471","vehicle":{"vgroup":"truck","market":"eu","brand":"MAN","model":"TGX"},"items":[{"name":"Фильтр масляный","qty":2},{"name":"Ремень приводной","qty":1}],"deadline":"week","payment":"invoice"}'

GET/api/v1/buyer/exchange/requests/{id}

Заявка компании

Право: exchangeЛимит: default

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring

Ответ 200 Заявка

{
  "exchange_request_id": "r_9d2e3f4a5b",
  "number": "2609-412",
  "status": "active",
  "exchange": "new",
  "test": false,
  "via": "api",
  "reference": "ТК-Север-4471",
  "created_at": "2026-09-22T07:10:00.000Z",
  "updated_at": "2026-09-22T07:10:00.000Z",
  "revision": 0,
  "revised_at": null,
  "expires_at": "2026-09-22T20:59:59.000Z",
  "deadline": "today",
  "deadline_name": "сегодня",
  "priority": "fast",
  "priority_name": "быстрее",
  "vehicle": {
    "vgroup": "truck",
    "market": "eu",
    "category": "Грузовые / Европа",
    "brand": "MAN",
    "model": "TGX",
    "year": "2016",
    "vin": "WMA06XZZ0FP000001",
    "plate": "А123ВС78"
  },
  "city": "Санкт-Петербург",
  "region": "Санкт-Петербург",
  "delivery": "main",
  "delivery_name": "основной",
  "pickup": "delivery",
  "pickup_name": "доставка",
  "pickup_address": "ул. Салова, 27",
  "payment": "invoice",
  "payment_name": "счёт",
  "comment": "Машина на линии",
  "max_proposals": 5,
  "items": [
    {
      "item_id": "i_3c4d5e6f7a",
      "position": 1,
      "name": "Фильтр масляный",
      "qty": 2,
      "comment": "по VIN",
      "bought_qty": 0,
      "need_qty": 2,
      "open": true
    },
    {
      "item_id": "i_4d5e6f7a8b",
      "position": 2,
      "name": "Ремень приводной",
      "qty": 1,
      "comment": "",
      "bought_qty": 0,
      "need_qty": 1,
      "open": true
    }
  ],
  "proposals_summary": {
    "active": 0,
    "sellers": 0,
    "total": 0,
    "best_value_kop": null
  },
  "unread_messages": 0,
  "change_seq": 184290
}

Ошибки: 401, 403, 404, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/buyer/exchange/requests/r_9d2e3f4a5b -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/buyer/exchange/requests/{id}/update

Изменить заявку

Право: exchangeIdempotency-Key: рекомендуетсяЛимит: default

Юридические ворота не действуют: правка — не новое обязательство. Купленную позицию нельзя удалить, переименовать или уменьшить ниже купленного (409 INVALID_STATE). Переименование или удаление позиции закрывает строки КП по ней. Биржу (exchange) можно расширить всегда; сузить — нельзя, пока продавцы, которые потеряют доступ к заявке, держат действующие КП (409 INVALID_STATE, details[0].field = exchange).

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-Keyзаголовокнетstring

Тело запроса application/json

ПолеОбяз.ТипОписание
vehicleнетobject
itemsнетмассив object
exchangeнетstring: new | used | allБиржа заявки. Расширить (new или used → all) можно всегда: продавцам, которым заявка стала видна, уходит exchange.request.created. Сузить (all → new или used, new ↔ used) нельзя, пока у продавцов, которые потеряют доступ к заявке, есть действующие КП (sent, partial): 409 INVALID_STATE, details[0].field = exchange; каких продавцов это касается, ответ не говорит. Черновики таких продавцов закрываются. Номер заявки не меняется. null или "" — 400
deadlineнетstring: today | days_2_3 | week
priorityнетstring: fast | cheap | any
max_proposalsнетinteger
deliveryнетstring: main | pickup | express
pickupнетstring: delivery | pickup
pickup_addressнетstring
paymentнетstringПо умолчанию — balance разрешённым типам компаний, иначе invoice Значения: balance, invoice, card_on_delivery
cityнетstring
regionнетstringРегион из справочника GET /dictionary/exchange
commentнетstring
{
  "deadline": "week",
  "max_proposals": 5,
  "comment": "Машина на линии, нужен счёт на оплату"
}

Ответ 200 Заявка после правки

{
  "exchange_request_id": "r_9d2e3f4a5b",
  "number": "2609-412",
  "status": "active",
  "exchange": "new",
  "test": false,
  "via": "api",
  "reference": "ТК-Север-4471",
  "created_at": "2026-09-22T07:10:00.000Z",
  "updated_at": "2026-09-22T07:10:00.000Z",
  "revision": 0,
  "revised_at": null,
  "expires_at": "2026-09-22T20:59:59.000Z",
  "deadline": "today",
  "deadline_name": "сегодня",
  "priority": "fast",
  "priority_name": "быстрее",
  "vehicle": {
    "vgroup": "truck",
    "market": "eu",
    "category": "Грузовые / Европа",
    "brand": "MAN",
    "model": "TGX",
    "year": "2016",
    "vin": "WMA06XZZ0FP000001",
    "plate": "А123ВС78"
  },
  "city": "Санкт-Петербург",
  "region": "Санкт-Петербург",
  "delivery": "main",
  "delivery_name": "основной",
  "pickup": "delivery",
  "pickup_name": "доставка",
  "pickup_address": "ул. Салова, 27",
  "payment": "invoice",
  "payment_name": "счёт",
  "comment": "Машина на линии",
  "max_proposals": 5,
  "items": [
    {
      "item_id": "i_3c4d5e6f7a",
      "position": 1,
      "name": "Фильтр масляный",
      "qty": 2,
      "comment": "по VIN",
      "bought_qty": 0,
      "need_qty": 2,
      "open": true
    },
    {
      "item_id": "i_4d5e6f7a8b",
      "position": 2,
      "name": "Ремень приводной",
      "qty": 1,
      "comment": "",
      "bought_qty": 0,
      "need_qty": 1,
      "open": true
    }
  ],
  "proposals_summary": {
    "active": 0,
    "sellers": 0,
    "total": 0,
    "best_value_kop": null
  },
  "unread_messages": 0,
  "change_seq": 184290
}

Ошибки: 400, 401, 403, 404, 409, 422, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/buyer/exchange/requests/r_9d2e3f4a5b/update -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -d '{"deadline":"week","max_proposals":5}'

POST/api/v1/buyer/exchange/requests/{id}/close

Снять заявку

Право: exchangeIdempotency-Key: рекомендуетсяЛимит: default

Заявка → closed, её КП sent и draft → closed, строки корзин по заявке уходят. Уже снятая заявка — 200 и Idempotent-Replayed: state; выкупленную или истёкшую закрыть нельзя (409 REQUEST_CLOSED).

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-Keyзаголовокнетstring

Ответ 200 Заявка снята

{
  "exchange_request_id": "r_9d2e3f4a5b",
  "number": "2609-412",
  "status": "closed",
  "exchange": "new",
  "test": false,
  "via": "api",
  "reference": "ТК-Север-4471",
  "created_at": "2026-09-22T07:10:00.000Z",
  "updated_at": "2026-09-22T07:10:00.000Z",
  "revision": 0,
  "revised_at": null,
  "expires_at": "2026-09-22T20:59:59.000Z",
  "deadline": "today",
  "deadline_name": "сегодня",
  "priority": "fast",
  "priority_name": "быстрее",
  "vehicle": {
    "vgroup": "truck",
    "market": "eu",
    "category": "Грузовые / Европа",
    "brand": "MAN",
    "model": "TGX",
    "year": "2016",
    "vin": "WMA06XZZ0FP000001",
    "plate": "А123ВС78"
  },
  "city": "Санкт-Петербург",
  "region": "Санкт-Петербург",
  "delivery": "main",
  "delivery_name": "основной",
  "pickup": "delivery",
  "pickup_name": "доставка",
  "pickup_address": "ул. Салова, 27",
  "payment": "invoice",
  "payment_name": "счёт",
  "comment": "Машина на линии",
  "max_proposals": 5,
  "items": [
    {
      "item_id": "i_3c4d5e6f7a",
      "position": 1,
      "name": "Фильтр масляный",
      "qty": 2,
      "comment": "по VIN",
      "bought_qty": 0,
      "need_qty": 2,
      "open": true
    },
    {
      "item_id": "i_4d5e6f7a8b",
      "position": 2,
      "name": "Ремень приводной",
      "qty": 1,
      "comment": "",
      "bought_qty": 0,
      "need_qty": 1,
      "open": true
    }
  ],
  "proposals_summary": {
    "active": 0,
    "sellers": 0,
    "total": 0,
    "best_value_kop": null
  },
  "unread_messages": 0,
  "change_seq": 184290
}

Ошибки: 401, 403, 404, 409, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/buyer/exchange/requests/r_9d2e3f4a5b/close -H "Authorization: Bearer $K247_TOKEN"

GET/api/v1/buyer/exchange/proposals

КП по заявкам компании

Право: exchangeЛимит: default

Черновики продавцов сюда не попадают. Без status и since_seq — действующие КП (sent, partial, accepted); с since_seq — все статусы. sort=value — по стоимости КП, только вместе с exchange_request_id и без курсора.

Параметры

ИмяГдеОбяз.ТипОписание
exchange_request_idqueryнетstring
statusqueryнетstringСписок через запятую или all
sortqueryнетstring: valuevalue — все КП заявки по стоимости (value_kop) по возрастанию, в ответе первые limit; курсора нет, has_more: true — КП больше limit. Только с exchange_request_id, без since_seq, since и cursor
since_seqqueryнетintegerОбъекты с change_seq строго больше; сортировка change_seq ASC. Опрос без потерь: сохранить change_seq последнего элемента.
sincequeryнетstringОбъекты с updated_at строго больше (для людей и отладки; для синхронизации используйте since_seq — время ставится до коммита и не гарантирует полноты)
limitqueryнетinteger
cursorqueryнетstringНепрозрачный курсор из next_cursor

Ответ 200 Страница

{
  "items": [
    {
      "proposal_id": "o_7b8c9d0e1f",
      "number": "ТТ-014-00132",
      "exchange_request_id": "r_9d2e3f4a5b",
      "status": "sent",
      "test": false,
      "replaces_proposal_id": null,
      "seller": {
        "code": "ТТ-014",
        "city": "Санкт-Петербург",
        "region": "Санкт-Петербург",
        "rating": 4.8,
        "refusal_pct": null
      },
      "created_at": "2026-09-22T07:45:02.000Z",
      "updated_at": "2026-09-22T07:45:02.000Z",
      "comment": "Отгрузка со склада СПб в день оплаты",
      "value_kop": 406000,
      "lines_sum_kop": 652900,
      "coverage": {
        "items_open": 2,
        "items_offered": 2,
        "items_full": 2
      },
      "lines": [
        {
          "proposal_line_id": "ol_a1b2c3d4e5",
          "item_id": "i_3c4d5e6f7a",
          "brand": "MANN",
          "article": "",
          "oem": "",
          "price_kop": 123450,
          "qty": 2,
          "sum_kop": 246900,
          "vat": "vat22",
          "vat_name": "НДС 22%",
          "term": "in_stock",
          "term_name": "В наличии",
          "term_days": 0,
          "availability": "in_stock",
          "stock": 12,
          "comment": "оригинальная упаковка",
          "photo_url": null,
          "status": "pending",
          "bought_qty": 0,
          "left_qty": 2,
          "counter_price_kop": null
        }
      ],
      "orders": [],
      "actions": [
        "accept",
        "reject"
      ],
      "counter": null,
      "change_seq": 184311
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Ошибки: 400, 401, 403, 429, 500, 503

Пример curl

curl -s "https://k-247.ru/api/v1/buyer/exchange/proposals?exchange_request_id=r_9d2e3f4a5b&sort=value" -H "Authorization: Bearer $K247_TOKEN"

GET/api/v1/buyer/exchange/proposals/{id}

КП по заявке компании

Право: exchangeЛимит: default

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring

Ответ 200 КП

{
  "proposal_id": "o_7b8c9d0e1f",
  "number": "ТТ-014-00132",
  "exchange_request_id": "r_9d2e3f4a5b",
  "status": "sent",
  "test": false,
  "replaces_proposal_id": null,
  "seller": {
    "code": "ТТ-014",
    "city": "Санкт-Петербург",
    "region": "Санкт-Петербург",
    "rating": 4.8,
    "refusal_pct": null
  },
  "created_at": "2026-09-22T07:45:02.000Z",
  "updated_at": "2026-09-22T07:45:02.000Z",
  "comment": "Отгрузка со склада СПб в день оплаты",
  "value_kop": 406000,
  "lines_sum_kop": 652900,
  "coverage": {
    "items_open": 2,
    "items_offered": 2,
    "items_full": 2
  },
  "lines": [
    {
      "proposal_line_id": "ol_a1b2c3d4e5",
      "item_id": "i_3c4d5e6f7a",
      "brand": "MANN",
      "article": "",
      "oem": "",
      "price_kop": 123450,
      "qty": 2,
      "sum_kop": 246900,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "in_stock",
      "term_name": "В наличии",
      "term_days": 0,
      "availability": "in_stock",
      "stock": 12,
      "comment": "оригинальная упаковка",
      "photo_url": null,
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 2,
      "counter_price_kop": null
    }
  ],
  "orders": [],
  "actions": [
    "accept",
    "reject"
  ],
  "counter": null,
  "change_seq": 184311
}

Ошибки: 401, 403, 404, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/buyer/exchange/proposals/o_7b8c9d0e1f -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/buyer/exchange/proposals/{id}/accept

Принять КП целиком или по строкам

Право: exchangeIdempotency-Key: обязателенЛимит: default

Оформление идёт тем же путём, что корзина сайта: одно «Принять» — один заказ на КП. Без lines берётся план «Принять» (по одному самому дешёвому варианту на позицию в пределах потребности). accept_terms обязателен и в песочнице. Адрес доставки заказа — delivery.address, без него — адрес получения заявки (pickup_address), при самовывозе — пункт самовывоза площадки «самовывоз, <адрес площадки>» (если адрес площадки не указан — адрес компании). Повтор с тем же Idempotency-Key отдаёт те же заказы.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-KeyзаголовокдаstringПовтор с тем же телом возвращает сохранённый ответ (заголовок Idempotent-Replayed: true), с другим телом — 409 Ключ хранится idempotencyTtlHours (24 ч). Позже тот же ключ — новый запрос: создаётся новая заявка, КП или заказ, прежний объект не возвращается. Ключ, которым уже выполнена другая операция в пределах срока, — 409 IDEMPOTENCY_CONFLICT.

Тело запроса application/json

ПолеОбяз.ТипОписание
linesнетмассив objectСтроки этого КП; без поля — план «Принять»: по одному самому дешёвому варианту на каждую открытую позицию
paymentнетstringБез поля — способ из заявки Значения: balance, invoice, card_on_delivery
deliveryнетobject
plateнетstring
referenceнетstring
accept_termsдаboolean: trueСогласие с условиями возврата обязательно и в песочнице
{
  "lines": [
    {
      "proposal_line_id": "ol_a1b2c3d4e5",
      "qty": 2
    }
  ],
  "payment": "invoice",
  "delivery": {
    "type": "main",
    "address": "ул. Салова, 27"
  },
  "plate": "А123ВС78",
  "reference": "ТК-Север-4471",
  "accept_terms": true
}

Ответ 201 Заказ оформлен

{
  "exchange_request_id": "r_9d2e3f4a5b",
  "proposal_id": "o_7b8c9d0e1f",
  "proposal_status": "partial",
  "request_status": "active",
  "orders": [
    {
      "order_id": "ord_5e1a2b3c4d",
      "number": "2609-1195",
      "source": "exchange",
      "seller_code": "ТТ-014",
      "status": "awaiting_payment",
      "total_kop": 246900,
      "accounting_total_rub": 2469,
      "lines": [
        {
          "line_id": "l_7c8d9e0f1a",
          "proposal_line_id": "ol_a1b2c3d4e5",
          "item_id": "i_3c4d5e6f7a",
          "brand": "MANN",
          "article": "W 950/26",
          "oem": "51.05504-0108",
          "qty": 2,
          "price_kop": 123450,
          "status": null
        }
      ]
    }
  ]
}

Ошибки: 400, 401, 403, 404, 409, 422, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/buyer/exchange/proposals/o_7b8c9d0e1f/accept -H "Authorization: Bearer $K247_TOKEN" -H "Idempotency-Key: ACC-4471" -H "Content-Type: application/json" -d '{"lines":[{"proposal_line_id":"ol_a1b2c3d4e5"}],"accept_terms":true}'

POST/api/v1/buyer/exchange/proposals/{id}/reject

Отклонить КП

Право: exchangeIdempotency-Key: рекомендуетсяЛимит: default

Строки pending → rejected, продавец получает уведомление и событие exchange.proposal.rejected. Повтор, когда отклонять уже нечего, — 200 и Idempotent-Replayed: state, без второго уведомления. Тело пустое: причины и встречной цены в v1 нет.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring
Idempotency-Keyзаголовокнетstring

Ответ 200 КП отклонено

{
  "proposal_id": "o_8c9d0e1f2a",
  "number": "ТТ-014-00132",
  "exchange_request_id": "r_9d2e3f4a5b",
  "status": "rejected",
  "test": false,
  "replaces_proposal_id": null,
  "seller": {
    "code": "ТТ-014",
    "city": "Санкт-Петербург",
    "region": "Санкт-Петербург",
    "rating": 4.8,
    "refusal_pct": null
  },
  "created_at": "2026-09-22T07:45:02.000Z",
  "updated_at": "2026-09-22T07:45:02.000Z",
  "comment": "Отгрузка со склада СПб в день оплаты",
  "value_kop": 406000,
  "lines_sum_kop": 652900,
  "coverage": {
    "items_open": 2,
    "items_offered": 2,
    "items_full": 2
  },
  "lines": [
    {
      "proposal_line_id": "ol_a1b2c3d4e5",
      "item_id": "i_3c4d5e6f7a",
      "brand": "MANN",
      "article": "",
      "oem": "",
      "price_kop": 123450,
      "qty": 2,
      "sum_kop": 246900,
      "vat": "vat22",
      "vat_name": "НДС 22%",
      "term": "in_stock",
      "term_name": "В наличии",
      "term_days": 0,
      "availability": "in_stock",
      "stock": 12,
      "comment": "оригинальная упаковка",
      "photo_url": null,
      "status": "pending",
      "bought_qty": 0,
      "left_qty": 2,
      "counter_price_kop": null
    }
  ],
  "orders": [],
  "actions": [],
  "counter": null,
  "change_seq": 184311
}

Ошибки: 401, 403, 404, 409, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/buyer/exchange/proposals/o_8c9d0e1f2a/reject -H "Authorization: Bearer $K247_TOKEN"

Вебхуки

GET/api/v1/webhooks

Подписки окружения ключа

Право: offers | orders | exchangeЛимит: default

Подписки компании в окружении ключа (test-ключ видит только тестовые, live — только боевые), без секрета. failures — неудачных доставок подряд, last_delivery — итог последней попытки.

Ответ 200 Подписки

{
  "items": [
    {
      "webhook_id": "wh_4b2a1c0d9e",
      "url": "https://erp.shop.ru/k247/hook",
      "events": [
        "order.created",
        "order.line_cancelled",
        "import.held"
      ],
      "env": "live",
      "enabled": true,
      "failures": 0,
      "disabled_reason": null,
      "description": "1С УТ",
      "last_delivery": {
        "at": "2026-09-15T09:40:00.000Z",
        "status": "delivered",
        "code": 200
      },
      "created_at": "2026-09-15T09:30:00.000Z"
    }
  ]
}

Ошибки: 401, 403, 429, 500, 503

Пример curl

curl -s https://k-247.ru/api/v1/webhooks -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/webhooks

Создать подписку (секрет показывается один раз)

Право: offers | orders | exchangeIdempotency-Key: рекомендуетсяЛимит: default

Подписка на события компании. Окружение подписки = окружение ключа: test-подписка получает события песочницы (тестовые заказы, проверочные загрузки), live — боевые. Проверки: адрес https (порты 80, 443, 8080, 8443, без логина в адресе; имя сервера не должно вести во внутреннюю сеть), события из WebhookEvent или ["*"], не больше webhooksMaxPerEnv (5) подписок на компанию и окружение — иначе 409 LIMIT_EXCEEDED. Секрет подписи whsec_… возвращается один раз: сохраните его, посмотреть снова нельзя (повтор с тем же Idempotency-Key возвращает тот же ответ). О новой подписке узнают сотрудники компании. Продавцу приходят order.* по его заказам, import.* и price.stale по его прайсам; покупателю — order.* по его заказам.

Параметры

ИмяГдеОбяз.ТипОписание
Idempotency-Keyзаголовокнетstring

Тело запроса application/json

ПолеОбяз.ТипОписание
urlдаstring
eventsдамассив stringЗначения: order.created, order.line_confirmed, order.line_rejected, order.line_cancelled, order.shipped, import.finished, import.held, price.stale, exchange.request.created, exchange.request.updated, exchange.request.closed, exchange.proposal.received, exchange.proposal.replaced, exchange.proposal.withdrawn, exchange.proposal.accepted, exchange.proposal.rejected, *, order.*, import.*, exchange.*
descriptionнетstring
optionsнетobjectФильтр exchange.request.created (M.8.3): регион заявок (all — ключу с регионом all и подписке из кабинета), группы и рынки техники
{
  "url": "https://erp.shop.ru/k247/hook",
  "events": [
    "order.created",
    "order.line_cancelled",
    "import.held"
  ],
  "description": "1С УТ"
}

Ответ 201 Подписка создана

{
  "webhook_id": "wh_4b2a1c0d9e",
  "url": "https://erp.shop.ru/k247/hook",
  "events": [
    "order.created",
    "order.line_cancelled",
    "import.held"
  ],
  "env": "live",
  "enabled": true,
  "failures": 0,
  "disabled_reason": null,
  "description": "1С УТ",
  "last_delivery": null,
  "created_at": "2026-09-15T09:30:00.000Z",
  "secret": "whsec_Qm9hY2hlbGxvV2ViaG9va1NlY3JldDI0"
}

Ошибки: 400, 401, 403, 409, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/webhooks -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: hook-erp-1" -d '{"url":"https://erp.shop.ru/k247/hook","events":["order.created","order.line_cancelled","import.held"],"description":"1С УТ"}'

Пример Node.js

const r = await fetch('https://k-247.ru/api/v1/webhooks', {
  method: 'POST',
  headers: { Authorization: 'Bearer ' + process.env.K247_TOKEN, 'Content-Type': 'application/json', 'Idempotency-Key': 'hook-erp-1' },
  body: JSON.stringify({ url: 'https://erp.shop.ru/k247/hook', events: ['order.created', 'order.line_cancelled', 'import.held'], description: '1С УТ' })
});
const hook = await r.json();
// hook.secret показывается один раз — сохраните его в настройках приёмника
console.log(r.status, hook.webhook_id);

DELETE/api/v1/webhooks/{id}

Удалить подписку

Право: offers | orders | exchangeЛимит: default

Удалить подписку окружения ключа. Недоставленные события подписки больше не отправляются. Чужая подписка или подписка другого окружения — 404.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring

Ответ 204 Удалено

Ошибки: 401, 403, 404, 429, 500, 503

Пример curl

curl -s -X DELETE https://k-247.ru/api/v1/webhooks/wh_4b2a1c0d9e -H "Authorization: Bearer $K247_TOKEN"

POST/api/v1/webhooks/{id}/test

Отправить событие ping

Право: offers | orders | exchangeЛимит: default

Поставить в очередь проверочное событие ping (в data — {"type":"ping","webhook_id":"…"}): оно подписано и доставляется как обычное событие, в том числе выключенной подписке. Итог доставки — в GET /webhooks (last_delivery) и в журнале кабинета. Не больше 10 проверочных событий в час на подписку (выключенную — 3), сверх — 429 RATE_LIMITED с Retry-After. Если прежний ping подписки ещё ждёт доставки, новый не создаётся: прежний отправляется сейчас, в ответе — его event_id.

Параметры

ИмяГдеОбяз.ТипОписание
idпутьдаstring

Ответ 202 Поставлено в очередь

{
  "event_id": "evt_01J8Z6A1B2C3"
}

Ошибки: 401, 403, 404, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/webhooks/wh_4b2a1c0d9e/test -H "Authorization: Bearer $K247_TOKEN"

Песочница

POST/api/v1/sandbox/orders

Тестовый заказ на собственный товар (только test-ключ продавца)

Право: ordersIdempotency-Key: обязателенЛимит: default

Только test-ключ продавца (live → 403 FORBIDDEN_ENV). Заказ test=true от служебного покупателя c_sandbox, requires_confirmation; остатки, резервы, деньги и уведомления в общую ленту не затрагиваются. Заказ оформляется на собственную позицию продавца (без lines — на любую свою с остатком), поэтому нужна хотя бы одна опубликованная позиция: test-ключ позиций не создаёт (offers/batch и pricelists — dry_run), загрузите прайс в кабинете (файл, ссылка, почта) или боевым ключом. Позиций нет — 422 BUSINESS_RULE.

Параметры

ИмяГдеОбяз.ТипОписание
Idempotency-KeyзаголовокдаstringПовтор с тем же телом возвращает сохранённый ответ (заголовок Idempotent-Replayed: true), с другим телом — 409 Ключ хранится idempotencyTtlHours (24 ч). Позже тот же ключ — новый запрос: создаётся новая заявка, КП или заказ, прежний объект не возвращается. Ключ, которым уже выполнена другая операция в пределах срока, — 409 IDEMPOTENCY_CONFLICT.

Тело запроса application/json

ПолеОбяз.ТипОписание
linesнетмассив object
deliveryнетobject
{
  "lines": [
    {
      "external_id": "A-779",
      "qty": 1
    }
  ],
  "delivery": {
    "type": "основной",
    "address": "Тестовый адрес, 1"
  }
}

Ответ 201 Тестовый заказ

{
  "order_id": "ord_5e1a2b3c4d",
  "number": "2609-1190",
  "status": "awaiting_confirmation",
  "status_name": "Ожидает подтверждения",
  "test": true,
  "created_at": "2026-09-15T09:05:00.000Z",
  "updated_at": "2026-09-15T09:05:00.000Z",
  "change_seq": 184230,
  "confirm_deadline_at": "2026-09-15T11:05:00.000Z",
  "auto_cancel_at": "2026-09-15T13:05:00.000Z",
  "buyer": {
    "code": "Покупатель",
    "city": "Санкт-Петербург"
  },
  "delivery": {
    "type": "основной",
    "address": "ул. Салова, 27",
    "express": false
  },
  "payment": "с баланса",
  "comment": "",
  "currency": "RUB",
  "total_kop": 246900,
  "delivery_kop": 0,
  "accounting_total_rub": 2469,
  "lines": [
    {
      "line_id": "l_9a8b7c6d5e",
      "offer_id": "p_3c1f9e2a7b",
      "external_id": "A-778",
      "brand": "FEBI BILSTEIN",
      "article": "01-089",
      "name": "Термостат",
      "qty": 2,
      "confirmed_qty": null,
      "shipped_qty": 0,
      "price_kop": 123450,
      "sum_kop": 246900,
      "status": "awaiting_confirmation",
      "reject_reason_code": null,
      "term_days_min": 1,
      "term_days_max": 3,
      "term_days_confirmed": null,
      "warehouse": "СПб-1",
      "external_order_ref": null,
      "plate": ""
    }
  ],
  "documents": {
    "invoice": "",
    "upd": "",
    "ksf": ""
  }
}

Ошибки: 400, 401, 403, 404, 409, 422, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/sandbox/orders -H "Authorization: Bearer $K247_TOKEN" -H "Idempotency-Key: sbx-1" -H "Content-Type: application/json" -d '{}'

POST/api/v1/sandbox/exchange/requests

Тестовая заявка робота-покупателя для своей компании (только test-ключ продавца)

Право: exchangeIdempotency-Key: обязателенЛимит: default

Live-ключ — 403 FORBIDDEN_ENV. Заявка test = true от служебного покупателя: биржа — по типу продавца, регион и город — продавца, машина MAN TGX 2016. Сценарий робот выполняет через sandboxAutoConfirmSec (30 с) после первого КП: accept — принять КП целиком (test-заказ из КП), accept_partial — купить первую позицию, reject — отклонять КП, close — снять заявку, expire — закрыть по сроку, compete — ответить КП робота ТЕСТ-000 на 10 % дешевле и купить у него. Действующих тестовых заявок — не больше 20 (409 LIMIT_EXCEEDED), за сутки — не больше 100 (429 RATE_LIMITED с Retry-After). Событие exchange.request.created уходит только на test-подписку этой компании.

Параметры

ИмяГдеОбяз.ТипОписание
Idempotency-KeyзаголовокдаstringПовтор с тем же телом возвращает сохранённый ответ (заголовок Idempotent-Replayed: true), с другим телом — 409 Ключ хранится idempotencyTtlHours (24 ч). Позже тот же ключ — новый запрос: создаётся новая заявка, КП или заказ, прежний объект не возвращается. Ключ, которым уже выполнена другая операция в пределах срока, — 409 IDEMPOTENCY_CONFLICT.

Тело запроса application/json

ПолеОбяз.ТипОписание
scenarioнетstringЗначения: accept, accept_partial, reject, close, expire, compete
itemsнетмассив object
deadlineнетstring: today | days_2_3 | week
{
  "scenario": "accept",
  "items": [
    {
      "name": "Фильтр масляный",
      "qty": 2
    },
    {
      "name": "Ремень приводной",
      "qty": 1
    }
  ],
  "deadline": "week"
}

Ответ 201 Тестовая заявка в формате GET /seller/exchange/requests/{id}

{
  "exchange_request_id": "r_8c1d2e3f4a",
  "number": "ТЕСТ-З-2609-7",
  "status": "active",
  "exchange": "new",
  "test": true,
  "created_at": "2026-09-22T07:10:00.000Z",
  "updated_at": "2026-09-22T07:42:11.000Z",
  "revision": 0,
  "revised_at": null,
  "expires_at": "2026-09-22T20:59:59.000Z",
  "deadline": "week",
  "deadline_name": "неделя",
  "priority": "any",
  "priority_name": "все",
  "vehicle": {
    "vgroup": "truck",
    "market": "eu",
    "category": "Грузовые / Европа",
    "brand": "MAN",
    "model": "TGX",
    "year": "2016",
    "vin": "WMA06XZZ0FP000001"
  },
  "buyer": {
    "ref": "П-7F3A9C",
    "city": "Санкт-Петербург"
  },
  "city": "Санкт-Петербург",
  "region": "Санкт-Петербург",
  "delivery": "main",
  "delivery_name": "основной",
  "pickup": "delivery",
  "pickup_name": "доставка",
  "payment": "invoice",
  "payment_name": "счёт",
  "comment": "Тестовая заявка робота-покупателя песочницы",
  "items": [
    {
      "item_id": "i_1a2b3c4d5e",
      "position": 1,
      "name": "Фильтр масляный",
      "qty": 2,
      "comment": "по VIN",
      "open": true
    },
    {
      "item_id": "i_2b3c4d5e6f",
      "position": 2,
      "name": "Ремень приводной",
      "qty": 1,
      "comment": "",
      "open": true
    }
  ],
  "max_proposals": 0,
  "competition": {
    "proposals": 0,
    "sellers": 0,
    "limit": 0,
    "slots_left": null,
    "my_rank": 0,
    "my_value_kop": 0,
    "basis": "accept_plan"
  },
  "my_proposals": [],
  "current_proposal_id": null,
  "can_propose": true,
  "cannot_propose_reason": null,
  "replace_available_at": null,
  "unread_messages": 0,
  "change_seq": 184300
}

Ошибки: 400, 401, 403, 409, 422, 429, 500, 503

Пример curl

curl -s -X POST https://k-247.ru/api/v1/sandbox/exchange/requests -H "Authorization: Bearer $K247_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: sbx-ex-1" -d '{"scenario":"accept"}'

Изменения

Ломающие изменения — только в новой версии /api/v2 с уведомлением за 30 дней.

  • 1.7.02026-10-16
    • Поиск покупателя (GET /buyer/search) в два шага: сначала «что нашлось по номеру», затем предложения выбранной пары и аналоги.
    • Ответ mode: brands: у каждой пары новые поля article (номер, как его пишут продавцы) и article_norm (нормализованный номер пары); второй шаг — ?article=&brand_id=.
    • Новый необязательный параметр similar=1: в ответе mode: brands ещё список similar — похожие номера (начинаются с введённого после нормализации), до 30 пар; больше — предупреждение SIMILAR_TRUNCATED в warnings. С mode=auto список приходит и при одном бренде, если похожие номера есть.
    • Второй шаг (brand_id одного бренда): в группах analog — и аналоги, которые прислали поставщики площадки (продавец — К-247, город склада, срок; поставщик не называется).
    • Прежние запросы и ответы не меняются: без similar ответы те же, новые поля — дополнительные.
  • 1.6.02026-10-13
    • Гарантия на позицию: два новых необязательных поля — warranty_months (срок в месяцах, 1–120; null — срок не указан, действует гарантия по условиям площадки) и warranty_manufacturer (true — гарантия изготовителя детали, false — гарантия продавца). Свободного текста гарантии нет.
    • Продавец: поля принимает POST /seller/offers/batch (null в warranty_months снимает срок, поля нет — срок не меняется) и отдаёт GET /seller/offers; правка гарантии меняет change_seq позиции. В файлах прайса — колонки «Гарантия» (число месяцев или текст: «24 мес.», «2 года») и «Гарантия изготовителя» (да / нет); поля есть в GET /dictionary/fields. Неверное значение в offers/batch — код строки WARRANTY_INVALID, строка не записывается. В файле прайса гарантия — необязательное сведение: неразобранное значение («есть», «нет», «0», «14 дней») строку не отклоняет — она принимается без срока гарантии с предупреждением WARRANTY_IGNORED в errors отчёта импорта, число таких строк — counters.warranty_ignored (оба кода — новые в перечне BatchRowCode).
    • Биржа: поля принимают строки КП (POST /seller/exchange/requests/{id}/proposals — отправка и черновик) и отдают строки КП продавцу и покупателю (lines[].warranty_months, lines[].warranty_manufacturer).
    • Покупатель: поля есть в предложениях GET /buyer/search и в заменах отказанной позиции заказа.
    • Строки заказа продавца и покупателя (lines[]) несут warranty_months и warranty_manufacturer — гарантию позиции на момент заказа.
    • Прежние запросы и ответы не меняются: поля необязательные, у позиций без срока — null и false.
  • 1.5.22026-10-12
    • Справочник биржи (GET /dictionary/exchange): max_proposals.cabinet — [0] (было [0, 3, 5, 10]). Форма заявки в кабинете лимит числа КП больше не задаёт — новая заявка кабинета идёт без лимита. API принимает max_proposals 0–50, как раньше; лимит прежних заявок сохраняется.
    • Срок заявки today: если заявка создана (или сохранена правкой с deadline) меньше чем за час до конца суток по Москве, expires_at — 23:59:59 следующего дня. Раньше такая заявка закрывалась через минуты, а созданная в последнюю секунду суток сразу была просроченной. В остальное время today — до конца текущего дня, как раньше; days_2_3 и week не менялись. Форма кабинета теперь создаёт заявки со сроком days_2_3.
    • «Принять» (POST /buyer/exchange/proposals/{id}/accept) без delivery.address по заявке с самовывозом (pickup: pickup или delivery.type: pickup): адрес заказа — пункт самовывоза площадки строкой «самовывоз, <адрес площадки>», как при оформлении корзины на сайте. Раньше в заказ записывался адрес компании покупателя; он остаётся, если адрес площадки не указан. Заказ продавцу отдаётся с тем же адресом.
    • Покупателю в кабинете — уведомление, когда срок заявки истёк, а по ней были действующие КП или строки в корзине. Вебхук exchange.request.closed (reason expired) не менялся.
  • 1.5.12026-10-10
    • Заказ покупателя (GET /buyer/orders и GET /buyer/orders/{id}): documents.upd — пустая строка, пока заказ не отгружен (статус shipped, closed или return), даже если продавец выписал УПД заранее. Раньше номер УПД, выписанного до отгрузки, покупатель видел сразу. В кабинете покупателя правило то же: УПД появляется после отгрузки.
    • Заказ покупателя: documents.ksf — пустая строка, пока по заказу нет согласованного возврата (одобренный возврат товара или возврат денег по претензии), а также у заказа без НДС и у покупателя-физлица. После согласования — номер корректировочного счёта-фактуры заказа: «КСФ-<номер заказа>-1» при одном согласованном возврате, сводный «КСФ-<номер заказа>» при нескольких. Раньше поле показывало номер, который можно было «выписать» по любому заказу.
    • Заказ продавца (GET /seller/orders, GET /seller/orders/{id}): documents.upd — как раньше (продавец выписывает УПД и до отгрузки); documents.ksf записывается при согласовании возврата тем же номером, что видит покупатель. Формат полей не менялся.
  • 1.5.02026-10-06
    • «Принять» (POST /buyer/exchange/proposals/{id}/accept) без delivery.address: адрес доставки заказа — адрес получения заявки pickup_address; при самовывозе и пустом адресе заявки — адрес компании, как раньше. Раньше pickup_address в заказ не переносился.
    • Программе продавца способ доставки заявки — только из справочника: значение вне справочника (так попадал адрес покупателя из классической формы кабинета) отдаётся как delivery = null и пустой delivery_name. Адрес покупателя продавец видит только в заказе — и в API, и в кабинете.
    • Группа техники «Иное»: новое значение vehicle.vgroup = other (в справочнике /dictionary/exchange — последней, markets: false) — во входе и выдаче заявки, в параметре vgroup ленты продавца и в данных exchange.request.created. Программы с жёсткой проверкой перечисления должны принять новое значение. У other рынок не сохраняется.
    • Заявка группы «Иное» проходит любой фильтр продавца: параметры vgroup и market ленты GET /seller/exchange/requests и фильтр подписки options.groups / options.markets. Легковые и грузовые без рынка (рынок «Иное», market = null) проходят любой фильтр market. options.groups = ["other"] — 400 VALIDATION_FAILED.
    • Смена биржи заявки: POST /buyer/exchange/requests/{id}/update принимает exchange (new, used, all; раньше — 400). Расширить можно всегда — продавцам, которым заявка стала видна, уходит exchange.request.created. Сузить нельзя, пока у продавцов, которые потеряют доступ, есть действующие КП: 409 INVALID_STATE с details[0].field = exchange; черновики таких продавцов закрываются.
    • Новые сроки строки КП: term = hours_3 (через 3 часа, term_days 0), tomorrow_am (завтра с утра, 1), week (через неделю, 7) — во входе POST /seller/exchange/requests/{id}/proposals, в ProposalLine у продавца и покупателя, в справочнике /dictionary/exchange (terms, по порядку списка формы). Прежние коды и подписи (in_stock, days_2_3 «2-3 дня», days_4_5 «4-5 дней», on_order) не изменились; без term код из term_days выводится, как раньше. Программы с жёсткой проверкой перечисления должны принять новые значения.
    • availability строки КП: in_stock — только у term = in_stock (у other — при term_days = 0). Раньше in_stock давал и term_days = 0 при любом коде; hours_3 (0 дней) — on_order.
    • Артикул и оригинальный номер продавца покупателю до покупки не раскрываются: в КП программе покупателя (GET /buyer/exchange/proposals и /proposals/{id}, ответ reject) article и oem — пустые строки, тип полей прежний. После заказа оба есть в строках заказа (ответ accept, GET /buyer/orders) и в УПД: новое поле oem в строке заказа покупателя (BuyerOrderLine и строки заказа ответа accept) — оригинальный номер строки КП, у строк витрины — пустая строка. Программе продавца его КП — как раньше.
  • 1.4.22026-09-28
    • Пределы песочницы на компанию: в тестовом заказе покупателя — не больше 20 позиций (400 VALIDATION_FAILED); тестовых заказов — не больше 50 действующих (409 LIMIT_EXCEEDED) и 200 за сутки (429 RATE_LIMITED с Retry-After); тестовых заявок биржи — не больше 100 за сутки (429 RATE_LIMITED). Тестовые заказы удаляются через exchangeTestRetentionDays (7 дней) после создания.
    • Новая причина остановки загрузки held_reason: EMAIL_SENDER_UNVERIFIED — письмо в источник «почта» без указанного отправителя, отправитель не подтверждён (DMARC не pass); value и limit — null. Загрузка ждёт утверждения, как при других причинах.
  • 1.4.12026-09-23
    • Склад в строках заказа покупателя — город и обезличенный код warehouse_code («ТТ-014-2»); текст склада продавца покупателю не отдаётся.
    • В текстах КП нельзя передавать и сведения о продавце: реквизиты, название, адрес, фамилии сотрудников — 422 CONTACTS_FORBIDDEN, в details[].kind — requisites, company или person; в старых записях — «[скрыто]».
    • В текстах КП нельзя предлагать расчёты мимо площадки («платите напрямую продавцу», «минуя площадку», «переведите на карту») — 422 CONTACTS_FORBIDDEN, в details[].kind — bypass; в старых записях покупателю всё предложение показывается как «[скрыто]». Тексты КП покупателю очищаются с профилем продавца КП: его название, адрес складов и фамилии сотрудников скрыты и в старых записях.
    • Заказы покупателя: экспресс через delivery.type: express, комментарий заказа comment до 500 символов, payment — код или подпись; accounting_total_rub — число (total_kop / 100).
    • Idempotency-Key: прежний объект возвращается повтором, только пока он моложе срока хранения ключа (24 ч) и создан той же операцией; позже тот же ключ — новый запрос.
    • ping: capabilities.exchange.enabled — включена ли биржа; заголовки X-RateLimit-* и в ответах 403, 404 и 405 с ключом; тело не в UTF-8 — 400 BAD_REQUEST.
    • Вебхуки автовладельцу недоступны (403 FORBIDDEN_ROLE); в WebhookEnvelope.type перечислены события биржи.
    • POST /buyer/orders тестовым ключом считается по классу лимита default (x-rate-limit-class-test): второй тестовый заказ подряд больше не получает 429; боевой ключ — bulk, как раньше.
    • Описаны: mode поиска при одном бренде, адрес доставки заказа при «Принять» (delivery.address, без него — адрес компании; pickup_address заявки в заказ не переносится).
  • 1.4.02026-09-23
    • Биржа для программы покупателя-организации: создание, правка и снятие заявки, список и одна заявка, КП по заявкам, «Принять» целиком и по строкам, отклонение КП.
    • Заказы покупателя: перепроверка условий POST /buyer/quotes, оформление POST /buyer/orders, список и один заказ (заказы магазина и из КП, фильтр source).
    • Вебхуки покупателю: exchange.proposal.received, exchange.proposal.replaced, exchange.proposal.withdrawn, exchange.request.closed и order.* по его заказам.
    • Песочница покупателя: sandbox.scenario в заявке (standard, two_sellers, withdraw, no_answer, expire) — роботы-продавцы отвечают КП; робот 5B подтверждает, отклоняет строку с qty = 13 и отгружает тестовые заказы.
    • Каждое событие доставки описано отдельной записью в x-webhooks: назначение, кому приходит и пример конверта с data.
    • Страница подключения public/api.html: разделы «Биржа: программа продавца», «Биржа: программа покупателя», «Заказы покупателя» и «Песочница биржи» с пошаговыми сценариями.
  • 1.3.02026-09-22
    • Биржа для программы продавца: лента заявок, КП и черновики, снятие КП, синхронизация по since_seq; справочник /dictionary/exchange.
    • Право ключа exchange и регион заявок ключа (own, all); ключ отключённого сотрудника — 401 KEY_OWNER_INACTIVE.
    • Заказы из КП в /seller/orders: source, exchange, actions, buyer.ref; confirm и reject — 409 EXCHANGE_ORDER; ship — как раньше.
    • Вебхуки биржи exchange.request.* и exchange.proposal.*, шаблоны order.*, import.*, exchange.*, фильтр заявок options; «*» событий биржи не включает.
    • Песочница биржи: POST /sandbox/exchange/requests — тестовая заявка робота-покупателя со сценарием.
    • Юридические ворота: прайс, пакет предложений, утверждение загрузки и КП боевым ключом — после принятия документов площадки (403 LEGAL_NEEDS).
  • 1.2.02026-09-17
    • Вебхуки: подписка методами /webhooks и в кабинете, подпись HMAC, повторы 7 раз за сутки, проверочное событие ping.
    • Код ошибки LIMIT_EXCEEDED (409) — предел подписок.
    • В следующем обновлении: перепроверка цен и заказы покупателя через API.
  • 1.1.02026-09-16
    • Первая публикация API v1: прайсы файлом, пакет предложений, склады, загрузки и их проверка.
    • Заказы продавца с подтверждением по строкам, отказом и отгрузкой; синхронизация по since_seq.
    • Поиск покупателя, песочница с тестовыми ключами.
    • В следующем обновлении: вебхуки, перепроверка цен и заказы покупателя через API.