openapi: 3.0.3

info:
  title: Sarex Backend API
  version: "1.1.12"
  description: |
    REST API сервиса **sarex-backend** — монолитное Django-приложение (проект
    `config`, бизнес-логика в пакете `sarex`) на Django REST Framework. Отдаётся
    через uWSGI (`config.wsgi:application`) на порту `8000`.

    Документ описывает основную поверхность публичного API под префиксом `/api/`
    и внутреннего API под префиксом `/internal/`. Маршрутизация собирается в
    `config/urls.py` и включаемых `sarex/*/api/urls.py`. Многие ресурсы
    зарегистрированы через DRF-роутеры (`SimpleRouter`/`DefaultRouter`), поэтому
    поддерживают стандартный набор действий (list/create/retrieve/update/
    partial_update/destroy). Конкретные схемы запросов/ответов в коде не
    объявлены декларативно (используется `rest_framework.schemas.coreapi.AutoSchema`),
    поэтому тела здесь описаны обобщённо.

    ### Аутентификация
    Большинство эндпоинтов требуют аутентификации (DRF
    `DEFAULT_PERMISSION_CLASSES = [IsAuthenticated]`). Поддерживаются несколько
    механизмов (`DEFAULT_AUTHENTICATION_CLASSES`): Zitadel JWT, SimpleJWT
    (`Authorization: Bearer <jwt>`, алгоритм `RS512`), Basic, Session,
    RemoteUser. Токены выпускаются эндпоинтами `/api/token…`.

    ### Пагинация
    По умолчанию используется `LimitOffsetPagination` (`PAGE_SIZE = 1000`).
    Списочные ответы содержат `count`, `next`, `previous`, `results`.

    ### Замечание о полноте
    Перечислены основные маршруты. Часть включаемых подмодулей
    (`sarex/pg/api/*`, `sarex/mar/*`, `sarex/base/api/commons`) представлена
    группами; детальные под-пути см. в соответствующих `urls.py`.

servers:
  - url: https://api.sarex.io
    description: prod
  - url: https://stage-api.sarex.io
    description: stage
  - url: /
    description: contour (относительные пути)

security:
  - bearerAuth: []

tags:
  - name: auth
    description: Аутентификация и JWT-токены
  - name: base
    description: Уведомления, workflows, модули, health
  - name: core
    description: Пользователи, компании, цели, миссии, медиа
  - name: client
    description: Клиентский дашборд и self-сервис
  - name: analytics
    description: Дашборды, метрики, виджеты
  - name: map
    description: Кадастр и заметки на карте
  - name: pg
    description: Облака точек, экспорт, измерения
  - name: internal
    description: Внутрикластерные вызовы
  - name: system
    description: Метрики и служебные эндпоинты

