Перейти к основному содержимому

API «Оптимы»: обзор

REST API бэкенда «Оптимы» (NestJS). Полный контракт со всеми эндпоинтами, схемами запросов и ответов — в Swagger UI; эта страница описывает общие соглашения, которые действуют для всех модулей.

Базовые адреса​

ОкружениеБазовый URLОткуда собирается
Productionhttps://api.optimawb.ruветка main, образ ghcr.io/affiliatefabriq/optima-backend:latest
Stageдомен stage в репозитории не зафиксирован — смотрите карточку приложения optima-backend (stage) в Coolifyветка develop, образ …:develop
Локальноhttp://localhost:3000npm run start:dev или docker compose up (см. Локальная разработка)

Глобального префикса и версионирования нет. В main.ts не вызываются setGlobalPrefix и enableVersioning: маршруты начинаются сразу с имени ресурса — https://api.optimawb.ru/wb-accounts, https://api.optimawb.ru/digitization/summary. Префикс /api есть только у Swagger UI (/api/docs).

Служебные маршруты​

МаршрутНазначениеДоступ
GET /healthreadiness: проверка PostgreSQL и Redisпубличный
GET /health/liveliveness процессапубличный
GET /versionверсия приложения (из package.json)публичный
GET /api/docsSwagger UIПубличный во всех окружениях, без авторизации (вызовы API из него требуют JWT — кнопка «Authorize»)
GET /admin/queuesBull Board — состояние очередей BullMQdev — открыт; production — HTTP Basic (QUEUE_BOARD_USER/QUEUE_BOARD_PASSWORD), иначе не монтируется
GET /metricsметрики Prometheusdev — открыт; production — HTTP Basic (METRICS_USER/METRICS_PASSWORD), иначе не отдаётся

Swagger и контракт OpenAPI​

  • Swagger UI: https://api.optimawb.ru/api/docs — открыт без логина и пароля. Локально — http://localhost:3000/api/docs.
  • Схема безопасности в контракте — access-token (HTTP Bearer, JWT). В Swagger UI нажмите Authorize и вставьте access-токен; авторизация сохраняется между перезагрузками страницы (persistAuthorization).
  • openapi.json выгружается командой npm run docs:openapi (из backend/). Файл не хранится в git (backend/.gitignore), но публикуется как артефакт openapi каждого прогона workflow backend-ci в GitHub Actions. Фронтенд генерирует из него типы (npm run api:generate в frontend/).
  • Ко всем операциям контракта автоматически добавлены ответы 400/401/403/404/429/500/502 со схемой ApiErrorBodyDto (backend/src/swagger.ts, augmentWithGlobalErrors).

Аутентификация​

Все маршруты, кроме помеченных @Public(), требуют access-токен: глобальный guard GlobalJwtAuthGuard зарегистрирован в AppModule.

Authorization: Bearer <accessToken>

Токены.

ТокенФорматСрок жизниГде задаётся
accessJWT (подпись JWT_ACCESS_SECRET), содержит id и роль пользователя15 минутJWT_ACCESS_TTL
refreshopaque-строка (256 бит), в БД хранится только sha-25630 днейJWT_REFRESH_TTL

Cookie API не использует. Оба токена приходят в теле ответа и передаются в теле запроса. Веб-приложение держит access-токен в памяти, а refresh-токен — в localStorage браузера.

Поток.

ШагЗапросТелоОтвет
РегистрацияPOST /auth/registeremail, password, displayNameпрофиль (роль seller)
ВходPOST /auth/loginemail, passwordaccessToken, refreshToken
ОбновлениеPOST /auth/refreshrefreshTokenновая пара; старый refresh отзывается (ротация)
ВыходPOST /auth/logoutrefreshToken204
Выход вездеPOST /auth/logout-all— (нужен access)все сессии пользователя завершены
ПрофильPOST /auth/me, PATCH /auth/me— / поля профиляпрофиль текущего пользователя
АдминPATCH /auth/users/:userId/role, POST /auth/users/:userId/revoke-sessions—только роль admin

