Интеграция с Wildberries
Всё общение с Seller API Wildberries идёт только из бэкенда через один компонент —
WbApiClient (backend/src/modules/wb-api). Он берёт на себя базовые адреса доменов, локальный
rate-limit, повторы, таймауты и обработку ошибок, поэтому функциональные модули (оцифровка, цены,
автобиддер и др.) не знают деталей протокола. Решения —
ADR-0006, ADR-0041,
ADR-0048, ADR-0049,
ADR-0051, ADR-0057,
ADR-0066. Справка по самим методам WB —
WB API.
Клиент и домены
WbApiClient.request({ scope, apiKey, path, method, body, query, timeoutMs, accountId }) реализован
на нативном fetch (Node 22) с AbortSignal.timeout, без axios. Токен кабинета передаётся в запросе:
сервисы расшифровывают его перед вызовом (Безопасность). scope выбирает базовый адрес:
| Scope | Базовый URL (prod) | Лимит по умолчанию |
|---|---|---|
common | common-api.wildberries.ru | 3 запроса / 30 с |
content | content-api.wildberries.ru | 100 / мин |
analytics | seller-analytics-api.wildberries.ru | 60 / мин |
prices | discounts-prices-api.wildberries.ru | 60 / мин |
marketplace | marketplace-api.wildberries.ru | 100 / мин |
statistics | statistics-api.wildberries.ru | 60 / мин |
advert | advert-api.wildberries.ru | 100 / мин |
feedbacks | feedbacks-api.wildberries.ru | 100 / мин |
buyer-chat | buyer-chat-api.wildberries.ru | 60 / мин |
supplies | supplies-api.wildberries.ru | 60 / мин |
returns | returns-api.wildberries.ru | 60 / мин |
documents | documents-api.wildberries.ru | 60 / мин |
finance | finance-api.wildberries.ru | 60 / мин |
user-management | user-management-api.wildberries.ru | 5 / с |
Реестр адресов — wb-api-scope.ts (WB_API_BASE_URLS); для части доменов есть песочница
(WB_API_USE_SANDBOX=true). Точные лимиты методов заданы в wb-rate-limits.ts
(WB_ENDPOINT_RATE_LIMITS, ключ scope:path) и перекрывают scope-дефолт — например,
common:/ping — 3 за 30 с, common:/api/v1/seller-info — 1 в минуту, справочник складов
supplies:/api/v1/warehouses — 6 в минуту; фиды заказов и продаж (statistics) — по 1 запросу в минуту (у заказов допустим всплеск до 10).
Реестр дополняется по мере появления новых методов.
Token-bucket лимитер
Лимитер WbRateLimiter (ADR-0041) — token-bucket в Redis, отдельный на кабинет и метод:
ключ wb:rl:v2:{accountId}:{scope}:{path} (для запросов без кабинета — _global).
- Параметры бакета:
tokensна окноintervalMs, опциональныйburst(ёмкость всплеска). Токены восполняются плавно. - Захват атомарен: Lua-скрипт зеркалит чистую функцию-эталон
evaluateTokenBucket(wb-token-bucket.ts) — арифметика тестируется на TS, атомарность даёт Redis. Времяnowпередаётся из приложения. - Состояние ключа живёт до полного восполнения пустой корзины и истекает само.
- Потолок ожидания:
rateLimitWaitCeilingMs = max(WB_API_RATE_LIMIT_MAX_WAIT_MS, N × окно + 5 с)(по умолчанию 30 с). Если ждать дольше — клиент бросаетWbApiRateLimitedErrorлокально, не обращаясь к WB. WbQuotaServiceчитает состояние корзин без списания (used,capacity,resetsInMs) (GET /wb-accounts/:id/quota).
Обратная связь от WB (429 и заголовки)
Локальная модель всегда чуть оптимистичнее счётчика WB, поэтому 429 — штатная обратная связь, а не авария (ADR-0048, ADR-0066):
- Штраф бакета (
penalize): после реального 429 корзина осушается и получаетnotBefore— момент, раньше которого токен не выдаётся даже если он «восполнился». Штраф монотонен и записывается в том числе на последней попытке, чтобы следующий прогон не бился в то же закрытое окно. - Пауза на 429 =
min( max(Retry-After, восполнение одного токена, экспонента), потолок ): дляstatisticsэто 60 с, дляprices— 0,6 с. КороткийRetry-Afterподнимается до окна. - Сверка по заголовкам (
parseObservedRateLimit): на каждом ответе читаютсяX-Ratelimit-Limit,-Remaining,-Reset,-Retry; бакет только сужается доRemaining, а приRemaining: 0удерживается до сброса окна. Сбой сверки на запрос не влияет. - Прочие ошибки: 5xx и сетевые сбои повторяются короткой экспонентой с jitter
(
WB_API_RETRY_ATTEMPTS=3,WB_API_RETRY_BASE_MS=500); 401/403/404 не повторяются.
| Ошибка клиента | Когда |
|---|---|
WbApiUnauthorizedError / WbApiForbiddenError | 401 / 403 — токен отозван, истёк, нет прав |
WbApiNotFoundError | 404 |
WbApiRateLimitedError | 429 от WB или локальный потолок ожидания |
WbApiServerError | 5xx |
WbApiNetworkError | DNS/TCP/обрыв соединения |
WbApiResponseTimeoutError | таймаут чтения (общий дефолт WB_API_TIMEOUT_MS=10 000; тяжёлые методы задают свой timeoutMs, например отчёт о реализации — ADR-0049) |
На 401 WbApiEvents публикует событие «WB отверг ключ»: пользователь получает уведомление
«Проверьте API-ключ Wildberries» (Уведомления).
Перенос джобы по лимиту (reschedule)
Ждать в воркере минуты — значит занимать слот concurrency, пока соседние кабинеты стоят.
Поэтому процессоры длинных синков (digitization-sync, rnp-ads-sync, rnp-finance-sync,
rnp-sections-sync, inventory-sync, autobidder-sync) оборачивают работу в
WbRateLimitRescheduleService.run(...) (ADR-0051):
- Внутри
AsyncLocalStorage-контекстаacquireпри ожидании дольше порога (5 с) бросаетWbRateLimitDeferError(waitMs)вместо сна. - Процессор переносит джобу в
delayedна нужную паузу без расхода попытки и с сохранением прогресса (курсор/страница), в прогрессе джобы отображается ожидание. - Короткие очереди (
pricing-bulk,booking-slot-monitor) не переносятся: у них вызовы не многостраничные, а бронировщику важна частота опроса.
Gate кабинета
Ограничения WB действуют на токен, а не на джобу, поэтому на один кабинет в «общие» бакеты одновременно
ходит одна джоба (WbAccountJobGate, ADR-0057): ключ wb:gate:{accountId}, lease 90 с с heartbeat 30 с.
Пользовательские запуски (ручной импорт, ручной синк) имеют приоритет над плановыми; плановые не копят
хвост и пропускают тик со статусом account-busy. При переносе по лимиту владелец продлевает lease на
паузу + 60 с, чтобы соседняя джоба не вклинилась в окно. Повторный POST импорта при уже живой джобе
возвращает alreadyRunning с её jobId. Подробнее — Фоновые задачи.
Переменные окружения
| Переменная | По умолчанию | Назначение |
|---|---|---|
WB_API_USE_SANDBOX | false | использовать песочницы доменов, где они есть |
WB_API_TIMEOUT_MS | 10 000 | таймаут запроса по умолчанию |
WB_API_RETRY_ATTEMPTS | 3 | число попыток |
WB_API_RETRY_BASE_MS | 500 | база экспоненциального backoff |
WB_API_RATE_LIMIT_MAX_WAIT_MS | 30 000 | базовый потолок ожидания лимитера |
SYNC_QUEUE_CONCURRENCY | 5 | параллельные кабинеты в sync-очередях |
Полный перечень — Переменные окружения.