API «Оптимы»: обзор
REST API бэкенда «Оптимы» (NestJS). Полный контракт со всеми эндпоинтами, схемами запросов и ответов — в Swagger UI; эта страница описывает общие соглашения, которые действуют для всех модулей.
Базовые адреса
| Окружение | Базовый URL | Откуда собирается |
|---|---|---|
| Production | https://api.optimawb.ru | ветка main, образ ghcr.io/affiliatefabriq/optima-backend:latest |
| Stage | домен stage в репозитории не зафиксирован — смотрите карточку приложения optima-backend (stage) в Coolify | ветка develop, образ …:develop |
| Локально | http://localhost:3000 | npm 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 /health | readiness: проверка PostgreSQL и Redis | публичный |
GET /health/live | liveness процесса | публичный |
GET /version | версия приложения (из package.json) | публичный |
GET /api/docs | Swagger UI | Публичный во всех окружениях, без авторизации (вызовы API из него требуют JWT — кнопка «Authorize») |
GET /admin/queues | Bull Board — состояние очередей BullMQ | dev — открыт; production — HTTP Basic (QUEUE_BOARD_USER/QUEUE_BOARD_PASSWORD), иначе не монтируется |
GET /metrics | метрики Prometheus | dev — открыт; 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каждого прогона workflowbackend-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>
Токены.
| Токен | Формат | Срок жизни | Где задаётся |
|---|---|---|---|
| access | JWT (подпись JWT_ACCESS_SECRET), содержит id и роль пользователя | 15 минут | JWT_ACCESS_TTL |
| refresh | opaque-строка (256 бит), в БД хранится только sha-256 | 30 дней | JWT_REFRESH_TTL |
Cookie API не использует. Оба токена приходят в теле ответа и передаются в теле
запроса. Веб-приложение держит access-токен в памяти, а refresh-токен — в localStorage
браузера.
Поток.
| Шаг | Запрос | Тело | Ответ |
|---|---|---|---|
| Регистрация | POST /auth/register | email, password, displayName | профиль (роль seller) |
| Вход | POST /auth/login | email, password | accessToken, refreshToken |
| Обновление | POST /auth/refresh | refreshToken | новая пара; старый refresh отзывается (ротация) |
| Выход | POST /auth/logout | refreshToken | 204 |
| Выход везде | 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):
code | HTTP | Когда |
|---|---|---|
VALIDATION_FAILED | 400 | DTO не прошёл валидацию |
BAD_REQUEST | 400 | прочие некорректные запросы (например, период длиннее REPORT_MAX_PERIOD_DAYS) |
UNAUTHORIZED | 401 | нет или недействителен access-токен |
FORBIDDEN | 403 | недостаточно прав |
NOT_FOUND | 404 | ресурс не найден или вне области данных пользователя |
CONFLICT | 409 | конфликт состояния (например, email уже занят) |
TOO_MANY_REQUESTS | 429 | входящий лимит API, лимит Wildberries или ИИ-провайдера; с заголовком Retry-After, если срок известен |
WB_AUTH_FAILED | 502 | Wildberries отклонил API-ключ кабинета. Это не 401: сессия пользователя в порядке, проблема в ключе кабинета |
WB_UNAVAILABLE | 502 | Wildberries недоступен (5xx, сеть) |
LLM_UNAVAILABLE | 502 | ИИ-провайдер недоступен, отклонил доступ или не настроен |
SERVICE_UNAVAILABLE | 503 | временная недоступность; в том числе запрос к БД превысил statement_timeout (тогда Retry-After: 5 и совет сузить период) |
INTERNAL_ERROR | 500 | необработанная ошибка; стек пишется в лог, наружу не отдаётся |
Валидация
Глобальный 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):
POST …/syncилиPOST …/importотвечает 202 с теломjobIdиalreadyRunning. Если по кабинету уже идёт задача того же типа, новая не ставится: возвращаетсяjobIdсуществующей иalreadyRunning: true.- Клиент опрашивает статус (
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 |
| auth | POST /auth/register, POST /auth/login | 5 запросов / 15 мин | THROTTLE_AUTH_LIMIT, THROTTLE_AUTH_TTL_MS |
| refresh | POST /auth/refresh | 60 запросов / 60 с | THROTTLE_REFRESH_LIMIT, THROTTLE_REFRESH_TTL_MS |
| LLM | эндпоинты с вызовом ИИ: ai-assistant, ai-agent, content, autoreply (генерация, жалобы), legal/claims, knowledge-base | 30 запросов / 1 ч | THROTTLE_LLM_LIMIT, THROTTLE_LLM_TTL_MS |
В окружении test лимитер отключён. Помимо этого действуют квоты расхода LLM
(ADR-0047).
Разделы API
Тег — как в Swagger UI. Модуль — каталог backend/src/modules/<модуль>.
| Префикс маршрутов | Тег Swagger | Модуль | Модуль ТЗ | Назначение |
|---|---|---|---|---|
/auth | Auth | auth | Общие | регистрация, вход, токены, профиль, роли |
/users | Users | users | Общие | список пользователей (только admin) |
/brand-access | Brand access | brand-access | Общие | выдача и отзыв доступа менеджеров к брендам (только admin) |
/audit-logs | Audit | audit-log | Общие | журнал аудита действий |
/wb-accounts | WB Accounts | wb-accounts | Общие | кабинеты WB: CRUD, проверка ключа (/:id/check), состояние лимитов (/:id/quota) |
/brands | Brands | catalog | Общие | бренды, доступные пользователю |
/catalog | Каталог | catalog | Общие | общий каталог товаров кабинета, синхронизация по требованию (ADR-0052) |
/notifications | Notifications | notifications | Общие (используется бронировщиком 5.8) | уведомления пользователя |
/digitization, /digitization/reports | Оцифровка | digitization | 5.1 | импорт фидов и отчёта реализации, KPI, прибыль, продажи, отчёты |
/accounting/reports, /accounting/transactions | Бухгалтерия | accounting | 5.7 | реестр операций, сверка, CSV-выгрузка, ручные операции |
/pricing | Цены | pricing | 5.2 | товары с ценами, изменение цен и скидок (одиночно и пакетно), история |
/repricer, /repricer/rules, /repricer/products | Репрайсер | repricer | 5.3 | правила, запуск движка, журнал прогонов, KPI |
/autobidder, /autobidder/strategies, /autobidder/budgets, /autobidder/analytics, /autobidder/run | Автобиддер, autobidder | autobidder | 5.4 | синхронизация кампаний, стратегии ставок, бюджеты, аналитика рекламы, журнал ставок |
/autoreply, /autoreply/scenarios, /autoreply/templates, /autoreply/history, /autoreply/complaints, /autoreply/complaint-rules | Автоответы | autoreply | 5.5 | импорт отзывов и вопросов, генерация и отправка ответов, сценарии, шаблоны, жалобы |
/rnp/analytics, /rnp/product-lists | РНП — аналитика, РНП | rnp | 5.6 | аналитика эффективности по артикулам и брендам, списки артикулов |
/booking, /booking/tasks, /booking/warehouses | Booking, booking | booking | 5.8 | задания на бронирование слотов поставки, справочник складов, KPI |
/ai-assistant | ИИ-помощник | ai-assistant | 5.9 | вопросы по работе с WB (RAG + LLM), анализ товара, история |
/ai-agent | ИИ-агент (маркетолог) | ai-agent | 5.10 | разбор задачи, план действий, подтверждения, выполнение и откат |
/inventory | Товарный учёт | inventory | 5.11 | остатки, движения товара, синхронизация остатков |
/legal/claims | Legal | legal | 5.12 | досудебные претензии к WB, экспорт |
/content | Content | content | 5.13 | генерация текстов карточек и версии контента |
/ab-tests, /ab-test-images | A/B тестирование | ab-test | 5.14 | A/B-тесты карточек, варианты фото, анализ и отчёт; /ab-test-images/:imageId — публичная отдача фото |
/knowledge-base | База знаний | knowledge-base | 5.15 | поиск и выдача материалов, загрузка материалов |
/health, /version, /metrics, / | Служебные | health, core/metrics | — | служебные проверки и метрики |
/examples/items, /examples/queue | Примеры | examples | — | демонстрационные эндпоинты конвенций (пагинация, очереди) |
Модули llm, rag, prompt-templates и wb-api собственных HTTP-маршрутов не имеют —
это внутренние слои.