Роли: admin, seller, manager (матрица прав — backend/docs/rbac-matrix.md). После 10 неудачных попыток входа учётная запись блокируется на 15 минут (AUTH_MAX_FAILED_ATTEMPTS, AUTH_LOCKOUT_MS). Подробнее — ADR-0003, ADR-0004, ADR-0043.

Область данных: wbAccountId и brandId​

  • wbAccountId (UUID кабинета WB) — основной параметр области данных. Его передают query-параметром или в теле почти все модульные эндпоинты. Сервис проверяет, что кабинет доступен пользователю: admin видит все кабинеты, seller и manager — только свои. Чужой или несуществующий кабинет даёт 404, а не 403 (факт существования чужого кабинета не раскрывается).
  • brandId (UUID бренда) — необязательный фильтр в части отчётов (/catalog/products, /digitization/reports/*, /digitization/profit, /autobidder/analytics/*, /pricing/summary). Доступ на уровне бренда для менеджеров выдаёт администратор через /brand-access; для маршрутов с brandId в пути предусмотрены BrandAccessGuard и декоратор @RequiresBrandAccess.

CORS​

  • development / test — разрешён любой origin.
  • production — разрешены только origin'ы из CORS_ORIGINS (через запятую). Если переменная не задана, CORS-заголовки не выдаются, и браузерные клиенты с другого домена API не вызовут.

Дополнительно helmet выставляет HSTS, X-Content-Type-Options: nosniff, Referrer-Policy и X-Frame-Options: DENY. Тело JSON ограничено значением Express по умолчанию — 100 КБ.

Формат ошибок​

Любая ошибка возвращается в едином формате ApiErrorBodyDto (его формирует GlobalExceptionFilter):

{
"code": "VALIDATION_FAILED",
"message": "Невалидный запрос",
"details": [{ "field": "email", "errors": ["isEmail"] }],
"traceId": "4d3a2e6e-9c5b-4f1c-9a7d-1a2b3c4d5e6f"
}
  • code — стабильный машиночитаемый код (на него стоит опираться в клиентской логике);
  • message — текст на русском;
  • details — для VALIDATION_FAILED массив полей, для лимитов — retryAfterSeconds;
  • traceId — совпадает с заголовком ответа X-Request-Id. Если клиент сам прислал X-Request-Id, используется его значение. По traceId запрос находится в логах.

Коды ошибок (backend/src/core/errors/api-error-code.ts):

codeHTTPКогда
VALIDATION_FAILED400DTO не прошёл валидацию
BAD_REQUEST400прочие некорректные запросы (например, период длиннее REPORT_MAX_PERIOD_DAYS)
UNAUTHORIZED401нет или недействителен access-токен
FORBIDDEN403недостаточно прав
NOT_FOUND404ресурс не найден или вне области данных пользователя
CONFLICT409конфликт состояния (например, email уже занят)
TOO_MANY_REQUESTS429входящий лимит API, лимит Wildberries или ИИ-провайдера; с заголовком Retry-After, если срок известен
WB_AUTH_FAILED502Wildberries отклонил API-ключ кабинета. Это не 401: сессия пользователя в порядке, проблема в ключе кабинета
WB_UNAVAILABLE502Wildberries недоступен (5xx, сеть)
LLM_UNAVAILABLE502ИИ-провайдер недоступен, отклонил доступ или не настроен
SERVICE_UNAVAILABLE503временная недоступность; в том числе запрос к БД превысил statement_timeout (тогда Retry-After: 5 и совет сузить период)
INTERNAL_ERROR500необработанная ошибка; стек пишется в лог, наружу не отдаётся

Валидация​

Глобальный ValidationPipe (backend/src/app.bootstrap.ts):

  • transform: true — query-строки приводятся к типам DTO (page=2 превращается в число);
  • whitelist + forbidNonWhitelisted — лишние поля отклоняются с 400, а не игнорируются молча;
  • ошибки вложенных объектов возвращаются dot-путями (address.city), в errors — имена нарушенных правил class-validator (isEmail, min, isUUID и т. п.).

Пагинация, сортировка и фильтры​

Списочные эндпоинты построены на примитивах backend/src/core/pagination.

  • Query: page (с 1, по умолчанию 1), limit (по умолчанию 20, максимум 100).
  • Сортировка: sortBy (только из белого списка полей эндпоинта) и sortDirection = asc | desc.
  • Ответ — конверт { items, meta }:
{
"items": [],
"meta": { "total": 142, "page": 2, "limit": 20, "totalPages": 8, "hasNext": true, "hasPrev": true }
}

Пагинация offset-based. Не-списочные эндпоинты возвращают объект без обёртки. Отчёты за период требуют dateFrom/dateTo; период длиннее REPORT_MAX_PERIOD_DAYS (по умолчанию 366 дней) отклоняется с 400 (ADR-0046).

KPI-эндпоинты GET /<модуль>/summary​

Единый формат KPI-ленты (ADR-0053):

GET /<модуль>/summary?wbAccountId=<uuid>&period=7d|30d
  • period — 7d или 30d (по умолчанию 30d);
  • в ответе всегда есть period, currency (RUB) и updatedAt (момент расчёта ответа, а не свежесть данных);
  • поле <показатель>Delta — относительное изменение к предыдущему периоду той же длины в процентах (100 — вдвое больше); null — «сравнивать не с чем», а не «без изменений»;
  • окна — календарные сутки UTC, предыдущее окно примыкает к текущему без пересечения.

Конверт SummaryEnvelopeDto используют /digitization/summary, /pricing/summary, /repricer/summary, /autobidder/summary, /autoreply/summary, /ai-assistant/summary. У /inventory/summary, /booking/summary, /rnp/analytics/summary и /digitization/financial-report/summary собственные схемы — смотрите Swagger.

Длительные операции (фоновые задачи)​

Синхронизации с WB выполняются в очередях BullMQ. Шаблон (ADR-0057):

  1. POST …/sync или POST …/import отвечает 202 с телом jobId и alreadyRunning. Если по кабинету уже идёт задача того же типа, новая не ставится: возвращается jobId существующей и alreadyRunning: true.
  2. Клиент опрашивает статус (GET …/status/:jobId или GET …/sync-status) и получает state (pending / active / completed / failed) и progress:
    • phase — queued, waiting-account (кабинет занят другой WB-задачей), running, rate-limited (пауза по лимиту WB), retrying, completed, failed;
    • message — готовый русский текст для UI;
    • resumeAt — когда задача продолжит работу после паузы;
    • attempt, maxAttempts — номер попытки.

Эндпоинты с этим шаблоном: /digitization/import, /digitization/financial-report/import (+ /digitization/import/status/:jobId), /autobidder/sync (+ /autobidder/sync/status/:jobId), /catalog/sync (+ /catalog/sync/status), /inventory/sync (+ /inventory/sync-status). Подробнее о лимитах и переносах — Интеграция с WB API.

Входящий rate limiting​

Собственный лимитер API (@nestjs/throttler, счётчики в Redis, общие для всех реплик). Ключ — IP клиента (берётся из X-Forwarded-For, число доверенных прокси — TRUST_PROXY_HOPS). Превышение — 429 TOO_MANY_REQUESTS.

ГруппаГде действуетПо умолчаниюПеременные
общийвсе маршруты300 запросов / 60 сTHROTTLE_DEFAULT_LIMIT, THROTTLE_DEFAULT_TTL_MS
authPOST /auth/register, POST /auth/login5 запросов / 15 минTHROTTLE_AUTH_LIMIT, THROTTLE_AUTH_TTL_MS
refreshPOST /auth/refresh60 запросов / 60 сTHROTTLE_REFRESH_LIMIT, THROTTLE_REFRESH_TTL_MS
LLMэндпоинты с вызовом ИИ: ai-assistant, ai-agent, content, autoreply (генерация, жалобы), legal/claims, knowledge-base30 запросов / 1 чTHROTTLE_LLM_LIMIT, THROTTLE_LLM_TTL_MS

В окружении test лимитер отключён. Помимо этого действуют квоты расхода LLM (ADR-0047).

Разделы API​

Тег — как в Swagger UI. Модуль — каталог backend/src/modules/<модуль>.

Префикс маршрутовТег SwaggerМодульМодуль ТЗНазначение
/authAuthauthОбщиерегистрация, вход, токены, профиль, роли
/usersUsersusersОбщиесписок пользователей (только admin)
/brand-accessBrand accessbrand-accessОбщиевыдача и отзыв доступа менеджеров к брендам (только admin)
/audit-logsAuditaudit-logОбщиежурнал аудита действий
/wb-accountsWB Accountswb-accountsОбщиекабинеты WB: CRUD, проверка ключа (/:id/check), состояние лимитов (/:id/quota)
/brandsBrandscatalogОбщиебренды, доступные пользователю
/catalogКаталогcatalogОбщиеобщий каталог товаров кабинета, синхронизация по требованию (ADR-0052)
/notificationsNotificationsnotificationsОбщие (используется бронировщиком 5.8)уведомления пользователя
/digitization, /digitization/reportsОцифровкаdigitization5.1импорт фидов и отчёта реализации, KPI, прибыль, продажи, отчёты
/accounting/reports, /accounting/transactionsБухгалтерияaccounting5.7реестр операций, сверка, CSV-выгрузка, ручные операции
/pricingЦеныpricing5.2товары с ценами, изменение цен и скидок (одиночно и пакетно), история
/repricer, /repricer/rules, /repricer/productsРепрайсерrepricer5.3правила, запуск движка, журнал прогонов, KPI
/autobidder, /autobidder/strategies, /autobidder/budgets, /autobidder/analytics, /autobidder/runАвтобиддер, autobidderautobidder5.4синхронизация кампаний, стратегии ставок, бюджеты, аналитика рекламы, журнал ставок
/autoreply, /autoreply/scenarios, /autoreply/templates, /autoreply/history, /autoreply/complaints, /autoreply/complaint-rulesАвтоответыautoreply5.5импорт отзывов и вопросов, генерация и отправка ответов, сценарии, шаблоны, жалобы
/rnp/analytics, /rnp/product-listsРНП — аналитика, РНПrnp5.6аналитика эффективности по артикулам и брендам, списки артикулов
/booking, /booking/tasks, /booking/warehousesBooking, bookingbooking5.8задания на бронирование слотов поставки, справочник складов, KPI
/ai-assistantИИ-помощникai-assistant5.9вопросы по работе с WB (RAG + LLM), анализ товара, история
/ai-agentИИ-агент (маркетолог)ai-agent5.10разбор задачи, план действий, подтверждения, выполнение и откат
/inventoryТоварный учётinventory5.11остатки, движения товара, синхронизация остатков
/legal/claimsLegallegal5.12досудебные претензии к WB, экспорт
/contentContentcontent5.13генерация текстов карточек и версии контента
/ab-tests, /ab-test-imagesA/B тестированиеab-test5.14A/B-тесты карточек, варианты фото, анализ и отчёт; /ab-test-images/:imageId — публичная отдача фото
/knowledge-baseБаза знанийknowledge-base5.15поиск и выдача материалов, загрузка материалов
/health, /version, /metrics, /Служебныеhealth, core/metrics—служебные проверки и метрики
/examples/items, /examples/queueПримерыexamples—демонстрационные эндпоинты конвенций (пагинация, очереди)

Модули llm, rag, prompt-templates и wb-api собственных HTTP-маршрутов не имеют — это внутренние слои.

Связанные страницы​