В моих магазинах заказ, отгрузка и поступление денег постоянно оказываются в разных отчётах, хотя относятся к одной продаже. Когда я начал собирать собственную аналитику, именно на этом пришлось остановиться: забрать данные оказалось проще, чем правильно связать их между собой. Список заказов ещё не объясняет, сколько я заработал, а остаток товара сам по себе не говорит, сколько доступно покупателю. Поэтому от подключения к Ozon я жду не красивой таблицы, а возможности восстановить путь товара и денег. Из этой задачи у меня и вырос SellOps.
Если перевести вопрос «API Озон — это что?» на язык моей ежедневной работы, ответ получится простым: программа обращается к площадке за конкретными сведениями или передаёт ей разрешённые изменения. Вместо скачивания отчёта руками я отправляю запрос с доступом нужного магазина и обрабатываю ответ. Но единой кнопки «отдай всё и посчитай прибыль» там нет. Товары, отправления, финансовые операции и реклама требуют разных запросов, а иногда и разных способов авторизации. Ниже я покажу, что именно имеет смысл забирать и где обычно ломаются ожидания от такого подключения.
Какие данные даёт Ozon API для продавцов
Начинаю я с каталога, потому что без него остальные выгрузки трудно соединять. Запрос списка товаров через Ozon API нужен прежде всего для получения идентификаторов и построения связи между карточкой площадки и моим артикулом. Подробные характеристики, цены, состояние карточек и другие сведения могут приходить отдельными методами, а не внутри первоначального списка. Поэтому «товар найден» и «вся информация о товаре загружена» для меня разные состояния. Я сохраняю идентификаторы отдельно от названия: название можно поменять, и оно не должно разрывать историю продаж. Возможность создавать или обновлять карточки тоже существует, но конкретный набор доступных действий я проверяю по документации выбранного метода.
С остатками я сначала выясняю, какой склад и какую схему работы описывает ответ. Товар на складе, доступное к продаже количество и резерв под заказ могут означать разные вещи, поэтому складывать похожие поля без проверки нельзя. Для складов продавца предусмотрены сценарии передачи остатков, а для хранения на стороне площадки нужны соответствующие методы получения сведений. Это не взаимозаменяемые операции. В своей базе я сохраняю привязку остатка к складу и время получения данных, иначе вчерашнее количество легко принять за текущее. Если нужной детализации в ответе нет, я не восстанавливаю её догадками.
В операционном контуре я забираю отправления, их состав, статусы и доступные сведения об отменах и возвратах. Здесь приходится учитывать различия между FBO и FBS: методы и процесс обработки зависят от того, кто хранит и отгружает товар. Заказ покупателя и отправление нельзя без проверки считать одной сущностью, иначе при объединении таблиц появляются лишние продажи. Сведения о возврате тоже нужно получать и обновлять отдельно там, где этого требует выбранная схема данных. Для учёта я сохраняю исходные идентификаторы и связи между сущностями, а уже потом строю привычный список заказов. Получение истории не стоит путать с управлением отгрузкой: действия, меняющие состояние отправления, требуют отдельного внимания к правам и условиям метода.
Чем отличаются ключи кабинета продавца и доступ к рекламе
Для Seller API я использую идентификатор клиента и API-ключ, созданный в кабинете соответствующего магазина. Такой доступ нужен для работы с продавцовыми данными в пределах назначенных разрешений. У рекламного контура отдельная авторизация: для Performance API применяются рекламные учётные данные, через которые получают токен доступа. Обычный ключ кабинета продавца не следует считать универсальным пропуском к рекламной статистике. Я развожу эти подключения в настройках и храню явную связь каждого доступа с магазином или рекламным аккаунтом. Особенно это помогает, когда магазинов несколько и ошибка выбора кабинета выглядит как вполне правдоподобный отчёт.
Через рекламный API я рассчитываю получать доступную статистику кампаний, расходы и показатели, которые поддерживает конкретный отчёт. Детализация до товара, периода или другого разреза зависит от рекламного инструмента и метода, поэтому обещать одинаковый набор полей для всей рекламы нельзя. Часть отчётов может формироваться через задачу: сначала запрашивается подготовка, затем проверяется готовность и скачивается результат. Выручку, приписанную рекламе, я не прибавляю к общей выручке магазина — это другой взгляд на продажи, а не дополнительный денежный поток. Перед сравнением рекламного отчёта с заказами проверяю период и правила отнесения результата к рекламе. Если правила не совпадают с моей моделью учёта, показываю показатели отдельно.
Права я выбираю от задачи: чтение каталога не даёт оснований разрешать интеграции менять цены или остатки. Названия и состав доступных ролей проверяю в кабинете, а для каждого метода смотрю требования в актуальной документации Ozon Seller API. Там меня интересуют обязательные поля, фильтры, формат ответа, ограничения и пометки об изменении или прекращении поддержки метода. Найденный в поиске старый пример запроса не заменяет эту проверку. Сам порядок подключения я вынес в материал о том, как получить ключи Ozon API, а секреты храню вне исходного кода и журналов запросов. Если ключ попал в переписку или открытый файл, считаю его скомпрометированным и заменяю, а не просто удаляю сообщение.
Почему лимиты Ozon Seller API замедляют большую выгрузку
Большой магазин обычно нельзя выгрузить одним обращением: списки разбиваются на порции, а подробности приходится получать дополнительными запросами. В зависимости от метода продолжение выбирается через страницу, смещение, курсор или другой предусмотренный механизм. Поверх этого могут действовать ограничения на размер ответа, период выборки и частоту обращений. Конкретные значения я здесь не привожу, потому что без проверки актуального метода они быстро становятся вредным советом. Для оценки времени я считаю, сколько порций предстоит получить, какие запросы зависят от предыдущих и какие отчёты площадка должна подготовить отдельно. Поэтому долгая первая загрузка сама по себе ещё не доказывает, что интеграция написана плохо.
Чтобы переживать ограничения по частоте запросов, я использую очередь и регулирую скорость в соответствии с правилами конкретных методов. При ответе о превышении лимита запрос не нужно немедленно повторять в бесконечном цикле: это только поддерживает перегрузку. Я увеличиваю паузу между повторными попытками, добавляю случайный разброс и учитываю подсказку сервера о времени ожидания, если она передана. Ошибки доступа и некорректные параметры такой паузой не лечатся, их нужно разбирать отдельно. После успешной порции сохраняю результат и точку продолжения, чтобы сбой не заставлял заново обходить всю историю. Если срок действия курсора или устройство метода не позволяют продолжить с прежнего места, повторный обход должен безопасно обновить уже загруженные записи.
Неполная выгрузка не означает отсутствие продаж, расходов или остатков. Пока очередь не обработана и все нужные порции не получены, я помечаю период как незавершённый и не использую его для окончательного расчёта прибыли. При неудачном обновлении сохраняю прежние данные вместе с отметкой об их давности, а не заменяю их пустым ответом. Иначе технический сбой превращается в выдуманное обнуление склада или внезапный рост заработка.
Как из финансовых данных получить расчёт, который можно проверить
Финансовые методы нужны мне для получения доступных начислений, удержаний и других операций, связанных с работой магазина. Но сумма заказов, начисленный доход и выплата на расчётный счёт отвечают на разные вопросы. Выплата отражает движение денег и расчёты с площадкой, а не автоматически прибыль за выбранный период. Я сначала определяю, считаю ли результат по датам финансовых операций или экономику конкретных продаж с последующими корректировками. Смешивать эти подходы внутри одного показателя нельзя: возврат и удержание могут попасть в другое время относительно исходного заказа. Для воспроизводимого расчёта по периоду использую следующий порядок.
- Собираю базу дохода за выбранный период по финансовым операциям. Отдельно выделяю продажи и относящиеся к доходу корректировки, сохраняя исходные знаки сумм. Проверяю, не содержит ли выбранное поле уже вычтенные удержания. Покупательский платёж не подставляю вместо дохода продавца без сверки состава начислений.
- Применяю возвраты и отменяющие начисления операции, если они ещё не учтены в базе. Затем вычитаю комиссию, логистику и прочие расходы площадки, которые относятся к принятому контуру расчёта. Для каждой категории проверяю, не вошла ли она в другую итоговую сумму. После этого получаю результат после расходов площадки, но ещё без полной себестоимости бизнеса.
- Вычитаю себестоимость реализованного товара из своего учёта закупок. Возвраты обрабатываю по фактической судьбе товара и принятой учётной модели, а не автоматически как новую закупку или безусловное восстановление стоимости. Добавляю относящиеся к расчёту расходы, которых нет в полученных операциях Ozon. Если закупочная стоимость неизвестна, оставляю явный пропуск, а не подставляю нулевую себестоимость.
- Учитываю рекламу ровно в том месте, где она ещё не была вычтена. Сопоставляю финансовые списания с рекламными отчётами, учитывая различия дат и состава показателей. Затем добавляю применимые налоги и общехозяйственные расходы из внешнего учёта, если нужен результат всего бизнеса. Если этих данных нет, называю показатель результатом до таких расходов, а не чистой прибылью.
После расчёта я делаю сверку: итог каждой категории должен раскладываться до конкретных исходных операций. Если часть расходов не привязана к товару или отправлению, она остаётся отдельной строкой на уровне магазина. Распределять её по артикулам можно только по заранее выбранному правилу, которое видно пользователю отчёта. Иначе точность до товара получается нарисованной. Для проверки я выбираю завершённую продажу, нахожу связанные начисления, удержания и корректировки, а затем сравниваю ручной расчёт с программным. Расхождение ищу в составе операций, знаках, датах и повторных записях, а не маскирую поправочным коэффициентом.
Конкретная ошибка возникает, когда расход на рекламу уже попал в финансовые данные, а продавец дополнительно вычитает расход из рекламного отчёта. Получается, что одна и та же экономическая затрата уменьшает результат повторно. В деньгах прибыль оказывается занижена на всю повторно учтённую сумму, даже если оба источника по отдельности загрузились без ошибок. Из-за такого отчёта можно отключить кампанию или поднять цену на товар, который на самом деле приносит приемлемый доход. Я устраняю это не удалением рекламной статистики, а разделением назначения источников: один служит для денежного учёта, другой — для анализа рекламы. Если состав расходов в источниках различается, сначала составляю соответствие категорий и выясняю, что действительно пересекается.
Как я организую загрузку и что проверить завтра
Первую загрузку я отделяю от регулярного обновления. Историю можно собирать в фоновом режиме, пока свежие остатки и текущие отправления получают приоритет в очереди. После первоначального обхода я запрашиваю изменения там, где метод это позволяет, и повторно проверяю недавние данные с учётом возможных корректировок. Для методов без подходящего фильтра нужен контролируемый повторный обход, а не предположение, что старые записи навсегда неизменны. Такой порядок уменьшает лишние обращения, но не отменяет сверок. Обещать точное время загрузки без объёма магазина, глубины истории и набора методов я бы не стал.
Заодно я делаю обработку повторных ответов безопасной: уже известная операция должна обновиться, а не превратиться в новую продажу. Для этого храню идентификатор магазина вместе с идентификатором сущности и выбираю ключ записи по устройству конкретных данных. Одного артикула для финансовой операции недостаточно, потому что с товаром происходит множество разных событий. При отправке изменений после обрыва связи сначала выясняю, было ли действие выполнено, если метод позволяет это проверить. Подробно связку учёта и обмена я разобрал в статье про интеграцию Ozon с 1С без задвоенных продаж и расхождений в остатках: там эта проверка нужна уже для согласования систем, а не просто для красивого отчёта.
В самой интеграции мне нужен журнал состояния загрузок по каждому магазину и источнику. Я хочу видеть время последнего успешного обновления, границы обработанного периода, оставшуюся очередь и причину остановки. Секретных ключей и лишних персональных данных в таком журнале быть не должно. Отдельно проверяю, не изменился ли формат ответа: успешный запрос ещё не гарантирует, что программа правильно поняла все поля. Если документация предупреждает об изменении метода, сначала проверяю новую обработку на ограниченной выборке и только потом переключаю регулярную загрузку. Так вопрос «что умеет API Озон Селлер» превращается из списка возможностей в понятный процесс получения проверяемых данных.
Завтра я бы начал с таблицы, в которой каждой задаче соответствует источник, нужное право доступа, период загрузки и способ проверки результата. Для каталога проверкой станет сопоставление идентификаторов, для остатков — сверка склада и значения на близкий момент времени, для финансов — разбор исходных операций. Затем подключил бы доступ с минимально необходимыми правами к выбранному магазину и выгрузил ограниченный фрагмент данных. До массовой загрузки проверил бы повтор запроса, прерывание процесса и восстановление после ошибки. И только после совпадения ручной проверки с программным результатом расширял бы историю и подключал остальные магазины.
Хотите увидеть эти цифры по своим товарам?
Попробовать SellOps бесплатно