paths:

  # ---------------------------------------------------------------------------
  # Auth / tokens
  # ---------------------------------------------------------------------------
  /api/login/:
    post:
      tags: [auth]
      summary: Вход пользователя (сессия)
      security: []
      responses:
        "200": { $ref: "#/components/responses/Ok" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/logout/:
    post:
      tags: [auth]
      summary: Выход пользователя
      responses:
        "200": { $ref: "#/components/responses/Ok" }
  /api/app-settings/:
    get:
      tags: [auth]
      summary: Настройки приложения
      responses:
        "200": { $ref: "#/components/responses/Ok" }
  /api/token/:
    post:
      tags: [auth]
      summary: Получить пару access/refresh токенов
      security: []
      requestBody: { $ref: "#/components/requestBodies/Generic" }
      responses:
        "200": { $ref: "#/components/responses/TokenPair" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/token/me:
    post:
      tags: [auth]
      summary: Токен для текущего пользователя
      responses:
        "200": { $ref: "#/components/responses/TokenPair" }
  /api/token/user/{pk}/:
    post:
      tags: [auth]
      summary: Токен для пользователя по id (из админки)
      parameters: [ { $ref: "#/components/parameters/PkPath" } ]
      responses:
        "200": { $ref: "#/components/responses/TokenPair" }
  /api/token/jwks:
    get:
      tags: [auth]
      summary: JWKS (набор публичных ключей)
      security: []
      responses:
        "200": { $ref: "#/components/responses/Ok" }
  /api/token/refresh/:
    post:
      tags: [auth]
      summary: Обновить access-токен (ротация)
      security: []
      requestBody: { $ref: "#/components/requestBodies/Generic" }
      responses:
        "200": { $ref: "#/components/responses/TokenPair" }
  /api/token/public/:
    get:
      tags: [auth]
      summary: Публичный ключ проверки JWT
      security: []
      responses:
        "200": { $ref: "#/components/responses/Ok" }
  /api/auth/obtain/:
    post:
      tags: [auth]
      summary: Получить refresh-токен
      security: []
      requestBody: { $ref: "#/components/requestBodies/Generic" }
      responses:
        "200": { $ref: "#/components/responses/TokenPair" }
  /api/auth/refresh/:
    post:
      tags: [auth]
      summary: Обновить access-токен
      security: []
      requestBody: { $ref: "#/components/requestBodies/Generic" }
      responses:
        "200": { $ref: "#/components/responses/TokenPair" }

  # ---------------------------------------------------------------------------
  # System
  # ---------------------------------------------------------------------------
  /metrics:
    get:
      tags: [system]
      summary: Метрики Prometheus
      security: []
      responses:
        "200": { $ref: "#/components/responses/Ok" }
  /api/health/:
    get:
      tags: [base]
      summary: Health check
      security: []
      responses:
        "200": { $ref: "#/components/responses/Ok" }

  # ---------------------------------------------------------------------------
  # Base
  # ---------------------------------------------------------------------------
  /api/modules/:
    get:
      tags: [base]
      summary: Список доступных модулей
      responses:
        "200": { $ref: "#/components/responses/Ok" }
  /api/update/notifications/:
    post:
      tags: [base]
      summary: Массовое обновление уведомлений
      responses:
        "200": { $ref: "#/components/responses/Ok" }
  /api/workflows/:
    get:
      tags: [base]
      summary: Список workflow
      responses:
        "200": { $ref: "#/components/responses/List" }
  /api/workflows/{id}/:
    parameters: [ { $ref: "#/components/parameters/IdPath" } ]
    get:
      tags: [base]
      summary: Workflow по id
      responses:
        "200": { $ref: "#/components/responses/Ok" }
  /api/notifications/:
    get:
      tags: [base]
      summary: Список уведомлений
      responses:
        "200": { $ref: "#/components/responses/List" }
  /api/notifications/{id}/:
    parameters: [ { $ref: "#/components/parameters/IdPath" } ]
    get:
      tags: [base]
      summary: Уведомление по id
      responses:
        "200": { $ref: "#/components/responses/Ok" }
  /api/usernotifications/:
    get:
      tags: [base]
      summary: Пользовательские уведомления
      responses:
        "200": { $ref: "#/components/responses/List" }
  /api/commons/cs/:
    get:
      tags: [base]
      summary: Справочник систем координат
      responses:
        "200": { $ref: "#/components/responses/List" }

  # ---------------------------------------------------------------------------
  # Core — ресурсы DRF-роутера (CRUD)
  # ---------------------------------------------------------------------------
  /api/core/users/:
    get:
      tags: [core]
      summary: Список пользователей
      responses: { "200": { $ref: "#/components/responses/List" } }
    post:
      tags: [core]
      summary: Создать пользователя
      requestBody: { $ref: "#/components/requestBodies/Generic" }
      responses: { "201": { $ref: "#/components/responses/Ok" } }
  /api/core/users/{id}/:
    parameters: [ { $ref: "#/components/parameters/IdPath" } ]
    get: { tags: [core], summary: Пользователь по id, responses: { "200": { $ref: "#/components/responses/Ok" } } }
    put: { tags: [core], summary: Обновить пользователя, requestBody: { $ref: "#/components/requestBodies/Generic" }, responses: { "200": { $ref: "#/components/responses/Ok" } } }
    patch: { tags: [core], summary: Частично обновить пользователя, requestBody: { $ref: "#/components/requestBodies/Generic" }, responses: { "200": { $ref: "#/components/responses/Ok" } } }
    delete: { tags: [core], summary: Удалить пользователя, responses: { "204": { description: No Content } } }
  /api/core/users/introspect:
    get:
      tags: [core]
      summary: Интроспекция текущего пользователя
      responses: { "200": { $ref: "#/components/responses/Ok" } }
  /api/core/users/{pk}/introspect:
    parameters: [ { $ref: "#/components/parameters/PkPath" } ]
    get:
      tags: [core]
      summary: Интроспекция пользователя (админ)
      responses: { "200": { $ref: "#/components/responses/Ok" } }
  /api/core/users/bulk/notifications/:
    post:
      tags: [core]
      summary: Массовое обновление уведомлений пользователей
      responses: { "200": { $ref: "#/components/responses/Ok" } }
  /api/core/users_by_sa/:
    get:
      tags: [core]
      summary: Пользователи по сервисному аккаунту
      responses: { "200": { $ref: "#/components/responses/List" } }
  /api/core/v2/users/:
    get:
      tags: [core]
      summary: Упрощённый список пользователей (v2)
      responses: { "200": { $ref: "#/components/responses/List" } }
  /api/core/v3/users/:
    get:
      tags: [core]
      summary: Оптимизированный список пользователей (v3)
      responses: { "200": { $ref: "#/components/responses/List" } }
  /api/core/companies/:
    get: { tags: [core], summary: Список компаний, responses: { "200": { $ref: "#/components/responses/List" } } }
    post: { tags: [core], summary: Создать компанию, requestBody: { $ref: "#/components/requestBodies/Generic" }, responses: { "201": { $ref: "#/components/responses/Ok" } } }
  /api/core/companies/{id}/:
    parameters: [ { $ref: "#/components/parameters/IdPath" } ]
    get: { tags: [core], summary: Компания по id, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/surfaces/:
    get: { tags: [core], summary: Список поверхностей, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/mesh/:
    get: { tags: [core], summary: Список mesh, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/multiple-surface/:
    get: { tags: [core], summary: Множественные поверхности, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/state/:
    get: { tags: [core], summary: Состояние приложения, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/polygons/:
    get: { tags: [core], summary: Полигоны, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/media-notes/:
    get: { tags: [core], summary: Медиа-заметки, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/media-notes-comments/:
    get: { tags: [core], summary: Комментарии медиа-заметок, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/media-notes-attachments/:
    get: { tags: [core], summary: Вложения медиа-заметок, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/media-folders/:
    get: { tags: [core], summary: Папки медиа, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/target-links/:
    get: { tags: [core], summary: Ссылки на цели, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/materials/:
    get: { tags: [core], summary: Материалы, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/pointclouds/:
    get: { tags: [core], summary: Облака точек, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/orthophotos/:
    get: { tags: [core], summary: Ортофотопланы, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/videos/:
    get: { tags: [core], summary: Видео, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/photos/:
    get: { tags: [core], summary: Фото, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/panoramas/:
    get: { tags: [core], summary: Панорамы, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/color-presets/:
    get: { tags: [core], summary: Цветовые пресеты компании, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/missions/:
    get: { tags: [core], summary: Список миссий, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/missions/import/:
    get: { tags: [core], summary: Задачи импорта миссий, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/missions/import/create/:
    post: { tags: [core], summary: Создать задачу импорта миссии, responses: { "201": { $ref: "#/components/responses/Ok" } } }
  /api/core/targets/:
    get: { tags: [core], summary: Список целей, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/targets/v2/:
    get: { tags: [core], summary: Список целей (v2), responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/targets/v3/:
    get: { tags: [core], summary: Список целей (v3), responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/targets/tree/:
    get: { tags: [core], summary: Дерево целей, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/targets/pdm/:
    get: { tags: [core], summary: Цели PDM, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/targets/{id}/missions/:
    parameters: [ { $ref: "#/components/parameters/IdPath" } ]
    get: { tags: [core], summary: Миссии цели, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/export/:
    get: { tags: [core], summary: Экспорт данных, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/export/pointcloud/:
    post: { tags: [core], summary: Экспорт региона из облака точек, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/contour/export/:
    post: { tags: [core], summary: Экспорт контура, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/contour/exports/:
    get: { tags: [core], summary: Список экспортов контура, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/uploads/streaming/:
    post: { tags: [core], summary: Потоковая загрузка (создание), responses: { "201": { $ref: "#/components/responses/Ok" } } }
  /api/core/uploads/streaming/{pk}/:
    parameters: [ { $ref: "#/components/parameters/PkStrPath" } ]
    put: { tags: [core], summary: Догрузка чанка, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/storage-cleanup/:
    post: { tags: [core], summary: Очистка хранилища, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/celery/stats:
    get: { tags: [core], summary: Статистика Celery, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/celery/active:
    get: { tags: [core], summary: Активные задачи Celery, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/celery/scheduled:
    get: { tags: [core], summary: Запланированные задачи Celery, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/celery/{uuid}/details/:
    parameters: [ { name: uuid, in: path, required: true, schema: { type: string } } ]
    get: { tags: [core], summary: Детали задачи Celery, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/admin/users/:
    get: { tags: [core], summary: Админ — пользователи, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/admin/groups/:
    get: { tags: [core], summary: Админ — группы, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/admin/companies/:
    get: { tags: [core], summary: Админ — компании, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/admin/positions/:
    get: { tags: [core], summary: Админ — должности, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/admin/departments/:
    get: { tags: [core], summary: Админ — подразделения, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/admin/contractors/:
    get: { tags: [core], summary: Админ — подрядчики, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/admin/permissions/:
    get: { tags: [core], summary: Админ — права, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/service_accounts/:
    get: { tags: [core], summary: Сервисные аккаунты, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/service-accounts/personalized/:
    get: { tags: [core], summary: Персонализированный сервисный аккаунт, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/service-accounts/user/{user_id}/:
    parameters: [ { name: user_id, in: path, required: true, schema: { type: integer } } ]
    get: { tags: [core], summary: Сервисный аккаунт пользователя (v2), responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/core/c2s-comparisons/:
    post: { tags: [core], summary: Создать c2s-сравнение, responses: { "201": { $ref: "#/components/responses/Ok" } } }
  /api/core/permissions/:
    get: { tags: [core], summary: Права (только чтение), responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/units/:
    get: { tags: [core], summary: Единицы измерения, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/upload/media-file/:
    post: { tags: [core], summary: Загрузка медиа-файла, responses: { "201": { $ref: "#/components/responses/Ok" } } }
  /api/core/mrpa/list/:
    get: { tags: [core], summary: Список MRPA, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/core/mrpa/:
    post: { tags: [core], summary: Создать MRPA, responses: { "201": { $ref: "#/components/responses/Ok" } } }
  /api/core/mrpa/{pk}/:
    parameters: [ { name: pk, in: path, required: true, schema: { type: string, format: uuid } } ]
    get: { tags: [core], summary: MRPA по id, responses: { "200": { $ref: "#/components/responses/Ok" } } }

  # ---------------------------------------------------------------------------
  # Client
  # ---------------------------------------------------------------------------
  /api/client/dashboard/targets/:
    get: { tags: [client], summary: Дашборд — цели, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/client/dashboard/missions/:
    get: { tags: [client], summary: Дашборд — миссии, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/client/dashboard/webcams/:
    get: { tags: [client], summary: Дашборд — веб-камеры, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/client/settings/:
    get: { tags: [client], summary: Настройки пользователя, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/client/settings/user/{user_id}/:
    parameters: [ { name: user_id, in: path, required: true, schema: { type: integer } } ]
    get: { tags: [client], summary: Настройки пользователя по id, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/client/self/:
    get: { tags: [client], summary: Информация о себе, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/client/self/set_password/:
    post: { tags: [client], summary: Смена пароля, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/client/uploads/:
    get: { tags: [client], summary: Загрузки клиента, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/client/folders/:
    get: { tags: [client], summary: Папки загрузок клиента, responses: { "200": { $ref: "#/components/responses/List" } } }

  # ---------------------------------------------------------------------------
  # Analytics
  # ---------------------------------------------------------------------------
  /api/analytics/dashboards/:
    get: { tags: [analytics], summary: Дашборды, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/analytics/widgets/:
    get: { tags: [analytics], summary: Виджеты, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/analytics/metrics/:
    get: { tags: [analytics], summary: Метрики, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/analytics/expressions/:
    get: { tags: [analytics], summary: Выражения, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/analytics/folders/:
    get: { tags: [analytics], summary: Папки метрик, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/analytics/filters/:
    get: { tags: [analytics], summary: Фильтры, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/analytics/groups/:
    get: { tags: [analytics], summary: Группы, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/analytics/values/:
    get: { tags: [analytics], summary: Значения метрик, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/analytics/attributes/:
    get: { tags: [analytics], summary: Атрибуты, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/analytics/attachments/:
    get: { tags: [analytics], summary: Вложения дашбордов, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/analytics/reviews-service-feed/:
    post: { tags: [analytics], summary: Приём событий из сервиса reviews, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/analytics/remarks-service-feed/:
    post: { tags: [analytics], summary: Приём событий из сервиса remarks, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/analytics/tracking-service-feed/:
    post: { tags: [analytics], summary: Приём событий трекинга, responses: { "200": { $ref: "#/components/responses/Ok" } } }

  # ---------------------------------------------------------------------------
  # Map
  # ---------------------------------------------------------------------------
  /api/map/cadastre/point/:
    get: { tags: [map], summary: Данные кадастра по точке, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/map/cadastre/export/:
    post: { tags: [map], summary: Экспорт кадастровых данных, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/map/cadastre/wikimapia/redirect/:
    get: { tags: [map], summary: Редирект Wikimapia, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/map/notes/:
    get: { tags: [map], summary: Заметки на ортофото, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/map/notes/folders/:
    get: { tags: [map], summary: Папки заметок на ортофото, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/ds/telecom/cadastre/:
    get: { tags: [map], summary: Изображение кадастра (telecom), responses: { "200": { $ref: "#/components/responses/Ok" } } }

  # ---------------------------------------------------------------------------
  # PG (облака точек / экспорт / измерения)
  # ---------------------------------------------------------------------------
  /api/pg/pointclouds/:
    get: { tags: [pg], summary: Облака точек, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/pg/pointclouds/{pointcloud}/measurements/:
    parameters: [ { name: pointcloud, in: path, required: true, schema: { type: string } } ]
    get: { tags: [pg], summary: Измерения облака точек, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/pg/volume-dynamic/:
    get: { tags: [pg], summary: Динамика объёмов, responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/pg/orthomosaicexport/:
    get: { tags: [pg], summary: Экспорт ортомозаики (кастомный), responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/pg/exports/pointcloud/:
    get: { tags: [pg], summary: Экспорты облаков точек, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/pg/exports/orthomosaic/:
    get: { tags: [pg], summary: Экспорты ортомозаики, responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/pg/measurements/:
    get: { tags: [pg], summary: Измерения (подмодуль), responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/pg/attachments/:
    get: { tags: [pg], summary: Вложения (подмодуль), responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/pg/compare/:
    get: { tags: [pg], summary: Сравнение (подмодуль), responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/pg/export/:
    get: { tags: [pg], summary: Экспорт (подмодуль), responses: { "200": { $ref: "#/components/responses/Ok" } } }
  /api/pg/pdf-overlays/:
    get: { tags: [pg], summary: PDF-оверлеи (подмодуль), responses: { "200": { $ref: "#/components/responses/List" } } }
  /api/pg/projects/:
    get: { tags: [pg], summary: Проекты Metashape (при SERVER_USE_METASHAPE), responses: { "200": { $ref: "#/components/responses/List" } } }

  # ---------------------------------------------------------------------------
  # Internal
  # ---------------------------------------------------------------------------
  /internal/client/settings/{pk}/:
    parameters: [ { $ref: "#/components/parameters/PkPath" } ]
    get:
      tags: [internal]
      summary: Внутренние настройки клиента
      responses: { "200": { $ref: "#/components/responses/Ok" } }
  /internal/client/token/{pk}/:
    parameters: [ { $ref: "#/components/parameters/PkPath" } ]
    get:
      tags: [internal]
      summary: Внутренний токен клиента
      responses: { "200": { $ref: "#/components/responses/TokenPair" } }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  parameters:
    IdPath:
      name: id
      in: path
      required: true
      schema: { type: integer }
    PkPath:
      name: pk
      in: path
      required: true
      schema: { type: integer }
    PkStrPath:
      name: pk
      in: path
      required: true
      schema: { type: string }

  requestBodies:
    Generic:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: true

  responses:
    Ok:
      description: Успешный ответ
      content:
        application/json:
          schema:
            type: object
            additionalProperties: true
    List:
      description: Списочный ответ с пагинацией (LimitOffset)
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/PaginatedList"
    TokenPair:
      description: Пара токенов
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/TokenPair"
    Unauthorized:
      description: Не аутентифицирован
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    PaginatedList:
      type: object
      properties:
        count: { type: integer }
        next: { type: string, nullable: true, format: uri }
        previous: { type: string, nullable: true, format: uri }
        results:
          type: array
          items:
            type: object
            additionalProperties: true
    TokenPair:
      type: object
      properties:
        access: { type: string }
        refresh: { type: string }
    Error:
      type: object
      properties:
        detail: { type: string }
