API для учётной системы
Передавайте себестоимость и остатки из 1С (или любой другой программы)
прямо в SnappyCheck — без файлов и ручной загрузки. Ниже всё, что нужно
программисту: адрес, ключ, готовые примеры запросов, правила записи, лимиты и
коды ошибок.
Что это и зачем
Аналитика считает прибыль по себестоимости и показывает остатки. Оба числа живут
в вашей учётной системе и меняются каждый день. Этот API нужен, чтобы они
попадали в SnappyCheck сами.
Что умеет ключ: писать себестоимость и остаток по баркоду для ОДНОГО
магазина и (если разрешено отдельной галочкой) читать записанные значения
обратно. Всё.
Чего не умеет: ключей маркетплейсов, заказов, финансов, данных других
магазинов он не видит и увидеть не может — это отдельный, намеренно узкий класс
доступа.
Данные привязываются к баркоду (штрихкоду товара). Это
единственный идентификатор, общий для всех площадок, поэтому себестоимость,
загруженная один раз, работает и в Ozon, и в Wildberries, и в Lamoda.
Ключ и адрес
Базовый адрес: https://portal.snappycheck.ru/api/v1 — только
https; на http:// сервер отвечает редиректом 308, а не
данными. Сам по себе базовый адрес ничего не отдаёт (404) — обращайтесь к
конкретным точкам: /ping, /status, /items.
Аутентификация: заголовок Authorization: Bearer sc_live_… в
каждом запросе. Больше ничего не требуется — ни подписи, ни сессии.
Где взять ключ
- Владелец магазина заходит в портал → раздел «Интеграция»
(portal.snappycheck.ru/integration).
Раздел доступен администратору магазина или владельцу аккаунта.
- Придумывает имя ключа («1С основной») и нажимает «Создать ключ».
- Ключ показывается один раз, целиком. Потерян — восстановить нельзя,
нужно выпустить новый и отозвать старый.
Одновременно у магазина может жить не больше двух активных ключей —
этого хватает, чтобы заменить ключ без простоя: выпустили новый, перевели
выгрузку, отозвали старый.
Две настройки ключа
- «Разрешить читать значения» — галочка при создании. Без неё
GET /api/v1/items отвечает 403: даже украденный ключ
не выгрузит вашу себестоимость. Запись работает всегда.
- «С каких IP принимать» — поле в строке ключа в портале: IP-адреса
(через запятую), с которых ключ принимается; с любого другого адреса —
403 ip_not_allowed. Пусто — принимается отовсюду. Сравнение точное,
по отдельным адресам; подсети (/24) не поддерживаются. Удобнее всего
заполнить после первого удачного обмена: под полем появится адрес, с которого он
пришёл, и кнопка «разрешить только его».
Ключ — это пароль к данным магазина. Не кладите его в git, в
общие папки и в письма. Если ключ мог утечь — отзовите его в портале, обмен по
нему прекращается сразу.
Проверка связи
GET/api/v1/ping — самый первый запрос,
который стоит сделать. Ничего не меняет, проверяет ключ и показывает, к какому
магазину он привязан.
curl -s https://portal.snappycheck.ru/api/v1/ping \
-H "Authorization: Bearer sc_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
{
"store": "ООО Ромашка",
"can_write": true,
"can_read_values": false
}
store — название магазина: убедитесь, что это тот магазин, который
вы ожидали. can_read_values — разрешено ли этому ключу читать
значения (см. Читать значения).
Отправить данные
POST/api/v1/items — основной метод.
Принимает пачку строк («партию») и записывает их магазину, к которому привязан
ключ.
curl -s -X POST https://portal.snappycheck.ru/api/v1/items \
-H "Authorization: Bearer sc_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 2026-08-05T09:00-delta" \
-d '{
"mode": "delta",
"dry_run": false,
"items": [
{"barcode": "4600000000001", "cost_price": 512.40, "stock": 17},
{"barcode": "4600000000002", "cost_price": 899},
{"barcode": "4600000000003", "stock": 0}
]
}'
{
"batch_id": "0f4a9d2e-6c1b-4f7a-9a3d-1f2b3c4d5e6f",
"status": "applied",
"duplicate": false,
"received": 3,
"applied": 2,
"skipped_locked": 0,
"unknown_barcodes": ["4600000000003"],
"unknown_count": 1,
"warnings": []
}
Поля запроса
| Поле | Тип | Значение |
mode | строка |
"delta" (по умолчанию) — прислали только изменения;
"full" — полная картина склада. См.
Режимы. |
dry_run | да/нет |
false по умолчанию. true — посчитать результат,
но НИЧЕГО не записывать. См. Предпросмотр. |
items | список |
Строки партии. Максимум 10 000 за запрос. |
items[].barcode | строка |
Обязательное. До 64 символов. |
items[].cost_price | число |
Себестоимость единицы. Можно строкой ("512.40"). |
items[].stock | число |
Остаток на вашем складе. Можно строкой. Ноль — валидное значение. |
Поле, которого нет в строке, не трогается. Хотите обновить только
остатки — присылайте строки вообще без cost_price.
Поля ответа
| Поле | Значение |
batch_id |
Идентификатор партии. По нему её видно в портале, в истории обменов, и по
нему делается откат. |
status |
"applied" — записана; "dry_run" — это был
предпросмотр, ничего не записано; "held" — сработал
стоп-кран, партия ждёт решения владельца в портале. |
duplicate |
true — эту партию уже принимали ранее по тому же
Idempotency-Key, повторно ничего не записано (см.
Идемпотентность). |
received | Сколько строк пришло в теле запроса. |
applied |
У скольких ТОВАРОВ реально сдвинулось хотя бы одно число. Прислали то же
самое, что уже хранится, — будет 0. Это нормальный,
здоровый результат синхронизации, а не ошибка. |
skipped_locked |
Сколько цен не записано из-за замка себестоимости (см.
Правила записи). |
unknown_barcodes |
Баркоды, которые не приняты: их нет ни в каталоге магазина, ни в его
себестоимости. Первые 100 штук — для диагностики. Значения по ним не
сохраняются, пришлите их снова, когда товар появится в каталоге. |
unknown_count | Сколько таких баркодов всего. |
warnings |
Список человекочитаемых предупреждений по-русски: какие строки отброшены
и почему, что выглядит подозрительно. Логируйте его. Партия при
этом принята. |
Правила записи
Часть правил неочевидна, и от них зависит, не потеряете ли вы данные. Прочитайте
эту таблицу до того, как настроите выгрузку.
| Что прислали | Что произойдёт |
cost_price больше нуля | Себестоимость записывается. |
cost_price: 0, null или поля нет |
Ничего не меняется. Обнулить себестоимость через API нельзя в
принципе — ноль в выгрузке почти всегда означает «данных нет», а не
«товар бесплатный». Обнулить можно только руками в портале. |
stock: 0 |
Записывается ноль. «На складе пусто» — это факт, а не отсутствие
данных. |
stock: null или поля нет | Остаток не трогается. |
| Отрицательное значение в любом поле |
Строка отбрасывается целиком, в warnings — номер строки.
Остальная партия применяется. |
| Пустой баркод или длиннее 64 символов |
Строка отбрасывается, в warnings — номер строки. Обрезать
баркод нельзя: обрезок склеил бы разные товары. |
| Один баркод встречается дважды |
Строки схлопываются поле за полем: если второе вхождение принесло
только stock, ранее собранная cost_price
сохраняется, а не затирается пустотой. |
| Баркод, которого нет ни в каталоге, ни в себестоимости |
Строка не принимается целиком — ни цена, ни остаток; баркод уходит в
unknown_barcodes. Каталог наполняется из кабинетов маркетплейсов
ночью, поэтому только что заведённый товар может доехать не сразу: пришлите
его в следующей выгрузке. Если в режиме full не принята
ни одна строка, партия отбивается ошибкой 422
(empty_full_batch) — иначе она обнулила бы остатки по всему
складу. |
| Товар с замком себестоимости |
Цена НЕ записывается (замок ставит продавец в портале — «эту цену веду
руками»), счётчик skipped_locked растёт. Остаток при
этом пишется как обычно — замок только про цену. |
Числа можно присылать строками ("512.40") — учётные
системы часто так и делают. Разделитель — точка.
Режимы delta и full
delta — «вот что изменилось»
Режим по умолчанию. Трогаются только присланные баркоды, остальные строки
магазина не задеваются вообще. Это правильный режим для регулярной выгрузки
каждые несколько часов.
full — «вот вся картина склада»
Всё то же самое, плюс одно важное следствие: всем товарам под интеграцией,
которых нет в партии, остаток становится нулевым. Логика простая: вы прислали
полную картину склада, значит, чего в ней нет — того на складе нет.
Что не обнуляется:
- себестоимость — никогда, режим касается только остатков;
- строки, которых интеграция ещё ни разу не касалась (продавец завёл их руками
в портале) — «полная картина» вашего склада про них ничего не утверждает.
curl -s -X POST https://portal.snappycheck.ru/api/v1/items \
-H "Authorization: Bearer sc_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: stock-2026-08-05" \
-d '{"mode": "full", "items": [
{"barcode": "4600000000001", "stock": 17},
{"barcode": "4600000000002", "stock": 4}
]}'
Не отправляйте full, если не уверены, что выгрузка
полная. Оборванный или отфильтрованный запрос в 1С — самая частая причина
обнуления склада. Против этого работают две защиты: если
full не
принесла ни одной годной строки, вы получите
422 и ничего не
запишется; если она обнулила бы больше половины склада — сработает
стоп-кран.
Предпросмотр (dry_run)
"dry_run": true прогоняет партию целиком — разбор, правила записи,
замки, все проверки, — и возвращает те же счётчики, что вернул бы боевой вызов,
но не записывает ничего. Ответ приходит со "status": "dry_run".
Пользуйтесь им при настройке выгрузки и после изменений в ней: он честно покажет,
сколько строк отбросится и сколько остатков обнулится, до того как это случится
на самом деле.
Предпросмотр не занимает ключ идемпотентности: тот же
Idempotency-Key потом сработает по-настоящему.
Повторы и идемпотентность
Сеть рвётся, задача падает по таймауту, cron запускается дважды. Чтобы повтор не
записал партию второй раз, присылайте заголовок Idempotency-Key —
любую строку до 64 символов, уникальную для этой партии (например,
«2026-08-05T09:00-delta»).
Если партия с таким ключом уже принималась у этого магазина, ответ придёт
200 с "duplicate": true и счётчиками ПЕРВОЙ партии —
ничего не записывается повторно.
Ключ длиннее 64 символов — это 400
(idempotency_key_too_long), партия не принимается. Обрезать его молча
нельзя: два разных ключа с одинаковым началом слиплись бы в один, и вторая партия
тихо стала бы «дублем» первой.
Без этого заголовка каждый запрос считается новой партией.
Читать значения
GET/api/v1/items?barcodes=a,b,c —
вернуть то, что сейчас хранится. Работает только у ключа, которому при создании
разрешили чтение (галочка в портале), иначе 403
read_not_allowed.
curl -s -G https://portal.snappycheck.ru/api/v1/items \
-H "Authorization: Bearer sc_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
--data-urlencode "barcodes=4600000000001,4600000000002"
{
"4600000000001": {"cost_price": 512.4, "stock": 17.0, "locked": false},
"4600000000002": {"cost_price": 899.0, "stock": null, "locked": true}
}
Баркоды, которых нет в базе магазина, просто отсутствуют в ответе — это не
ошибка. locked: true означает замок себестоимости: цену этого товара
ваши партии не перезапишут.
За один запрос принимается до 1000 баркодов; лишние молча отбрасываются —
режьте список на своей стороне. Пустой barcodes — пустой ответ
{}.
Состояние обмена
GET/api/v1/status — короткая сводка по
магазину. Удобно для мониторинга на вашей стороне: например, поднять тревогу,
если last_batch_at старше суток.
{
"last_batch_at": "2026-08-05T09:00:11.482913+00:00",
"last_batch_status": "applied",
"items_under_integration": 1840,
"locked_items": 3
}
| Поле | Значение |
last_batch_at |
Время последней партии магазина (UTC, ISO 8601). null — обменов
ещё не было. |
last_batch_status |
Её статус: applied, dry_run, held,
rejected, rolled_back. |
items_under_integration |
Сколько товаров магазина ведёт интеграция (хотя бы одно поле записано ею). |
locked_items |
Сколько товаров с замком себестоимости. |
Лимиты и частота
| Ограничение | Значение | Что будет при превышении |
| Строк в одной партии | 10 000 |
413 too_many_items — режьте выгрузку на части |
| Размер тела запроса | 4 МБ |
413 body_too_large |
| Частота записи | 1 обмен в 10 секунд на ключ |
429 rate_limited + заголовок
Retry-After: 10 |
Длина Idempotency-Key | 64 символа |
400 idempotency_key_too_long |
| Длина баркода | 64 символа |
отбрасывается только эта строка, с предупреждением |
| Баркодов в одном чтении | 1000 | лишние отбрасываются молча |
| Товарных позиций на магазин | 500 000 |
новые баркоды сверх потолка не заводятся (предупреждение в
warnings), обновления уже известных проходят как обычно |
| Хранение истории партий | 30 дней |
старые партии удаляются, откат по ним больше недоступен |
Троттлинг считается только по ЗАПИСИ (POST /items).
ping, status и чтение значений его не занимают и им не
ограничены. Отметка «был обмен» ставится ДО обработки партии, поэтому неудачная
партия тоже занимает окно — сломанный цикл не сможет долбить сервер отказами.
Как часто присылать. Раз в 10 секунд — это потолок защиты, а не
рекомендация. Реальной аналитике достаточно обмена раз в час; полную выгрузку
(full) обычно делают раз в сутки.
Коды ошибок
Тело любой ошибки этого API выглядит так:
{
"error": "неверный ключ",
"code": "invalid_key"
}
error — человеку в лог. code — программе.
Разбирайте именно code: текст error мы можем
переформулировать в любой момент, а code стабилен. У некоторых
ошибок в теле есть дополнительные поля — они указаны в таблице.
| HTTP | code | Что случилось и что делать |
| 401 | invalid_key |
Ключа нет в заголовке, он неверный или отозван. Проверьте заголовок
Authorization и живой ли ключ в портале. |
| 403 | ip_not_allowed |
Запрос пришёл с адреса, которого нет в списке разрешённых для этого ключа.
Поправьте список в портале. |
| 403 | no_active_access |
У магазина нет ни одного модуля с активным оплаченным периодом (или сам
магазин выключен). Обмен возобновится после оплаты, менять ничего в коде
не нужно. |
| 403 | read_not_allowed |
Ключу не разрешено читать значения. Разрешается галочкой при создании
ключа — у выпущенного ключа её не поменять, нужен новый ключ. |
| 400 | invalid_mode |
mode — только delta или full. |
| 400 | idempotency_key_too_long |
Idempotency-Key длиннее 64 символов. |
| 413 | too_many_items |
Строк больше 10 000. В теле поле limit — потолок в СТРОКАХ. |
| 413 | body_too_large |
Тело больше 4 МБ. В теле поле limit — потолок в БАЙТАХ. |
| 429 | rate_limited |
Чаще одного обмена в 10 секунд. Заголовок Retry-After говорит,
через сколько секунд повторить. |
| 422 | empty_full_batch |
mode=full не принёс ни одной годной строки — так обнулился бы
остаток всего склада, поэтому партия отвергнута целиком и НИЧЕГО не
записано. В теле warnings — почему строки отброшены. |
| 422 | held_batch_pending |
Предыдущая полная выгрузка задержана стоп-краном и ждёт
решения владельца в портале. Новые full не принимаются, пока
он не решит. delta работает как обычно. |
| 422 | invalid_body |
Тело не разобрано: не JSON, не тот тип поля. В теле detail —
что именно не понравилось. |
Все ответы 2xx поля code НЕ содержат —
оно есть только у ошибок. Ориентируйтесь на HTTP-статус, а внутри статуса — на
code: под одним статусом у нас живут разные причины.
Стоп-кран: задержанная партия
Если mode=full обнулила бы остаток больше чем у половины
товаров под интеграцией (и таких товаров не меньше 20), партия не
применяется. Ответ — обычный 200, но со
"status": "held": партия принята и сохранена целиком, данные магазина
не тронуты.
Решает человек: владелец магазина видит партию в портале, в разделе «Интеграция»,
и нажимает «Применить» (записать ровно то, что показано) или
«Отклонить» (данные остаются как есть).
Пока решения нет, каждая следующая full (в том числе с
dry_run) получает 422 held_batch_pending.
Режим delta продолжает работать без ограничений.
Что делать в коде: получили "status": "held" —
не повторяйте отправку, а сообщите оператору, что полная выгрузка ждёт
подтверждения в портале. Повторы всё равно будут отвергнуты, а склад изменится
только после решения человека.
Резкий скачок и откат
Если больше чем у 20 % сравнимых строк себестоимость изменилась
более чем втрое, партия применяется, но помечается как подозрительная:
в warnings появляется предупреждение с числами, а в портале —
плашка со ссылкой на откат. Сравнимыми считаются строки, у которых уже была
прежняя цена (первая в жизни загрузка аномалией не выглядит).
Откат. Любую применённую партию владелец может откатить в портале в
течение 30 дней: себестоимость и остатки вернутся такими, какими были до неё.
Откат не трогает позиции с замком и те, что кто-то поправил вручную после обмена,
— их количество показывается отдельно. Если те же товары уже перезаписала более
поздняя партия, откат целиком запрещается: смешивать состояния из разных моментов
нельзя.
Отзыв ключа
Ключ перестаёт работать в трёх случаях. После отзыва любой запрос по нему —
401 invalid_key; восстановить отозванный ключ нельзя,
только выпустить новый.
| Когда | Причина |
| Сразу | Владелец нажал «Отозвать» в портале. |
| Через 30 дней |
Ключом ни разу не воспользовались с момента выпуска. Выпустили «на потом» —
он не доживёт до «потом». |
| Через 90 дней |
Ключ молчит: ни одного успешного запроса. Опасны не старые ключи, а
забытые — выданный подрядчику на разовую настройку ключ остаётся жить в
чужом ноутбуке. |
Оба автоматических отзыва предупреждаются за 7 дней — плашкой в портале, с
точным числом оставшихся дней.
«Воспользовались» — это ЛЮБОЙ успешный запрос, включая
ping, status и чтение. Ключ «только на чтение», которым
пользуются каждый день, отозван не будет.
Частые ошибки
Собрано из реальных обращений. Если ваша ситуация здесь — исправление займёт минуту.
Приходит код 308 (Permanent Redirect), тела нет
Запрос ушёл на http://. Сервер работает только по https и на
обычный http отвечает «переезжайте на https» (308). Браузер такой редирект проходит сам,
а HTTP-клиент 1С — нет, поэтому вы видите 308. Исправление: адрес
https://portal.snappycheck.ru, порт 443, защищённое соединение.
Соединение = Новый HTTPСоединение("portal.snappycheck.ru", 443, , , , , Новый ЗащищенноеСоединениеOpenSSL());
Запрос = Новый HTTPЗапрос("/api/v1/ping");
Запрос.Заголовки.Вставить("Authorization", "Bearer sc_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
Ответ = Соединение.Получить(Запрос);
Сообщить(Ответ.КодСостояния); // ждём 200
Сообщить(Ответ.ПолучитьТелоКакСтроку()); // {"store": "…", "can_write": true, …}
В ответе {"detail": "Not Found"} (404)
Такого адреса нет. Чаще всего открывают «корень» /api/v1 или
/api/v1/ — сам по себе он ничего не отдаёт. Рабочие адреса только
три: GET /api/v1/ping, GET /api/v1/status,
POST/GET /api/v1/items. Проверьте путь по буквам:
/api/v1/item, /api/v1/ping/ с лишним слешем или
/api/ping без v1 — тоже 404.
Открываю адрес в браузере — «неверный ключ» (401)
Так и должно быть: браузер из адресной строки не передаёт заголовок с ключом,
а без него любой запрос — 401. Браузер для проверки не годится. Проверяйте из
командной строки — одной строкой:
curl -s https://portal.snappycheck.ru/api/v1/ping -H "Authorization: Bearer sc_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Ответ вида {"store": "Название магазина", "can_write": true, …} — связь есть,
ключ верный. Тот же 401 из 1С означает: заголовок не дошёл (опечатка в слове
Authorization, нет слова Bearer и пробела после него,
лишние пробелы или перенос строки в ключе) либо ключ отозван в портале.
403 ip_not_allowed — «адрес не разрешён для этого ключа»
У ключа в портале заполнено поле «С каких IP принимать», и запрос пришёл с другого
адреса (например, сервер 1С переехал или выгрузку запустили с рабочего места). Владелец
магазина либо добавляет новый адрес в это поле, либо очищает его — тогда ключ снова
принимается отовсюду.
403 read_not_allowed при чтении
Ключ выпущен без галочки «разрешить читать значения» — писать он может, читать нет.
Для сверки нужен ключ с этой галочкой (выпускается заново, старый можно отозвать).
Как понять, что запрос вообще дошёл до SnappyCheck
В портале, в разделе «Интеграция», у ключа есть колонка «Использован» — время
последнего запроса с этим ключом — и под полем адресов подпись «последний обмен пришёл с
адреса …». Если там «ни разу», до нас не долетело ни одного запроса: смотрите два первых
пункта (http вместо https, неверный адрес).
Поддержка
Вопрос по интеграции, нужен пример под вашу конфигурацию 1С или кажется, что
сервис ответил неправильно — напишите нам и приложите batch_id из
ответа: по нему видно всё, что произошло с партией.
Почта: SnappyCheck@yandex.com
Ещё не подключили магазин? Аналитика Ozon,
Wildberries и Lamoda — бесплатно на 30 дней, без карты.
Попробовать бесплатно