Мок-API
Wildberries
Что покрыто в моке Wildberries, откуда взята спецификация и чем мок отличается от боевого API.
На этой странице · 7
У Wildberries API разложен по десятку хостов и по четырнадцати файлам
спецификации. Это единственный сервис стенда, где X-APIStend-Upstream
у разных методов разный, и единственный, где встречаются все пять глаголов.
Откуда взята спецификация
Целостность снимка зафиксирована: specs/wildberries/MANIFEST.tsv содержит
sha256 каждого файла.
Что покрыто
316 методов на момент написания: 86 с примером ответа, 181 собирается
по схеме, 49 отдают пустой конверт. Числа живые — актуальные показывает
/api/services и каталог.
Группы каталога повторяют файлы спецификации: Товары, Заказы FBS, DBS, DBW, Поставки FBW, Самовывоз, Маркетинг и продвижение, Общение с покупателями, Аналитика и данные, Отчёты, Тарифы, Документы и бухгалтерия, Общее. «Wildberries Цифровой» вынесен отдельной группой с пометкой «отдельный продукт»: он живёт на своём хосте и требует токена другой категории.
Особенности вызова
Авторизация — заголовок Authorization с токеном, без префикса Bearer
(шлюз принимает и с префиксом):
curl -s localhost:8080/wb/api/v3/warehouses -H "Authorization: $KEY"
Глагол значим. Распределение по методам: 139 POST, 135 GET, 16 PUT,
15 PATCH, 11 DELETE. Запрос с чужим глаголом вернёт 404 и подсказку
X-APIStend-Did-You-Mean.
Чтение остатков — это POST
/api/v3/stocks/{warehouseId} работает по трём глаголам сразу: POST —
получить остатки, PUT — обновить, DELETE — удалить. Клиент, который
запрашивает остатки через GET, в бою получит ошибку; мок ведёт себя так же.
Лимиты и ошибки
Профиль — до 300 запросов в минуту, ёмкость ведра 300, при превышении 429
и X-Ratelimit-Retry: 20. Wildberries — единственный из трёх, кто отдаёт
заголовки лимита: X-Ratelimit-Limit, -Remaining и -Reset приходят
с каждым ответом, -Retry добавляется к 429.
Конверт ошибки самый подробный из трёх:
{"title":"Not Found","detail":"path not found","code":"NotFound","requestId":"0dcf08a519c89fe14c98a7c727fd116f","origin":"ag-gateway","status":404,"statusText":"not_found","timestamp":"2026-09-09T10:15:46.639Z"}
Поле origin различается: ag-api у ошибок сервиса, ag-gateway —
у 404 по пути и у таймаута. requestId совпадает с X-Request-Id ответа.
Известные отличия от боя
Важно
suppliers-api.wildberries.ru— исторический адрес. Он указан в профиле сервиса как «что подменяет/wb», но реальные хосты методов в каталоге другие:marketplace-api,content-api,advert-api,seller-analytics-api,statistics-api,discounts-prices-api,feedbacks-apiи ещё десяток. Какой хост подменяет конкретный метод, показываетX-APIStend-Upstreamв его ответе.- Разные хосты в бою — разные лимиты и разные токены. В песочнице ведро одно на весь сервис, а токен один на все хосты. Код, разложенный по хостам правильно, на стенде работать будет; обратное неверно.
- 29 методов отвечают
204с пустым телом — там, где в спецификации успешный ответ описан без содержимого. - Больше половины ответов собрано по схеме (
X-APIStend-Source: schema): форма из спецификации, значения выдуманы. - Тело ответа не зависит от запроса — включая значение
{warehouseId}в пути. Два запроса на разные склады дают побайтово одинаковый ответ.
Данные, а не только форма
Стенд отвечает не абстрактными записями, а согласованным набором: все методы говорят про одни и те же артикулы, и цифры внутри товара сходятся между собой. Три вещи, из-за которых на этом можно считать экономику, а не только разбирать JSON.
Даты живут относительно сегодня. В документации Wildberries заказы датированы 4 марта 2022 года, воронка отвечает за период с июня 2023 по март 2024, тариф коробов действует до февраля 2024. Витрину продавца считают за последние 7, 30 или 90 дней — в это окно ничего из перечисленного не попадает ни при каком запросе. Поэтому даты сдвигаются: самая свежая приходится на сегодня, расстояния между ними сохраняются, а сроки действия уезжают в будущее.
Страница не обрезает ответ молча. Списки отдают до тысячи записей за раз —
столько же, сколько разрешают боевые методы Wildberries. Карточная выдача
settings.cursor.limit держится своего документированного потолка в сто
карточек: карточка тяжёлая, и боевой сервис тысячу их не отдаёт. Прежний общий
предел в двести записей выглядел как отсутствие данных — клиент просил тысячу
цен, получал двести и считал, что на остальные карточки цены не заведены.
Каталог листается курсором. settings.cursor.nmID из ответа, возвращённый
в следующий запрос, продолжает выдачу со следующей карточки; конец каталога —
пустой список. Раньше курсор стоял на месте, и обход не заканчивался никогда.
Справочники пересекаются с каталогом. Комиссии приходят по одной строке на каждый предмет каталога, а не единственной записью «Оборудование зуботехническое» из документации: без пересечения ставку не к чему подобрать, и юнит-экономика не считается.
Заказы живут во времени. За последние девяносто дней песочница ведёт журнал:
кто что заказал, что из этого выкупили, что отменили, что вернули. Из него
отвечают /api/v1/supplier/orders, /api/v1/supplier/sales и лента заказов —
поэтому у записей разные дни, разные товары и разные исходы. Доли исходов берутся
из воронки самого товара: у карточки с 380 выкупами из 400 заказов и в журнале
девять выкупов из десяти, и процент выкупа, посчитанный клиентом по журналу,
сходится с тем, что отдаёт аналитика.
В продажи попадают только состоявшиеся сделки и возвраты: отменённый заказ
продажей не становится. Возврат уносит выплату обратно — forPay со знаком минус
и saleID, начинающийся с R, как в бою.
Период запроса влияет на ответ. dateFrom сужает выдачу, а не игнорируется:
семь дней, тридцать и девяносто дают три разных ответа. Аналитика возвращает
период, который спросили, а не интервал из документации, и цифры за него —
сумма дней окна: квартал больше недели. .../products/history отдаёт ряд по дням
окна с разными значениями — то, из чего строится график.
Без явного limit списки событий отдают всё запрошенное окно за раз, как боевая
статистика Wildberries: клиент строит график за девяносто дней одним запросом,
а не собирает его из страниц по двадцать записей.
Реклама сходится сама с собой. Список кампаний, статистика и разнесение
расхода по карточкам говорят об одних и тех же кампаниях: advertId из
/adv/v1/adverts — тот самый, по которому отвечает /adv/v3/fullstats. Клиент
берёт идентификаторы из списка и просит по ним статистику; ответ про другие
кампании он привязать не может, и расход у него уходит в никуда вместе с ДРР,
кликами, CTR и CPC. Чужой идентификатор не выдумывается — статистики по нему
просто нет, как и в бою.
Расход сходится на всех уровнях: сложили карточки — получили день площадки, сложили площадки — день кампании, сложили дни — итог в шапке. Показатели согласованы между собой: кликов не больше показов, CTR — их отношение, CPC — расход, делённый на клики.
Кампании покрывают весь каталог, а не первые двадцать карточек: у каждой свои
двадцать пять артикулов, свой период и свой бюджет. Завершённая кампания не
тратит после даты окончания — график расхода у неё обрывается там, где должен.
Без beginDate и endDate статистика отдаёт последнюю неделю: у боевого метода
эти параметры обязательны.
Вопрос про карточку получает ответ про неё. nmID, nmIDs, filterNmID,
imtID, артикул продавца, штрихкод и поисковая строка с артикулом внутри —
всё это адресует конкретный товар, и ответ приходит про него, а не первой
страницей каталога. Курсор settings.cursor.nmID при этом остаётся курсором:
он говорит «продолжи с этой карточки», а не «покажи только её».
У каждой карточки есть всё. Заказы, выкупы, возвраты, воронка, история по
дням, кампания с расходом, комиссия по предмету, остаток — по любому артикулу
каталога, а не по первым двадцати. Выгрузка заказов сходится с аналитикой:
сложив записи /api/v1/supplier/orders по товару за период, получите то же
число, что стоит в orderCount воронки за тот же период.
У оценки товара — два масштаба, и оба сходятся, но каждый со своим.
POST /api/analytics/v2/item-rating отдаёт рейтинг и отзывы дважды: по
каждой карточке (items[].feedbackRating/feedbackCount/пятизвёздочные-
однозвёздочные) и по аккаунту целиком (sellerRating, feedbackIncrease).
У аккаунтного свода в свою очередь два числа с разным охватом — ровно так же,
как в боевой спецификации: feedbackIncrease.total (и total внутри каждой
звезды) — это всё время существования товаров, то же число, что отдаёт
GET /api/common/v1/rating; feedbackIncrease.current (и current внутри
каждой звезды) — это прирост за запрошенный currentPeriod, и вот он ровно
складывается из feedbackCount.current/звёзд всех карточек каталога: сложите
их по всем страницам — получите feedbackIncrease.current день в день. Читать
.total, ожидая, что он сойдётся с суммой по карточкам за неделю или месяц,
не стоит — это не то же число: он больше настолько, насколько отзывы старше
запрошенного периода.
Запись сохраняется
Четыре метода не просто отвечают успехом, а меняют то, что видит следующий запрос той же песочницы, — как в бою:
POST /content/v2/cards/update— название, описание, характеристики. ПроверяютсяnmID/vendorCode/sizes(обязательны), длина названия (60 символов) и описания (5000), и характеристики — по небольшому справочнику песочницы (14177449«Цвет» и14177450«Состав» — настоящие ID из спецификации; полного каталога характеристик по каждому из тысяч предметов WB стенд не ведёт).GET /content/v2/object/charcs/{subjectId}отдаёт этот же справочник — только характеристики запрошенного предмета, с настоящими именами («Цвет», а не название товара), теми же ID, что приметcards/update.POST /api/v2/upload/task— цена и скидка. Отвечаетidзагрузки сразу же обработанным (status: 3вGET /api/v2/history/tasks) — песочница не моделирует очередь обработки.POST /content/v3/media/saveиPOST /content/v3/media/file— фото. Ссылки сохраняются как есть; у файла, загруженного вторым методом, реального содержимого нет — только адрес, детерминированный по артикулу и номеру.
GET/POST /content/v2/get/cards/list и GET/POST /api/v2/list/goods/filter
отдают уже сохранённое. Изменения приватны для ключа, которым записаны
(разные песочницы не видят чужих правок), и переживают перезапуск стенда —
до POST /api/sandbox/reset, который их и стирает.