Marketplace ABC: автоматизация Ozon API за 6 спринтов
Дистрибутор бытовой химии «БытТорг» вёл 4 200 SKU вручную и терял 11% оборота на штрафах за просрочку. Шесть спринтов — и команда из двух человек закрывает работу пятерых, а штрафы упали до 0.7%.
Дайджест Креастры
- Ozon Seller API закрывает 90% рутины: цены, остатки, FBO/FBS — всё через идемпотентные джобы
- Очередь на Redis + воркеры на Node — устойчивее, чем cron-скрипты на сервере склада
- Главный риск — лимиты API: пачкуем запросы по 1000 SKU и закладываем backoff с джиттером
В январе 2024 «БытТорг» — питерский дистрибутор бытовой химии — отгружал на Ozon 4 200 SKU и держал в штате трёх операторов «обновлятелей цен». Каждое утро открывали Excel, тянули остатки из 1С, перебивали в личный кабинет селлера. Раз в неделю — штрафы за просрочку отгрузки, потому что забыли поднять остатки. Через шесть спринтов всё это делают два инженера и одна очередь на Redis.
Спринт 1. Инвентаризация процессов
Перед автоматизацией мы две недели сидели в офисе клиента и вели хронометраж. Получилось 14 повторяющихся процессов — от обновления остатков и цен до генерации этикеток FBS и обработки возвратов. Семь из них занимали 80% времени операторов. С них и начали.
Спринт 2. Каркас интеграции
Подняли отдельный сервис на Node 20 + Fastify, к нему приклеили Postgres для журналов и Redis для очередей. Авторизация в Ozon Seller API через Client-Id и Api-Key, всё в одном секретном хранилище. Главное правило — клиент-Id никогда не попадает в логи.
// Воркер обновления остатков — идемпотентный по offer_id
await queue.process('stock-sync', 8, async (job) => {
const batch = job.data.items.slice(0, 1000); // лимит Ozon
const res = await ozon.post('/v2/products/stocks', { stocks: batch });
for (const r of res.result) {
if (!r.updated) await journal.warn({ sku: r.offer_id, errors: r.errors });
}
return { updated: res.result.filter(r => r.updated).length };
});Спринт 3. Цены и матрицы
У «БытТорга» цена зависит от трёх параметров: закупка, плечо логистики и комиссия категории. Положили формулу в БД, привязали к ней Min/Premium/Action — три уровня цен, которые уходят в Ozon одной пачкой раз в час. Параллельно подняли алерт: если цена ушла ниже себестоимости больше чем на 3% — менеджер получает сообщение в Telegram и публикация откатывается.
Спринт 4. FBS-этикетки и отгрузки
- Подписка на webhooks Ozon на новые отправления FBS
- Автоматическая генерация этикеток в PDF и складирование в S3-совместимое хранилище
- Печать пачкой через CUPS — оператор на складе нажимает одну кнопку
- Закрытие отправлений в API сразу после факта отгрузки
- Журнал событий на каждое отправление с TTL 90 дней
Спринт 5. Возвраты и претензии
Самый «грязный» процесс. У Ozon API возвратов несколько эндпоинтов в зависимости от типа. Мы написали единый адаптер: на входе тип события, на выходе нормализованная запись в нашей таблице returns с полями reason_code, refund_amount, ozon_id, decision_due_at. Менеджер открывает один интерфейс — не три разных раздела личного кабинета.
Спринт 6. Аналитика и алерты
Поверх Postgres подняли Metabase — собственнику дашборд по марже за вчера, операционке — по проблемным SKU и просроченным отгрузкам. Алерты в Telegram-канал инженеров: rate-limit от Ozon, рост 5xx, рассинхрон остатков 1С→Ozon больше 5%.
Подводные камни Ozon API
- Лимиты по эндпоинтам разные — общий «1000 запросов в минуту» миф
- Идемпотентность только по части методов; для остальных нужен журнал на нашей стороне
- Schema иногда меняется без уведомления — держим snapshot-тесты на ответы
- Часовые пояса в ответах не всегда UTC — приводим в воркере
- Возврат 200 ОК ещё не значит, что обновление применилось — читаем result
Что в цифрах
За шесть спринтов (12 недель, два инженера + аналитик) команда «БытТорга» сократилась с пяти операторов до двух. Штрафы за просрочку упали с 11% выручки до 0.7%. Время на ввод нового SKU — с 23 минут до 3.5. Окупаемость проекта — 4.5 месяца.