{
  "openapi": "3.1.0",
  "info": {
    "title": "Studio Assets API",
    "version": "0.20.0",
    "description": "Публичное описание API командной библиотеки Studio Assets. Документация открыта без входа; операции с данными требуют пользователя и его прав. Единая библиотека: engine=generic|unity|unreal|godot определяет целевую среду, type — содержимое, scope — права доступа. Все примеры содержат вымышленные данные. JSON-запросы передаются с Content-Type: application/json и Content-Length; multipart и Transfer-Encoding не поддерживаются. CORS для операций API не включён: внешние приложения обращаются к серверу напрямую, браузерные клиенты — с того же origin. Публичный контракт /openapi.json разрешает cross-origin чтение без credentials."
  },
  "servers": [
    {
      "url": "/",
      "description": "Текущий сервер Studio Assets"
    }
  ],
  "tags": [
    {
      "name": "System",
      "description": "Доступность сервиса и возможности обработки файлов.",
      "x-displayName": "Сервис"
    },
    {
      "name": "Assets",
      "description": "Поиск, создание и изменение карточек.",
      "x-displayName": "Ассеты"
    },
    {
      "name": "Versions",
      "description": "Неизменяемые ревизии, текущая версия и скачивание.",
      "x-displayName": "Версии и файлы"
    },
    {
      "name": "Previews",
      "description": "Изображения, аудио, обложки и генерация.",
      "x-displayName": "Превью"
    },
    {
      "name": "Preparation",
      "description": "Профили конвертации, очередь и результаты.",
      "x-displayName": "Подготовка для Unity"
    },
    {
      "name": "Publications",
      "description": "Явная публикация файлов из Unity Editor.",
      "x-displayName": "Публикация из проекта"
    },
    {
      "name": "Auth",
      "description": "Вход, персональные API-токены и завершение сессии.",
      "x-displayName": "Сессии и токены"
    },
    {
      "name": "Projects",
      "description": "Проекты, доступные текущему пользователю.",
      "x-displayName": "Доступные проекты"
    },
    {
      "name": "Admin",
      "description": "Пользователи, проекты и членство; только администратор.",
      "x-displayName": "Администрирование"
    },
    {
      "name": "Content",
      "description": "Атомы, использования и явные зависимости пакетов.",
      "x-displayName": "Атомы и связи"
    },
    {
      "name": "Collections",
      "x-displayName": "Коллекции",
      "description": "Подборки пакетов и отдельных атомов для команды или проекта."
    },
    {
      "name": "ReadyCatalog",
      "description": "Таксономия и готовые версии, их точки входа и права доступа.",
      "x-displayName": "Готовые ассеты"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "sessionCookie": []
    }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "System"
        ],
        "summary": "Проверить состояние сервиса",
        "description": "Не требует авторизации. max_upload_bytes — текущий лимит декодированного файла.",
        "x-access": "Публично",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "ok"
                    },
                    "max_upload_bytes": {
                      "type": "integer",
                      "minimum": 1
                    }
                  },
                  "required": [
                    "status",
                    "max_upload_bytes"
                  ]
                },
                "example": {
                  "status": "ok",
                  "max_upload_bytes": 67108864
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "security": []
      }
    },
    "/api/formats": {
      "get": {
        "operationId": "getSupportedFormats",
        "tags": [
          "System"
        ],
        "summary": "Узнать поддерживаемые форматы",
        "description": "Матрица форматов и ограничений конвертера. Наличие формата в automatic не гарантирует, что любой файл обработается: профиль и содержимое могут потребовать ручной работы.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "formats": {
                      "$ref": "#/components/schemas/Formats"
                    }
                  },
                  "required": [
                    "formats"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      }
    },
    "/api/assets": {
      "get": {
        "operationId": "listAssets",
        "tags": [
          "Assets"
        ],
        "summary": "Найти ассеты",
        "description": "Только доступные текущему пользователю карточки. Поиск без учёта регистра. q ищет подстроку; остальные фильтры сравниваются точно. Результат отсортирован по времени создания от новых к старым. HTTP-пагинации нет. versions отсутствует в списке; полная история — в отдельной карточке.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "assets": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Asset"
                      }
                    }
                  },
                  "required": [
                    "assets"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Подстрока в названии, описании, проекте, тегах, теме, стиле или названии/типе источника.",
            "schema": {
              "type": "string"
            },
            "example": "oak"
          },
          {
            "name": "engine",
            "in": "query",
            "description": "Целевая среда; отсутствие фильтра возвращает единый каталог.",
            "schema": {
              "type": "string",
              "enum": [
                "generic",
                "unity",
                "unreal",
                "godot"
              ],
              "description": "Целевая среда ассета и предпочтительный формат экспорта. Не определяет права доступа и не конвертирует существующие файлы."
            }
          },
          {
            "name": "space",
            "in": "query",
            "description": "Совместимость с v0.6: unity для engine=unity, general для остальных. Используйте engine.",
            "schema": {
              "type": "string",
              "enum": [
                "general",
                "unity"
              ],
              "deprecated": true,
              "description": "Совместимость с v0.6: unity для engine=unity, general для остальных. Используйте engine."
            },
            "deprecated": true
          },
          {
            "name": "type",
            "in": "query",
            "description": "Тип ассета.",
            "schema": {
              "type": "string",
              "enum": [
                "model",
                "prefab",
                "material",
                "texture",
                "audio",
                "animation",
                "shader",
                "script",
                "other"
              ]
            }
          },
          {
            "name": "scope",
            "in": "query",
            "description": "Область доступа.",
            "schema": {
              "type": "string",
              "description": "generic — все активные пользователи команды; project — по членству в проекте.",
              "enum": [
                "generic",
                "project"
              ]
            }
          },
          {
            "name": "project",
            "in": "query",
            "description": "Имя проекта, не ID.",
            "schema": {
              "type": "string"
            },
            "example": "Racer"
          },
          {
            "name": "tag",
            "in": "query",
            "description": "Один точный тег.",
            "schema": {
              "type": "string"
            },
            "example": "nature"
          },
          {
            "name": "theme",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Точное совпадение без учёта регистра."
          },
          {
            "name": "style",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Точное совпадение без учёта регистра."
          },
          {
            "name": "collection",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "Показать элементы выбранной коллекции."
          },
          {
            "name": "candidates",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Вместе с collection включает все подходящие ресурсы и поле in_collection."
          }
        ]
      },
      "post": {
        "operationId": "createAsset",
        "tags": [
          "Assets"
        ],
        "summary": "Создать карточку",
        "description": "Создаёт карточку единой библиотеки без файла. engine по умолчанию generic; type описывает содержимое. scope обязателен, project требуется для проектного доступа. Права доступа неизменяемы. engine можно изменить через PATCH; это не преобразует уже загруженные файлы. description — семантическое описание; theme/style/source хранятся отдельно. В веб-форме нового ассета они обязательны, API допускает отсутствие для совместимости со старыми клиентами.",
        "x-access": "Редактор с правом записи или администратор",
        "responses": {
          "201": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAsset"
              },
              "example": {
                "name": "Oak",
                "type": "model",
                "scope": "generic",
                "tags": [
                  "nature"
                ],
                "description": "Исходная модель дерева",
                "engine": "generic",
                "theme": "Природа",
                "style": "Low poly",
                "source": {
                  "provider": "own",
                  "name": "Studio",
                  "url": ""
                }
              }
            }
          }
        }
      }
    },
    "/api/assets/{asset_id}": {
      "get": {
        "operationId": "getAsset",
        "tags": [
          "Assets"
        ],
        "summary": "Получить карточку и историю",
        "description": "Возвращает полную историю versions и независимые current_versions.source/unity. latest_version — последняя загруженная метка, а не текущая версия.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ]
      },
      "patch": {
        "operationId": "updateAsset",
        "tags": [
          "Assets"
        ],
        "summary": "Изменить метаданные",
        "description": "Изменяет name, description, tags, type или engine. Байты версий, история, происхождение и права доступа сохраняются. Смена engine — изменение классификации, а не конвертация файлов. Также поддерживаются theme, style и source; переданный source заменяет весь объект источника.",
        "x-access": "Редактор с правом записи или администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EditAsset"
              },
              "example": {
                "description": "Модель для лесных сцен",
                "tags": [
                  "nature",
                  "forest"
                ]
              }
            }
          }
        }
      }
    },
    "/api/assets/{asset_id}/versions": {
      "post": {
        "operationId": "uploadVersion",
        "tags": [
          "Versions"
        ],
        "summary": "Загрузить новую версию",
        "description": "Создаёт неизменяемый файл. Сочетание карточка + version + kind уникально. kind=unity принимает только .unitypackage; .studioasset.zip создаёт конвертер. По умолчанию файл попадает только в историю. При publish_current=true загрузка и назначение выполняются атомарно. expected_current_id должен совпасть с текущим указателем этого kind; null означает отсутствие текущей версии. При конфликте сервер возвращает 409. Перечитайте карточку перед новым осознанным действием; не повторяйте переключение автоматически. package принимает .zip, .pck, .pak, .utoc и сохраняет байты без изменений. Нативная конвертация Unreal/Godot не выполняется.",
        "x-access": "Редактор с правом записи или администратор",
        "responses": {
          "201": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "$ref": "#/components/schemas/Version"
                    }
                  },
                  "required": [
                    "version"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadVersion"
              },
              "example": {
                "version": "1.0.0",
                "kind": "source",
                "filename": "sample.txt",
                "content_base64": "SGVsbG8gU3R1ZGlvCg==",
                "notes": "Первая ревизия",
                "publish_current": false
              }
            }
          }
        }
      }
    },
    "/api/assets/{asset_id}/current": {
      "post": {
        "operationId": "setCurrentVersion",
        "tags": [
          "Versions"
        ],
        "summary": "Назначить текущую версию или откатить",
        "description": "Переключает указатель source либо unity на уже существующую версию этой карточки и вида. Все файлы и история сохраняются. Импортированные Unity-проекты автоматически не изменяются. expected_current_id должен совпасть с текущим указателем этого kind; null означает отсутствие текущей версии. При конфликте сервер возвращает 409. Перечитайте карточку перед новым осознанным действием; не повторяйте переключение автоматически.",
        "x-access": "Редактор с правом записи или администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CurrentVersion"
              },
              "example": {
                "kind": "source",
                "version_id": "11111111111111111111111111111111",
                "expected_current_id": null
              }
            }
          }
        }
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/download": {
      "get": {
        "operationId": "downloadVersion",
        "tags": [
          "Versions"
        ],
        "summary": "Скачать оригинал версии",
        "description": "Возвращает исходные байты конкретной версии как вложение. Укажите UUID версии, а не метку. SHA-256 можно получить из карточки. Range для скачивания оригинала не реализован. Для публикации unity_recipe этот маршрут отдаёт project-resources.zip. Нативный пакет доступен через /representations/unitypackage.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Оригинальный файл.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "Content-Disposition": {
                "description": "attachment с безопасно закодированным исходным именем.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ]
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/media": {
      "get": {
        "operationId": "getVersionMedia",
        "tags": [
          "Previews"
        ],
        "summary": "Получить изображение или аудио",
        "description": "Только безопасные поддерживаемые изображения и аудио; для производного ассета может отдавать подготовленное медиа. Неподдерживаемый inline-формат возвращает 415; оригинал доступен через download.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Полное медиа.",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "416": {
            "description": "Некорректный Range. Пустое тело ответа.",
            "headers": {
              "Content-Range": {
                "schema": {
                  "type": "string"
                },
                "description": "bytes */total"
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "206": {
            "description": "Часть медиа по Range.",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "Content-Range": {
                "schema": {
                  "type": "string"
                },
                "description": "Диапазон bytes start-end/total."
              }
            }
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "Range",
            "in": "header",
            "description": "Необязательный один диапазон bytes=start-end. Поддерживаются открытые и суффиксные диапазоны; несколько диапазонов не принимаются.",
            "schema": {
              "type": "string"
            },
            "example": "bytes=0-1023"
          }
        ]
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/preview": {
      "get": {
        "operationId": "getVersionPreview",
        "tags": [
          "Previews"
        ],
        "summary": "Получить PNG-обложку",
        "description": "Только сохранённая обложка с состоянием ready. При отсутствии возвращает 404; для исходной картинки используйте media, если этот адрес указан в preview. Для растрового оригинала без пользовательской обложки автоматически возвращается нормализованный первый кадр PNG до 768 px. GIF/APNG/WebP не анимируются в каталоге; оригинальное медиа открывается отдельно.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Полное медиа.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "416": {
            "description": "Некорректный Range. Пустое тело ответа.",
            "headers": {
              "Content-Range": {
                "schema": {
                  "type": "string"
                },
                "description": "bytes */total"
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "206": {
            "description": "Часть медиа по Range.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "Content-Range": {
                "schema": {
                  "type": "string"
                },
                "description": "Диапазон bytes start-end/total."
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/429"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "Range",
            "in": "header",
            "description": "Необязательный один диапазон bytes=start-end. Поддерживаются открытые и суффиксные диапазоны; несколько диапазонов не принимаются.",
            "schema": {
              "type": "string"
            },
            "example": "bytes=0-1023"
          }
        ]
      },
      "post": {
        "operationId": "uploadVersionPreview",
        "tags": [
          "Previews"
        ],
        "summary": "Загрузить обложку",
        "description": "Принимает PNG, JPEG или WebP до 8 МиБ; сохраняет нормализованную PNG-обложку этой версии. Оригинал и текущий указатель не меняются. filename необязателен и не используется как путь.",
        "x-access": "Редактор с правом записи или администратор",
        "responses": {
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "201": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "preview": {
                      "$ref": "#/components/schemas/Preview"
                    }
                  },
                  "required": [
                    "preview"
                  ]
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CoverUpload"
              },
              "example": {
                "filename": "cover.png",
                "content_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aU1sAAAAASUVORK5CYII="
              }
            }
          }
        }
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/preview/generate": {
      "post": {
        "operationId": "generateVersionPreview",
        "tags": [
          "Previews"
        ],
        "summary": "Поставить генерацию превью в очередь",
        "description": "202 означает принятие задания, а не готовое изображение. Проверяйте preview.state в карточке. Повтор при pending даёт 409; переполненная очередь — 429. После перезапуска незавершённое превью помечается failed.",
        "x-access": "Редактор с правом записи или администратор",
        "responses": {
          "202": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "preview": {
                      "$ref": "#/components/schemas/Preview"
                    }
                  },
                  "required": [
                    "preview"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              },
              "example": {}
            }
          }
        }
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/prepare-unity": {
      "post": {
        "operationId": "prepareForUnity",
        "tags": [
          "Preparation"
        ],
        "summary": "Подготовить версию для Unity",
        "description": "Только версия kind=source, независимо от целевой среды. Создаёт отдельную Unity-карточку с теми же правами доступа; исходник сохраняется. Требуется явный profile. Результат prepared не означает проверку в Unity Editor. Повтор создаёт новое задание. Очередь: 1 worker, максимум 4 ожидающих/выполняющихся задания.",
        "x-access": "Редактор с правом записи или администратор",
        "responses": {
          "202": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PrepareRequest"
              },
              "example": {
                "profile": {
                  "source_units": "m",
                  "source_up": "Y",
                  "source_forward": "Z",
                  "geometry_only": true
                },
                "target_name": "Oak · Unity"
              }
            }
          }
        }
      }
    },
    "/api/jobs/{job_id}": {
      "get": {
        "operationId": "getPreparationJob",
        "tags": [
          "Preparation"
        ],
        "summary": "Получить состояние подготовки",
        "description": "Доступ определяется исходным ассетом. Терминальные состояния: succeeded, failed, manual_required. При succeeded target_asset_id указывает на новую карточку. При перезапуске незавершённое задание становится failed.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "ID задания подготовки.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ]
      }
    },
    "/api/assets/{asset_id}/jobs": {
      "get": {
        "operationId": "listPreparationJobs",
        "tags": [
          "Preparation"
        ],
        "summary": "Получить задания ассета",
        "description": "Последние 100 заданий, сначала новые. Только для пользователей с доступом к исходной карточке.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jobs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Job"
                      }
                    }
                  },
                  "required": [
                    "jobs"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ]
      }
    },
    "/api/project-publications": {
      "post": {
        "operationId": "publishProjectAssets",
        "tags": [
          "Publications"
        ],
        "summary": "Опубликовать экспорт проекта",
        "description": "Создаёт карточку или новую неизменяемую версию. Для новой карточки передайте asset, для существующей — target_asset_id. Generic принимает original_file или project_source_zip с именем project-source.zip. Unity принимает готовый unitypackage или unity_recipe. При unity_recipe передайте project-resources.zip в content_base64 и опишите его состав в unity_recipe. Сервер сверяет пути, GUID и SHA-256 исходных файлов и .meta. /download отдаёт исходный ZIP, /representations/unitypackage собирает нативный архив по запросу. Готовность в движке проверяется отдельно. При повторе того же request_id и payload ответ 200 (replayed=true); другой payload возвращает 409. Повтор проверяет текущие права. При publish_current=true передайте expected_current_id (null для первого назначения). Конфликт возвращает 409.",
        "x-access": "Редактор с правом записи или администратор",
        "responses": {
          "201": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicationResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "200": {
            "description": "Идемпотентный повтор; replayed=true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicationResult"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicationRequest"
              },
              "example": {
                "request_id": "publish-sample-0001",
                "asset": {
                  "name": "Sample from project",
                  "type": "other",
                  "scope": "generic",
                  "tags": [
                    "sample"
                  ]
                },
                "version": "1.0.0",
                "filename": "sample.txt",
                "content_base64": "SGVsbG8gU3R1ZGlvCg==",
                "publish_current": false,
                "provenance": {
                  "project_name": "Racer",
                  "unity_version": "6000.0",
                  "export_kind": "original_file",
                  "selected_files": [
                    "Assets/sample.txt"
                  ],
                  "selected_count": 1,
                  "dependency_count": 0
                },
                "client_readiness": "not_tested",
                "engine": "generic"
              }
            }
          }
        }
      }
    },
    "/api/auth/login": {
      "post": {
        "operationId": "login",
        "tags": [
          "Auth"
        ],
        "summary": "Войти и создать cookie-сессию",
        "description": "Вход существующего пользователя. Возвращает HttpOnly cookie studio_session и csrf_token. Сессия действует 8 часов. После 10 неуспешных попыток с одного IP за 5 минут — 429. Для интеграций можно использовать ранее выпущенный Bearer-токен без этого запроса.",
        "x-access": "Публично",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Auth"
                }
              }
            },
            "headers": {
              "Set-Cookie": {
                "schema": {
                  "type": "string"
                },
                "description": "studio_session; HttpOnly; SameSite=Strict; Secure при HTTPS."
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Login"
              },
              "example": {
                "username": "your-username",
                "password": "your-password"
              }
            }
          }
        }
      }
    },
    "/api/auth/me": {
      "get": {
        "operationId": "getCurrentUser",
        "tags": [
          "Auth"
        ],
        "summary": "Получить текущего пользователя",
        "description": "Возвращает роль и csrf_token для cookie-сессии. При Bearer csrf_token равен null.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Auth"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      }
    },
    "/api/auth/logout": {
      "post": {
        "operationId": "logout",
        "tags": [
          "Auth"
        ],
        "summary": "Завершить текущую cookie-сессию",
        "description": "Удаляет только текущую браузерную сессию и очищает cookie. Не отзывает остальные сессии или API-токены. Для отзыва токена используйте DELETE /api/auth/tokens/{token_id}. Тело не требуется.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ]
      }
    },
    "/api/auth/tokens": {
      "get": {
        "operationId": "listTokens",
        "tags": [
          "Auth"
        ],
        "summary": "Получить свои API-токены",
        "description": "Только ID, названия, даты и срок действия. Секреты повторно не возвращаются.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tokens": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Token"
                      }
                    }
                  },
                  "required": [
                    "tokens"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      },
      "post": {
        "operationId": "createToken",
        "tags": [
          "Auth"
        ],
        "summary": "Создать персональный API-токен",
        "description": "Секрет token возвращается только один раз. Срок действия — 90 дней. Токен использует текущие права владельца; отдельные scopes не реализованы.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "201": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
                      "pattern": "^[a-f0-9]{32}$"
                    },
                    "name": {
                      "type": "string"
                    },
                    "token": {
                      "type": "string",
                      "description": "Секрет API-токена. Сохраните при получении."
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "token"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Unity workstation"
              }
            }
          }
        }
      }
    },
    "/api/auth/tokens/{token_id}": {
      "delete": {
        "operationId": "revokeToken",
        "tags": [
          "Auth"
        ],
        "summary": "Отозвать свой API-токен",
        "description": "Идемпотентное удаление по ID. Возвращает ok и для отсутствующего или чужого ID; чужие токены не удаляет. Тело не требуется.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "token_id",
            "in": "path",
            "required": true,
            "description": "ID собственного API-токена.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ]
      }
    },
    "/api/projects": {
      "get": {
        "operationId": "listProjects",
        "tags": [
          "Projects"
        ],
        "summary": "Получить доступные проекты",
        "description": "Возвращает доступные проекты с эффективной ролью. Администратор видит все. Глобальный viewer остаётся читателем даже при членстве editor.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "projects": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    }
                  },
                  "required": [
                    "projects"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      }
    },
    "/api/admin/users": {
      "get": {
        "operationId": "listUsers",
        "tags": [
          "Admin"
        ],
        "summary": "Получить пользователей",
        "description": "Перечень пользователей без паролей, хешей и токенов.",
        "x-access": "Только администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "users": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/User"
                      }
                    }
                  },
                  "required": [
                    "users"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      },
      "post": {
        "operationId": "createUser",
        "tags": [
          "Admin"
        ],
        "summary": "Создать пользователя",
        "description": "Роль обязательна; пароль 12–1024 символа. Повтор имени возвращает 409. Пароли передавайте только по HTTPS.",
        "x-access": "Только администратор",
        "responses": {
          "201": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/User"
                    }
                  },
                  "required": [
                    "user"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewUser"
              },
              "example": {
                "username": "new-teammate",
                "password": "replace-with-private-password",
                "role": "viewer"
              }
            }
          }
        }
      }
    },
    "/api/admin/users/{user_id}": {
      "patch": {
        "operationId": "updateUser",
        "tags": [
          "Admin"
        ],
        "summary": "Изменить роль, доступ или пароль",
        "description": "Разрешены только role, disabled и password. Любое успешное изменение отзывает все сессии и токены пользователя. Нельзя отключить или понизить последнего активного администратора.",
        "x-access": "Только администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/User"
                    }
                  },
                  "required": [
                    "user"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "description": "ID пользователя.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EditUser"
              },
              "example": {
                "role": "editor"
              }
            }
          }
        }
      }
    },
    "/api/admin/projects": {
      "get": {
        "operationId": "listAllProjects",
        "tags": [
          "Admin"
        ],
        "summary": "Получить все проекты",
        "description": "Возвращает id и name; эффективная роль здесь не добавляется.",
        "x-access": "Только администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "projects": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    }
                  },
                  "required": [
                    "projects"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      },
      "post": {
        "operationId": "createProject",
        "tags": [
          "Admin"
        ],
        "summary": "Создать проект",
        "description": "Создаёт область проектного доступа. Имя должно быть уникальным. Создание проекта не добавляет участников автоматически.",
        "x-access": "Только администратор",
        "responses": {
          "201": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project": {
                      "$ref": "#/components/schemas/Project"
                    }
                  },
                  "required": [
                    "project"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200,
                    "minLength": 1
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Racer"
              }
            }
          }
        }
      }
    },
    "/api/admin/projects/{project_id}/members": {
      "get": {
        "operationId": "listProjectMembers",
        "tags": [
          "Admin"
        ],
        "summary": "Получить участников проекта",
        "description": "Возвращает членство и роль каждого участника. Глобальная роль viewer по-прежнему запрещает изменения.",
        "x-access": "Только администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "members": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Member"
                      }
                    }
                  },
                  "required": [
                    "members"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "ID проекта; отличается от его имени.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ]
      }
    },
    "/api/admin/projects/{project_id}/members/{user_id}": {
      "put": {
        "operationId": "setProjectMember",
        "tags": [
          "Admin"
        ],
        "summary": "Добавить участника или изменить его роль",
        "description": "Создаёт или обновляет членство. project_id и user_id должны существовать. Права действуют со следующего запроса. Глобальная роль пользователя ограничивает права членства.",
        "x-access": "Только администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "ID проекта; отличается от его имени.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "description": "ID пользователя.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "editor",
                      "viewer"
                    ]
                  }
                },
                "required": [
                  "role"
                ]
              },
              "example": {
                "role": "editor"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeProjectMember",
        "tags": [
          "Admin"
        ],
        "summary": "Убрать участника из проекта",
        "description": "Удаляет членство. Пользователь и проект должны существовать; отсутствие самого членства не считается ошибкой. Тело не требуется.",
        "x-access": "Только администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "ID проекта; отличается от его имени.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "description": "ID пользователя.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ]
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/contents": {
      "get": {
        "operationId": "getPackageContents",
        "tags": [
          "Previews"
        ],
        "summary": "Прочитать состав пакета и зависимости",
        "description": "Читает ZIP, .unitypackage или описывает отдельный файл. Unity YAML GUID и Godot res:// ссылки связываются с ресурсами той же версии; бинарные/встроенные зависимости могут быть недоступны. entries[].references содержит ID ресурсов. Нативные .pak/.pck/.utoc возвращают status=unsupported. Повреждённый или превышающий лимиты архив возвращает status=unavailable с пустым entries. Лимиты: 4096 записей, 64 МиБ на файл, 256 МиБ распакованных данных, 12 секунд. Скрипты не исполняются. Доступ проверяется при каждом запросе. atom_id — SHA-256 исходных байтов файла; id — вхождение в конкретной версии. Учитываются доступные ссылки Unity importer metadata (.meta). Глобальное совпадение GUID не создаёт связей.",
        "x-access": "Авторизованный пользователь с доступом к ассету",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "200": {
            "description": "Состав либо явная причина недоступности.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "contents": {
                      "$ref": "#/components/schemas/PackageContents"
                    }
                  },
                  "required": [
                    "contents"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/contents/{entry_id}": {
      "get": {
        "operationId": "getPackageResource",
        "tags": [
          "Previews"
        ],
        "summary": "Прочитать ресурс для живого превью",
        "description": "Отдаёт только разрешённый ресурс выбранной версии по ID из manifest: GLB/glTF/OBJ/FBX, растровые изображения, аудио, BIN и MTL. HTML, SVG, скрипты, нативные prefab и сцены не отдаются inline. Модели ограничены 32 МиБ и проверяются перед передачей; изображение/аудио проверяются по содержимому. Range поддерживается. WebGL использует только ресурсы этого пакета, без внешних URL. Несовместимые ресурсы возвращают 415, оригинальный пакет доступен через download.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Полное медиа.",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "416": {
            "description": "Некорректный Range. Пустое тело ответа.",
            "headers": {
              "Content-Range": {
                "schema": {
                  "type": "string"
                },
                "description": "bytes */total"
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "206": {
            "description": "Часть медиа по Range.",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "Content-Range": {
                "schema": {
                  "type": "string"
                },
                "description": "Диапазон bytes start-end/total."
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/429"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "entry_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "Непрозрачный ID из contents.entries; путь файла не принимается."
          },
          {
            "name": "Range",
            "in": "header",
            "description": "Необязательный один диапазон bytes=start-end. Поддерживаются открытые и суффиксные диапазоны; несколько диапазонов не принимаются.",
            "schema": {
              "type": "string"
            },
            "example": "bytes=0-1023"
          }
        ]
      }
    },
    "/api/atoms": {
      "get": {
        "operationId": "listAtoms",
        "tags": [
          "Content"
        ],
        "summary": "Найти атомы",
        "description": "Уникальные SHA-256 файлов среди доступных пользователю пакетов. Служебные .meta и миниатюры Unity-пакетов в список не входят, но доступны по точному адресу. Имена, теги, источники, совпадения, количества и поиск не учитывают закрытые пакеты. Фильтры применяются к вхождениям до агрегации. current=1 оставляет файлы текущих версий. Сортировка по имени. Фоновый индекс строится автоматически при старте и после загрузки; pending означает, что результат ещё неполон.",
        "x-access": "Авторизованный пользователь; доступ проверяется до поиска и подсчётов",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "atoms": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Atom"
                      }
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "offset": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "index": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer",
                        "minimum": 0
                      }
                    }
                  },
                  "required": [
                    "atoms",
                    "total",
                    "offset",
                    "limit",
                    "index"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Подстрока в названии, описании, проекте, тегах, теме, стиле или названии/типе источника.",
            "schema": {
              "type": "string"
            },
            "example": "oak"
          },
          {
            "name": "engine",
            "in": "query",
            "description": "Целевая среда; отсутствие фильтра возвращает единый каталог.",
            "schema": {
              "type": "string",
              "enum": [
                "generic",
                "unity",
                "unreal",
                "godot"
              ],
              "description": "Целевая среда ассета и предпочтительный формат экспорта. Не определяет права доступа и не конвертирует существующие файлы."
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "Тип ассета.",
            "schema": {
              "type": "string",
              "enum": [
                "model",
                "prefab",
                "material",
                "texture",
                "audio",
                "animation",
                "shader",
                "script",
                "other"
              ]
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Категория из GET /api/catalog/taxonomy вместе с вложенными. Файл относится к категории своей версии; файлы версий без категории в такую выборку не входят. Неизвестная категория даёт 400."
          },
          {
            "name": "scope",
            "in": "query",
            "description": "Область доступа.",
            "schema": {
              "type": "string",
              "description": "generic — все активные пользователи команды; project — по членству в проекте.",
              "enum": [
                "generic",
                "project"
              ]
            }
          },
          {
            "name": "project",
            "in": "query",
            "description": "Имя проекта, не ID.",
            "schema": {
              "type": "string"
            },
            "example": "Racer"
          },
          {
            "name": "tag",
            "in": "query",
            "description": "Один точный тег.",
            "schema": {
              "type": "string"
            },
            "example": "nature"
          },
          {
            "name": "theme",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Точное совпадение без учёта регистра."
          },
          {
            "name": "style",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Точное совпадение без учёта регистра."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 60
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "current",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "1 — только текущие версии; по умолчанию вся сохранённая история."
          },
          {
            "name": "collection",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "Показать элементы выбранной коллекции."
          },
          {
            "name": "candidates",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Вместе с collection включает все подходящие ресурсы и поле in_collection."
          }
        ]
      }
    },
    "/api/atoms/{atom_id}": {
      "get": {
        "operationId": "getAtom",
        "tags": [
          "Content"
        ],
        "summary": "Прочитать атом и его использования",
        "description": "Возвращает только доступные вхождения. Если доступных вхождений нет — 404, включая ранее доступный атом после отзыва прав. Пакеты при просмотре не изменяются.",
        "x-access": "Авторизованный пользователь; доступ проверяется до поиска и подсчётов",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "atom": {
                      "$ref": "#/components/schemas/AtomDetail"
                    }
                  },
                  "required": [
                    "atom"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "atom_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{64}$"
            },
            "description": "SHA-256 исходного содержимого файла."
          }
        ]
      }
    },
    "/api/atoms/{atom_id}/thumbnail": {
      "get": {
        "operationId": "getAtomThumbnail",
        "tags": [
          "Content"
        ],
        "summary": "Статическая миниатюра атома",
        "description": "PNG-миниатюра только для доступного растрового изображения. Сгенерирована из локальных исходных байтов после проверки прав; внешние URL не загружаются.",
        "x-access": "Авторизованный пользователь; доступ проверяется до поиска и подсчётов",
        "responses": {
          "200": {
            "description": "Нормализованная миниатюра.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "429": {
            "$ref": "#/components/responses/429"
          }
        },
        "parameters": [
          {
            "name": "atom_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{64}$"
            },
            "description": "SHA-256 исходного содержимого файла."
          }
        ]
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/dependencies": {
      "get": {
        "operationId": "getPackageDependencies",
        "tags": [
          "Content"
        ],
        "summary": "Прочитать связи версии",
        "description": "requires — явно закреплённые версии необходимых пакетов; used_by — обратные связи; shares_atoms_with — до 50 версий с одинаковыми файлами, не зависимости. has_unavailable сигнализирует о скрытых целях без раскрытия их имён/ID. Список и revision читаются из одного snapshot.",
        "x-access": "Авторизованный пользователь; доступ проверяется до поиска и подсчётов",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dependencies": {
                      "$ref": "#/components/schemas/PackageDependencies"
                    }
                  },
                  "required": [
                    "dependencies"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ]
      },
      "put": {
        "operationId": "setPackageDependencies",
        "tags": [
          "Content"
        ],
        "summary": "Изменить зависимости версии",
        "description": "Атомарно заменяет до 32 прямых связей. Сначала GET и его revision; при конфликте 409 перечитайте список. Запрещены ссылки на свой пакет, дубликаты, циклы и изменение списка со скрытыми целями. Смена текущей версии цели не изменяет связь. Удаление связи сохраняет файлы. Экспорт выдаёт исходный пакет: зависимые пакеты скачиваются отдельно.",
        "x-access": "Редактор с доступом к исходному пакету и правом чтения целей",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dependencies": {
                      "$ref": "#/components/schemas/PackageDependencies"
                    }
                  },
                  "required": [
                    "dependencies"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetDependencies"
              },
              "example": {
                "expected_revision": 0,
                "targets": [
                  {
                    "asset_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                    "version_id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/composition": {
      "get": {
        "operationId": "getComposition",
        "tags": [
          "Previews"
        ],
        "summary": "Состав самостоятельного комплекта",
        "description": "Исходная версия и её явно закреплённые транзитивные зависимости. key идентифицирует вхождение по версии и пути. bindings разрешает GUID только внутри версии и её доступных зависимостей, локальные файлы имеют приоритет. Неоднозначные ссылки перечислены в ambiguous. surface и nodes описывают поддерживаемые материалы и статические префабы. До 32 версий, 4096 файлов, 256 МиБ распакованных данных.",
        "x-access": "Авторизованный пользователь с доступом к ассету",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "200": {
            "description": "Состав либо явная причина недоступности.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "composition"
                  ],
                  "properties": {
                    "composition": {
                      "type": "object",
                      "required": [
                        "root",
                        "entries",
                        "scope",
                        "partial",
                        "has_unavailable",
                        "missing"
                      ],
                      "properties": {
                        "root": {
                          "type": "string"
                        },
                        "entries": {
                          "type": "array",
                          "items": {
                            "allOf": [
                              {
                                "$ref": "#/components/schemas/ContentEntry"
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "version_id": {
                                    "type": "string"
                                  },
                                  "asset_id": {
                                    "type": "string"
                                  },
                                  "key": {
                                    "type": "string"
                                  },
                                  "bindings": {
                                    "type": "object",
                                    "additionalProperties": {
                                      "type": "string"
                                    }
                                  },
                                  "ambiguous": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "path_bindings": {
                                    "type": "object",
                                    "additionalProperties": {
                                      "type": "string"
                                    },
                                    "description": "Путь → key вхождения в этой версии или её закреплённых зависимостях."
                                  }
                                }
                              }
                            ]
                          }
                        },
                        "scope": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "partial": {
                          "type": "boolean"
                        },
                        "has_unavailable": {
                          "type": "boolean"
                        },
                        "missing": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "path": {
                                "type": "string"
                              },
                              "count": {
                                "type": "integer"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/413"
          }
        }
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/bundle": {
      "get": {
        "operationId": "downloadBundle",
        "tags": [
          "Previews"
        ],
        "summary": "Скачать комплект ZIP",
        "description": "Собирает исходники версии и закреплённых пакетов с исходными путями и Unity .meta. Скрытые цели блокируют экспорт. Коллизии путей и GUID возвращают 409. Непрозрачные нативные контейнеры сохраняются целиком. Необеспеченные объявленные ссылки также возвращают 409. Поддержка нативных бинарных ссылок зависит от формата.",
        "x-access": "Авторизованный пользователь с доступом к ассету",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "200": {
            "description": "ZIP с файлами комплекта.",
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "415": {
            "$ref": "#/components/responses/415"
          }
        }
      }
    },
    "/api/collections": {
      "get": {
        "tags": [
          "Collections"
        ],
        "operationId": "listCollections",
        "summary": "Список коллекций",
        "description": "Только коллекции команды и доступных проектов. kind и item вместе выбирают подходящие коллекции для данного пакета или атома и возвращают contains.",
        "x-access": "Авторизованный пользователь с доступом к коллекции",
        "responses": {
          "200": {
            "description": "Успешный ответ",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "collections": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Collection"
                      }
                    }
                  },
                  "required": [
                    "collections"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "assets",
                "atoms"
              ]
            },
            "description": "Вид ресурса."
          },
          {
            "name": "item",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(?:[a-f0-9]{32}|[a-f0-9]{64})$"
            },
            "description": "ID пакета или SHA-256 атома."
          }
        ]
      },
      "post": {
        "tags": [
          "Collections"
        ],
        "operationId": "createCollection",
        "summary": "Создать коллекцию",
        "description": "Имя уникально в пределах команды или одного проекта без учёта регистра.",
        "x-access": "Редактор коллекции или администратор",
        "responses": {
          "201": {
            "description": "Успешный ответ",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "collection": {
                      "$ref": "#/components/schemas/Collection"
                    }
                  },
                  "required": [
                    "collection"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewCollection"
              }
            }
          }
        }
      }
    },
    "/api/collections/{collection_id}": {
      "patch": {
        "tags": [
          "Collections"
        ],
        "operationId": "renameCollection",
        "summary": "Переименовать коллекцию",
        "description": "Изменяет название. Область доступа задаётся при создании.",
        "x-access": "Редактор коллекции или администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "collection": {
                      "$ref": "#/components/schemas/Collection"
                    }
                  },
                  "required": [
                    "collection"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "collection_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "ID коллекции."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "additionalProperties": false,
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Collections"
        ],
        "operationId": "deleteCollection",
        "summary": "Удалить коллекцию",
        "description": "Удаляются подборка и её связи. Все версии пакетов и файлы сохраняются.",
        "x-access": "Редактор коллекции или администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "collection_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "ID коллекции."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ]
      }
    },
    "/api/collections/{collection_id}/{kind}/{item_id}": {
      "put": {
        "tags": [
          "Collections"
        ],
        "operationId": "addCollectionItem",
        "summary": "Добавить ресурс в коллекцию",
        "description": "Изменяет одну связь. Повтор операции безопасен. Командная коллекция принимает общие ресурсы. Проектная принимает также ресурсы своего проекта.",
        "x-access": "Редактор коллекции или администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "collection": {
                      "$ref": "#/components/schemas/Collection"
                    }
                  },
                  "required": [
                    "collection"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "collection_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "ID коллекции."
          },
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "assets",
                "atoms"
              ]
            },
            "description": "assets или atoms."
          },
          {
            "name": "item_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(?:[a-f0-9]{32}|[a-f0-9]{64})$"
            },
            "description": "32 символа для пакета, 64 для атома."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Collections"
        ],
        "operationId": "removeCollectionItem",
        "summary": "Убрать ресурс из коллекции",
        "description": "Изменяет одну связь. Повтор операции безопасен. Командная коллекция принимает общие ресурсы. Проектная принимает также ресурсы своего проекта.",
        "x-access": "Редактор коллекции или администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "collection": {
                      "$ref": "#/components/schemas/Collection"
                    }
                  },
                  "required": [
                    "collection"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "collection_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "ID коллекции."
          },
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "assets",
                "atoms"
              ]
            },
            "description": "assets или atoms."
          },
          {
            "name": "item_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(?:[a-f0-9]{32}|[a-f0-9]{64})$"
            },
            "description": "32 символа для пакета, 64 для атома."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ]
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/contents/{entry_id}/download": {
      "get": {
        "operationId": "downloadAtomOccurrence",
        "tags": [
          "Previews"
        ],
        "summary": "Скачать один файл пакета",
        "description": "Возвращает только исходные байты одного вхождения в указанной версии с оригинальным именем файла. Для любых индексируемых форматов. SHA-256 сверяется с индексом. Ответ — application/octet-stream и Content-Disposition: attachment. Доступны Range и 206/416; для закрытого пакета требуется доступ.",
        "x-access": "Авторизованный пользователь",
        "responses": {
          "200": {
            "description": "Исходные байты файла.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "416": {
            "description": "Некорректный Range. Пустое тело ответа.",
            "headers": {
              "Content-Range": {
                "schema": {
                  "type": "string"
                },
                "description": "bytes */total"
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "206": {
            "description": "Диапазон исходных байтов.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "409": {
            "$ref": "#/components/responses/409"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "entry_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "Непрозрачный ID из contents.entries; путь файла не принимается."
          },
          {
            "name": "Range",
            "in": "header",
            "description": "Необязательный один диапазон bytes=start-end. Поддерживаются открытые и суффиксные диапазоны; несколько диапазонов не принимаются.",
            "schema": {
              "type": "string"
            },
            "example": "bytes=0-1023"
          }
        ]
      }
    },
    "/api/v2/assets": {
      "get": {
        "operationId": "pageAssets",
        "tags": [
          "Assets"
        ],
        "summary": "Страница каталога",
        "description": "Краткие карточки, отобранные по правам и фильтрам до пагинации. Порядок стабилен благодаря ID. Поиск по подстроке без учёта регистра; короткие запросы также поддерживаются.",
        "x-access": "Авторизованный пользователь с правом чтения ресурса",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 60
            },
            "description": "Число строк на странице."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Следующая страница предыдущего ответа с тем же запросом и сортировкой."
          },
          {
            "name": "q",
            "in": "query",
            "description": "Подстрока в названии, описании, проекте, тегах, теме, стиле или названии/типе источника.",
            "schema": {
              "type": "string"
            },
            "example": "oak"
          },
          {
            "name": "engine",
            "in": "query",
            "description": "Целевая среда; отсутствие фильтра возвращает единый каталог.",
            "schema": {
              "type": "string",
              "enum": [
                "generic",
                "unity",
                "unreal",
                "godot"
              ],
              "description": "Целевая среда ассета и предпочтительный формат экспорта. Не определяет права доступа и не конвертирует существующие файлы."
            }
          },
          {
            "name": "space",
            "in": "query",
            "description": "Совместимость с v0.6: unity для engine=unity, general для остальных. Используйте engine.",
            "schema": {
              "type": "string",
              "enum": [
                "general",
                "unity"
              ],
              "deprecated": true,
              "description": "Совместимость с v0.6: unity для engine=unity, general для остальных. Используйте engine."
            },
            "deprecated": true
          },
          {
            "name": "type",
            "in": "query",
            "description": "Тип ассета.",
            "schema": {
              "type": "string",
              "enum": [
                "model",
                "prefab",
                "material",
                "texture",
                "audio",
                "animation",
                "shader",
                "script",
                "other"
              ]
            }
          },
          {
            "name": "scope",
            "in": "query",
            "description": "Область доступа.",
            "schema": {
              "type": "string",
              "description": "generic — все активные пользователи команды; project — по членству в проекте.",
              "enum": [
                "generic",
                "project"
              ]
            }
          },
          {
            "name": "project",
            "in": "query",
            "description": "Имя проекта, не ID.",
            "schema": {
              "type": "string"
            },
            "example": "Racer"
          },
          {
            "name": "tag",
            "in": "query",
            "description": "Один точный тег.",
            "schema": {
              "type": "string"
            },
            "example": "nature"
          },
          {
            "name": "theme",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Точное совпадение без учёта регистра."
          },
          {
            "name": "style",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Точное совпадение без учёта регистра."
          },
          {
            "name": "collection",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            },
            "description": "Показать элементы выбранной коллекции."
          },
          {
            "name": "candidates",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Вместе с collection включает все подходящие ресурсы и поле in_collection."
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created",
                "updated",
                "name"
              ],
              "default": "created"
            },
            "description": "По дате создания, дате текущих версий или имени."
          },
          {
            "name": "include_total",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1"
              ]
            },
            "description": "Точный счётчик всех совпадений по явному запросу."
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000
            },
            "description": "Совместимость со старыми MCP-клиентами. Используется без курсора."
          }
        ],
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "assets": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CatalogCard"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "assets",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      }
    },
    "/api/v2/assets/{asset_id}": {
      "get": {
        "operationId": "getCatalogCard",
        "tags": [
          "Assets"
        ],
        "summary": "Краткая карточка",
        "description": "Возвращает одну карточку со ссылками на текущие версии и обложку.",
        "x-access": "Авторизованный пользователь с правом чтения ресурса",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "asset": {
                      "$ref": "#/components/schemas/CatalogCard"
                    }
                  },
                  "required": [
                    "asset"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      }
    },
    "/api/v2/assets/{asset_id}/versions": {
      "get": {
        "operationId": "pageVersionHistory",
        "tags": [
          "Versions"
        ],
        "summary": "Страница истории",
        "description": "История в порядке убывания даты и ID. Паспорт и сведения о превью запрашиваются отдельно.",
        "x-access": "Авторизованный пользователь с правом чтения ресурса",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 60
            },
            "description": "Число строк на странице."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Следующая страница предыдущего ответа с тем же запросом и сортировкой."
          }
        ],
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "versions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/VersionHistoryEntry"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "versions",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      }
    },
    "/api/v2/assets/{asset_id}/versions/{version_id}": {
      "get": {
        "operationId": "getVersionDetail",
        "tags": [
          "Versions"
        ],
        "summary": "Одна версия с паспортом",
        "description": "Возвращает паспорт, происхождение и сведения о превью только выбранной версии.",
        "x-access": "Авторизованный пользователь с правом чтения ресурса",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "$ref": "#/components/schemas/Version"
                    }
                  },
                  "required": [
                    "version"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      }
    },
    "/api/v2/assets/{asset_id}/versions/{version_id}/thumbnail": {
      "get": {
        "operationId": "getCatalogThumbnail",
        "tags": [
          "Previews"
        ],
        "summary": "Подготовленная миниатюра каталога",
        "description": "Возвращает сохранённую PNG-обложку или заранее подготовленную миниатюру версии. Проверяет доступ к ассету и принадлежность версии. Поддерживает Range.",
        "x-access": "Авторизованный пользователь с правом чтения ресурса",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "Range",
            "in": "header",
            "description": "Один диапазон байтов.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PNG-обложка.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Часть PNG-обложки.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "416": {
            "description": "Недопустимый диапазон байтов."
          }
        }
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/representations/unitypackage": {
      "get": {
        "operationId": "downloadUnityRepresentation",
        "summary": "Собрать .unitypackage из ресурсов версии",
        "tags": [
          "Publications"
        ],
        "description": "Собирает нативный .unitypackage из проверенных файлов и .meta конкретной версии, опубликованной из Unity с рецептом. zip-оригинал можно получить по /download, а отдельный ресурс через каталог файлов. 404 означает, что доступная версия не имеет такого представления, 409 — несогласованность сохранённых ресурсов с рецептом.",
        "x-access": "Авторизованный пользователь с доступом к карточке",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "200": {
            "description": "Собранный .unitypackage для этой версии. Контрольная сумма относится к файлу ответа.",
            "headers": {
              "X-Content-SHA256": {
                "required": true,
                "schema": {
                  "type": "string",
                  "pattern": "^[a-f0-9]{64}$"
                }
              },
              "Content-Disposition": {
                "schema": {
                  "type": "string"
                },
                "description": "Вложение с расширением .unitypackage."
              },
              "Content-Length": {
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/api/catalog/taxonomy": {
      "get": {
        "operationId": "getCatalogTaxonomy",
        "tags": [
          "ReadyCatalog"
        ],
        "summary": "Прочитать категории и действия",
        "description": "Стабильные ID категорий, parent (null у корня), метки и допустимые расширения файлов по действиям.",
        "x-access": "Авторизованный пользователь в пределах доступной библиотеки",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogTaxonomy"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        }
      }
    },
    "/api/catalog/ready": {
      "get": {
        "operationId": "searchReadyAssets",
        "tags": [
          "ReadyCatalog"
        ],
        "summary": "Найти готовые ассеты",
        "description": "Показывает версии, прошедшие проверку в Unity, с проверенными средами и нужными пакетами. У карточки выдаётся текущая версия, если она подтверждена, иначе последняя подтверждённая. Версии с внешними закреплёнными пакетами не показываются. download_url отдаёт импортируемый пакет. При продолжении передайте next_cursor и те же фильтры.",
        "x-access": "Авторизованный пользователь в пределах доступной библиотеки",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadyCatalogPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "in": "query",
            "name": "category",
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Стабильный id из GET /api/catalog/taxonomy."
            },
            "description": "Категория, включая все подразделы."
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            },
            "description": "Поиск по карточке, категории и точкам входа."
          },
          {
            "in": "query",
            "name": "project",
            "schema": {
              "type": "string"
            },
            "description": "Имя проекта."
          },
          {
            "in": "query",
            "name": "scope",
            "schema": {
              "type": "string",
              "enum": [
                "generic",
                "project"
              ]
            },
            "description": "Область доступа."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 60
            },
            "description": "Размер страницы."
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            },
            "description": "Значение next_cursor предыдущей страницы."
          }
        ]
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/catalog": {
      "get": {
        "operationId": "getVersionCatalog",
        "tags": [
          "ReadyCatalog"
        ],
        "summary": "Прочитать назначение версии",
        "description": "catalog=null означает, что у версии нет назначения. revision равна 0 до первого изменения. Ответ также содержит статус, журнал проверок и назначение соседней версии в suggestion.",
        "x-access": "Авторизованный пользователь в пределах доступной библиотеки",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogDesignationRevision"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ]
      },
      "put": {
        "operationId": "setVersionCatalog",
        "tags": [
          "ReadyCatalog"
        ],
        "summary": "Назначить категорию и точки входа",
        "description": "Заменяет всё назначение одной версии. Сначала прочитайте revision. Изменение назначения снимает подтверждение: новое описание нужно проверить заново. В рецепте допускаются только объявленные корни. В нативном пакете проверяются существующие пути и GUID. Внешние закреплённые зависимости исключают назначение.",
        "x-access": "Редактор с правом записи в карточку",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogDesignationRevision"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CatalogDesignationUpdate"
              },
              "example": {
                "expected_revision": 0,
                "category": "3d/environments/buildings",
                "entrypoints": [
                  {
                    "path": "Assets/City/HouseOnFire.prefab",
                    "action": "place",
                    "label": "Горящий дом"
                  }
                ]
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeVersionCatalog",
        "tags": [
          "ReadyCatalog"
        ],
        "summary": "Снять назначение версии",
        "description": "Снимает описание из готового каталога. Версия, байты и текущий указатель сохраняются. Отправьте прочитанную revision в JSON. Конфликт возвращает 409.",
        "x-access": "Редактор с правом записи в карточку",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogDesignationRevision"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expected_revision": {
                    "type": "integer",
                    "minimum": 0
                  }
                },
                "required": [
                  "expected_revision"
                ],
                "additionalProperties": false
              },
              "example": {
                "expected_revision": 1
              }
            }
          }
        }
      }
    },
    "/api/catalog/review": {
      "get": {
        "operationId": "listReadyReview",
        "tags": [
          "ReadyCatalog"
        ],
        "summary": "Очередь проверки готовых ассетов",
        "description": "Версии с назначением, которые ещё никто не проверял. Показывает только карточки, куда у пользователя есть запись. can_confirm=false означает, что подтвердить должен другой редактор.",
        "x-access": "Редактор или администратор",
        "responses": {
          "200": {
            "description": "Успешный ответ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogReviewPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 60
            },
            "description": "Размер страницы."
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            },
            "description": "Значение next_cursor предыдущей страницы."
          }
        ]
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/catalog/verifications": {
      "post": {
        "operationId": "recordCatalogVerification",
        "tags": [
          "ReadyCatalog"
        ],
        "summary": "Записать результат проверки в Unity",
        "description": "Записывает результат импорта версии в чистый проект. Успешная проверка выводит версию в каталог. Автор версии подтвердить её не может, кроме администратора: такая запись помечается self_confirmed. Сообщить о проблеме может любой редактор. Новый провал в той же среде снимает её успешный результат.",
        "x-access": "Редактор с правом записи в карточку",
        "responses": {
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "409": {
            "$ref": "#/components/responses/409"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "201": {
            "description": "Проверка записана.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogDesignationRevision"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ],
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CatalogVerificationCreate"
              },
              "example": {
                "expected_revision": 1,
                "unity_version": "6000.3.11f1",
                "render_pipeline": "urp",
                "result": "passed",
                "checked_paths": [
                  "Assets/City/HouseOnFire.prefab"
                ],
                "notes": "Эффект играет, потерянных ссылок нет."
              }
            }
          }
        }
      }
    },
    "/api/assets/{asset_id}/versions/{version_id}/webgl-preview": {
      "get": {
        "operationId": "downloadWebglPreview",
        "tags": [
          "Previews"
        ],
        "summary": "Скачать пакет WebGL-просмотра",
        "description": "AssetBundle для плеера Unity WebGL. Плеер и пакет должны быть собраны одной версией Unity.",
        "x-access": "Пользователь с доступом к ассету",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "200": {
            "description": "Пакет.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      },
      "put": {
        "operationId": "uploadWebglPreview",
        "tags": [
          "Previews"
        ],
        "summary": "Загрузить пакет WebGL-просмотра",
        "description": "Заменяет пакет этой версии. Сервер проверяет заголовок UnityFS, версию Unity и то, что entry_path есть в составе версии. Сборка выполняется вне сервера: webgl-viewer/build.py.",
        "x-access": "Редактор с правом записи или администратор",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "entry_path",
                  "render_pipeline",
                  "content_base64"
                ],
                "properties": {
                  "entry_path": {
                    "type": "string",
                    "description": "Путь Assets/... к префабу, материалу, мешу (.asset) или сцене."
                  },
                  "render_pipeline": {
                    "type": "string",
                    "enum": [
                      "builtin"
                    ]
                  },
                  "content_base64": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "entry_path": "Assets/Car.prefab",
                "render_pipeline": "builtin",
                "content_base64": "VW5pdHlGUwA="
              }
            }
          }
        },
        "responses": {
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "411": {
            "$ref": "#/components/responses/411"
          },
          "413": {
            "$ref": "#/components/responses/413"
          },
          "415": {
            "$ref": "#/components/responses/415"
          },
          "429": {
            "$ref": "#/components/responses/429"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "201": {
            "description": "Пакет сохранён.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webgl_preview": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string"
                        },
                        "size_bytes": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "sha256": {
                          "type": "string"
                        },
                        "unity_version": {
                          "type": "string"
                        },
                        "render_pipeline": {
                          "type": "string"
                        },
                        "entry_path": {
                          "type": "string"
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "prefab",
                            "material",
                            "scene"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "url",
                        "size_bytes",
                        "sha256",
                        "unity_version",
                        "render_pipeline",
                        "entry_path",
                        "kind",
                        "created_at"
                      ]
                    }
                  },
                  "required": [
                    "webgl_preview"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ]
      },
      "delete": {
        "operationId": "deleteWebglPreview",
        "tags": [
          "Previews"
        ],
        "summary": "Удалить пакет WebGL-просмотра",
        "description": "Версия и её файлы не меняются; просмотр возвращается к приближённому.",
        "x-access": "Редактор с правом записи или администратор",
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "ID карточки ассета.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "description": "UUID версии, а не её текстовая метка.",
            "schema": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "200": {
            "description": "Пакет удалён.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "deleted"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": [],
            "csrfToken": []
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Личный API-токен из меню аккаунта → API-токены. Срок 90 дней; права соответствуют владельцу."
      },
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "studio_session",
        "description": "Cookie после POST /api/auth/login; сессия действует 8 часов."
      },
      "csrfToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-CSRF-Token",
        "description": "Для изменяющих запросов с cookie. Получить из login или GET /api/auth/me. Bearer не требует CSRF."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Описание ошибки."
          }
        },
        "required": [
          "error"
        ]
      },
      "Ok": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          }
        },
        "required": [
          "ok"
        ]
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "username": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "editor",
              "viewer"
            ]
          },
          "disabled": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          }
        },
        "required": [
          "id",
          "username",
          "role",
          "disabled"
        ]
      },
      "Auth": {
        "type": "object",
        "properties": {
          "user": {
            "$ref": "#/components/schemas/User"
          },
          "csrf_token": {
            "anyOf": [
              {
                "type": "string",
                "description": "CSRF для cookie-сессии; null при Bearer."
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "user",
          "csrf_token"
        ]
      },
      "Token": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "name": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires": {
            "type": "number",
            "description": "Время истечения: Unix timestamp в секундах."
          }
        },
        "required": [
          "id",
          "name",
          "created_at",
          "expires"
        ]
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "editor",
              "viewer"
            ]
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "Member": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "username": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "editor",
              "viewer"
            ]
          },
          "disabled": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          }
        },
        "required": [
          "id",
          "username",
          "role",
          "disabled"
        ]
      },
      "Preview": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "none",
              "ready",
              "pending",
              "failed"
            ]
          },
          "kind": {
            "type": "string",
            "description": "Классифицированный тип предпросмотра."
          },
          "url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "thumbnail_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "message": {
            "type": "string"
          },
          "can_generate": {
            "type": "boolean"
          },
          "source_format": {
            "type": "string"
          },
          "derived": {
            "type": "boolean"
          },
          "media_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "media_kind": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "state",
          "kind",
          "url",
          "thumbnail_url",
          "message",
          "can_generate"
        ]
      },
      "ProvenanceInput": {
        "type": "object",
        "properties": {
          "project_name": {
            "type": "string",
            "description": "Название без абсолютного пути и символов /, \\, :.",
            "minLength": 1,
            "maxLength": 200
          },
          "unity_version": {
            "type": "string",
            "description": "Версия Unity, без пути.",
            "minLength": 1,
            "maxLength": 100
          },
          "export_kind": {
            "type": "string",
            "enum": [
              "original_file",
              "project_source_zip",
              "unitypackage",
              "unity_recipe"
            ]
          },
          "selected_files": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Безопасный относительный путь Assets/… или Packages/…; без .., пустых сегментов, обратных слешей и управляющих символов.",
              "maxLength": 500
            },
            "minItems": 1,
            "maxItems": 512,
            "uniqueItems": true
          },
          "selected_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 512,
            "description": "Должно совпадать с длиной selected_files."
          },
          "dependency_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000
          },
          "reviewed_warning_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000
          }
        },
        "required": [
          "project_name",
          "unity_version",
          "export_kind",
          "selected_files",
          "selected_count",
          "dependency_count"
        ],
        "additionalProperties": false
      },
      "ClientManifest": {
        "type": "object",
        "properties": {
          "selected_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000
          },
          "dependency_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000
          },
          "includes_dependencies": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "ProjectProvenance": {
        "type": "object",
        "properties": {
          "project_name": {
            "type": "string",
            "description": "Название без абсолютного пути и символов /, \\, :.",
            "minLength": 1,
            "maxLength": 200
          },
          "unity_version": {
            "type": "string",
            "description": "Версия Unity, без пути.",
            "minLength": 1,
            "maxLength": 100
          },
          "export_kind": {
            "type": "string",
            "enum": [
              "original_file",
              "project_source_zip",
              "unitypackage",
              "unity_recipe"
            ]
          },
          "selected_files": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Безопасный относительный путь Assets/… или Packages/…; без .., пустых сегментов, обратных слешей и управляющих символов.",
              "maxLength": 500
            },
            "minItems": 1,
            "maxItems": 512,
            "uniqueItems": true
          },
          "selected_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 512,
            "description": "Должно совпадать с длиной selected_files."
          },
          "dependency_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000
          },
          "reviewed_warning_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000
          },
          "origin": {
            "type": "string",
            "enum": [
              "original_project_file",
              "reconstructed_project_export"
            ]
          },
          "declaration": {
            "type": "string",
            "enum": [
              "client_declared"
            ]
          },
          "client_manifest": {
            "$ref": "#/components/schemas/ClientManifest"
          },
          "client_readiness": {
            "type": "string",
            "description": "Заявление клиента; сервер не подтверждает engine_verified."
          }
        }
      },
      "Version": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "asset_id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "version": {
            "type": "string",
            "description": "Метка версии; порядок истории определяется временем загрузки, а не SemVer.",
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$",
            "maxLength": 80
          },
          "kind": {
            "type": "string",
            "enum": [
              "source",
              "unity",
              "package"
            ],
            "description": "source — оригинал; unity — нативный пакет, подготовленный Unity-артефакт или набор исходных ресурсов с рецептом; package — пакет Unreal/Godot. Для каждого kind действует независимый указатель текущей версии."
          },
          "filename": {
            "type": "string",
            "description": "Имя файла без каталогов, /, \\, двоеточия и управляющих символов. Имена «.» и «..» запрещены.",
            "minLength": 1,
            "maxLength": 200
          },
          "notes": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 1
          },
          "sha256": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "download_url": {
            "type": "string"
          },
          "preview": {
            "$ref": "#/components/schemas/Preview"
          },
          "passport": {
            "type": "object",
            "properties": {},
            "additionalProperties": true,
            "description": "Технический паспорт зависит от формата. Неизвестные свойства не считаются проверенными."
          },
          "lineage": {
            "anyOf": [
              {
                "type": "object",
                "properties": {},
                "additionalProperties": true,
                "description": "Происхождение результата подготовки: source_asset_id, source_version_id, source_sha256, profile, profile_hash и tool_version."
              },
              {
                "type": "null"
              }
            ]
          },
          "readiness": {
            "type": "string"
          },
          "project_provenance": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ProjectProvenance"
              },
              {
                "type": "null"
              }
            ]
          },
          "representations": {
            "type": "object",
            "description": "У версии с рецептом Unity содержит путь к сборке .unitypackage по запросу.",
            "properties": {
              "unitypackage": {
                "type": "string",
                "format": "uri-reference"
              }
            },
            "required": [
              "unitypackage"
            ]
          },
          "unity_packages": {
            "type": "array",
            "description": "Только у версии с рецептом: пакеты Unity, которые получатель устанавливает сам.",
            "items": {
              "$ref": "#/components/schemas/UnityPackageRequirement"
            }
          }
        },
        "required": [
          "id",
          "asset_id",
          "version",
          "kind",
          "filename",
          "notes",
          "size_bytes",
          "sha256",
          "created_at",
          "download_url",
          "preview",
          "passport",
          "lineage",
          "readiness",
          "project_provenance"
        ]
      },
      "Asset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1
          },
          "type": {
            "type": "string",
            "enum": [
              "model",
              "prefab",
              "material",
              "texture",
              "audio",
              "animation",
              "shader",
              "script",
              "other"
            ]
          },
          "space": {
            "type": "string",
            "enum": [
              "general",
              "unity"
            ],
            "deprecated": true,
            "description": "Совместимость с v0.6: unity для engine=unity, general для остальных. Используйте engine."
          },
          "scope": {
            "type": "string",
            "description": "generic — все активные пользователи команды; project — по членству в проекте.",
            "enum": [
              "generic",
              "project"
            ]
          },
          "project": {
            "type": "string",
            "description": "Имя проекта; пустая строка для generic."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "maxItems": 30
          },
          "description": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "version_count": {
            "type": "integer",
            "minimum": 0
          },
          "latest_version": {
            "anyOf": [
              {
                "type": "string",
                "description": "Метка версии; порядок истории определяется временем загрузки, а не SemVer.",
                "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$",
                "maxLength": 80
              },
              {
                "type": "null"
              }
            ]
          },
          "current_versions": {
            "type": "object",
            "properties": {
              "source": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Version"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "unity": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Version"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "package": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/Version"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "source",
              "unity",
              "package"
            ]
          },
          "versions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Version"
            },
            "description": "Полная история только в ответе отдельной карточки; в списке ассетов отсутствует."
          },
          "engine": {
            "type": "string",
            "enum": [
              "generic",
              "unity",
              "unreal",
              "godot"
            ],
            "description": "Целевая среда ассета и предпочтительный формат экспорта. Не определяет права доступа и не конвертирует существующие файлы."
          },
          "theme": {
            "type": "string",
            "maxLength": 120,
            "description": "Тема содержимого, например природа или космос."
          },
          "style": {
            "type": "string",
            "maxLength": 120,
            "description": "Визуальный или звуковой стиль."
          },
          "source": {
            "$ref": "#/components/schemas/AssetSource"
          },
          "in_collection": {
            "type": "boolean",
            "description": "При запросе кандидатов — входит ли ресурс в подборку."
          }
        },
        "required": [
          "id",
          "name",
          "type",
          "space",
          "scope",
          "project",
          "tags",
          "description",
          "created_at",
          "version_count",
          "latest_version",
          "current_versions",
          "engine"
        ]
      },
      "AssetResult": {
        "type": "object",
        "properties": {
          "asset": {
            "$ref": "#/components/schemas/Asset"
          }
        },
        "required": [
          "asset"
        ]
      },
      "CreateAsset": {
        "type": "object",
        "properties": {
          "space": {
            "type": "string",
            "enum": [
              "general",
              "unity"
            ],
            "deprecated": true,
            "description": "Совместимость с v0.6: unity для engine=unity, general для остальных. Используйте engine."
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1
          },
          "type": {
            "type": "string",
            "enum": [
              "model",
              "prefab",
              "material",
              "texture",
              "audio",
              "animation",
              "shader",
              "script",
              "other"
            ]
          },
          "scope": {
            "type": "string",
            "description": "generic — все активные пользователи команды; project — по членству в проекте.",
            "enum": [
              "generic",
              "project"
            ]
          },
          "project": {
            "type": "string",
            "description": "Обязательное точное имя существующего проекта при scope=project.",
            "maxLength": 200
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "maxItems": 30
          },
          "description": {
            "type": "string",
            "maxLength": 10000
          },
          "engine": {
            "type": "string",
            "enum": [
              "generic",
              "unity",
              "unreal",
              "godot"
            ],
            "description": "Целевая среда ассета и предпочтительный формат экспорта. Не определяет права доступа и не конвертирует существующие файлы.",
            "default": "generic"
          },
          "theme": {
            "type": "string",
            "maxLength": 120,
            "description": "Тема содержимого, например природа или космос."
          },
          "style": {
            "type": "string",
            "maxLength": 120,
            "description": "Визуальный или звуковой стиль."
          },
          "source": {
            "$ref": "#/components/schemas/AssetSource"
          }
        },
        "required": [
          "name",
          "type",
          "scope"
        ],
        "if": {
          "properties": {
            "scope": {
              "const": "project"
            }
          },
          "required": [
            "scope"
          ]
        },
        "then": {
          "required": [
            "project"
          ],
          "properties": {
            "project": {
              "type": "string",
              "minLength": 1
            }
          }
        }
      },
      "PublicationAsset": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1
          },
          "type": {
            "type": "string",
            "enum": [
              "model",
              "prefab",
              "material",
              "texture",
              "audio",
              "animation",
              "shader",
              "script",
              "other"
            ]
          },
          "scope": {
            "type": "string",
            "description": "generic — все активные пользователи команды; project — по членству в проекте.",
            "enum": [
              "generic",
              "project"
            ]
          },
          "project": {
            "type": "string",
            "description": "Обязательное точное имя существующего проекта при scope=project.",
            "maxLength": 200
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "maxItems": 30
          },
          "description": {
            "type": "string",
            "maxLength": 10000
          },
          "theme": {
            "type": "string",
            "maxLength": 120,
            "description": "Тема содержимого, например природа или космос."
          },
          "style": {
            "type": "string",
            "maxLength": 120,
            "description": "Визуальный или звуковой стиль."
          },
          "source": {
            "$ref": "#/components/schemas/AssetSource"
          }
        },
        "required": [
          "name",
          "type",
          "scope"
        ],
        "additionalProperties": false,
        "if": {
          "properties": {
            "scope": {
              "const": "project"
            }
          },
          "required": [
            "scope"
          ]
        },
        "then": {
          "required": [
            "project"
          ],
          "properties": {
            "project": {
              "type": "string",
              "minLength": 1
            }
          }
        },
        "else": {
          "properties": {
            "project": {
              "const": ""
            }
          }
        }
      },
      "EditAsset": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1
          },
          "type": {
            "type": "string",
            "enum": [
              "model",
              "prefab",
              "material",
              "texture",
              "audio",
              "animation",
              "shader",
              "script",
              "other"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "maxItems": 30
          },
          "description": {
            "type": "string",
            "maxLength": 10000
          },
          "engine": {
            "type": "string",
            "enum": [
              "generic",
              "unity",
              "unreal",
              "godot"
            ],
            "description": "Целевая среда ассета и предпочтительный формат экспорта. Не определяет права доступа и не конвертирует существующие файлы."
          },
          "theme": {
            "type": "string",
            "maxLength": 120,
            "description": "Тема содержимого, например природа или космос."
          },
          "style": {
            "type": "string",
            "maxLength": 120,
            "description": "Визуальный или звуковой стиль."
          },
          "source": {
            "$ref": "#/components/schemas/AssetSource"
          }
        },
        "additionalProperties": false
      },
      "UploadVersion": {
        "type": "object",
        "properties": {
          "version": {
            "type": "string",
            "description": "Метка версии; порядок истории определяется временем загрузки, а не SemVer.",
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$",
            "maxLength": 80
          },
          "kind": {
            "type": "string",
            "enum": [
              "source",
              "unity",
              "package"
            ],
            "description": "source — оригинал; unity — .unitypackage или подготовленный Unity-артефакт; package — пакет Unreal/Godot (.zip, .pck, .pak, .utoc). Каждый kind имеет независимый текущий указатель."
          },
          "filename": {
            "type": "string",
            "description": "Имя файла без каталогов, /, \\, двоеточия и управляющих символов. Имена «.» и «..» запрещены.",
            "minLength": 1,
            "maxLength": 200
          },
          "content_base64": {
            "type": "string",
            "description": "Base64 непустого файла. Лимит декодированных байтов сообщает GET /api/health; по умолчанию 64 МиБ.",
            "contentEncoding": "base64"
          },
          "notes": {
            "type": "string",
            "maxLength": 10000
          },
          "publish_current": {
            "type": "boolean",
            "default": false,
            "description": "Только явное true одновременно назначает загруженную версию текущей."
          },
          "expected_current_id": {
            "anyOf": [
              {
                "type": "string",
                "description": "При publish_current=true обязательно: текущий ID для этого kind либо null, если указатель пуст. При false поле нужно опустить.",
                "pattern": "^[a-f0-9]{32}$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "version",
          "kind",
          "filename",
          "content_base64"
        ],
        "if": {
          "properties": {
            "publish_current": {
              "const": true
            }
          },
          "required": [
            "publish_current"
          ]
        },
        "then": {
          "required": [
            "expected_current_id"
          ]
        },
        "else": {
          "not": {
            "required": [
              "expected_current_id"
            ]
          }
        }
      },
      "CurrentVersion": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "source",
              "unity",
              "package"
            ],
            "description": "source — оригинал; unity — .unitypackage или подготовленный Unity-артефакт; package — пакет Unreal/Godot (.zip, .pck, .pak, .utoc). Каждый kind имеет независимый текущий указатель."
          },
          "version_id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "expected_current_id": {
            "anyOf": [
              {
                "type": "string",
                "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
                "pattern": "^[a-f0-9]{32}$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "kind",
          "version_id",
          "expected_current_id"
        ]
      },
      "CoverUpload": {
        "type": "object",
        "properties": {
          "content_base64": {
            "type": "string",
            "description": "Base64 PNG, JPEG или WebP; максимум 8 МиБ исходных байтов. Нормализуется в PNG.",
            "contentEncoding": "base64"
          },
          "filename": {
            "type": "string",
            "description": "Необязательное имя; содержимое проверяется по фактическому формату."
          }
        },
        "required": [
          "content_base64"
        ]
      },
      "NormalizationProfile": {
        "type": "object",
        "properties": {
          "source_units": {
            "type": "string",
            "enum": [
              "m",
              "cm",
              "mm"
            ]
          },
          "source_up": {
            "type": "string",
            "enum": [
              "X",
              "Y",
              "Z",
              "-X",
              "-Y",
              "-Z"
            ]
          },
          "source_forward": {
            "type": "string",
            "enum": [
              "X",
              "Y",
              "Z",
              "-X",
              "-Y",
              "-Z"
            ]
          },
          "geometry_only": {
            "type": "boolean"
          },
          "texture_kind": {
            "type": "string",
            "enum": [
              "default",
              "normal"
            ]
          },
          "texture_color_space": {
            "type": "string",
            "enum": [
              "sRGB",
              "linear"
            ]
          },
          "audio_sample_rate": {
            "type": "integer",
            "minimum": 8000,
            "maximum": 192000
          },
          "audio_channels": {
            "type": "integer",
            "enum": [
              1,
              2
            ]
          },
          "audio_bit_depth": {
            "type": "integer",
            "enum": [
              8,
              16,
              24,
              32
            ]
          },
          "texel_density": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "texture_coverage_width_m": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "texture_coverage_height_m": {
            "type": "number",
            "exclusiveMinimum": 0
          }
        },
        "additionalProperties": false,
        "description": "Явный профиль. Для OBJ нужны единицы и перпендикулярные оси; неполные данные могут привести к manual_required. Обе величины физического покрытия текстуры передаются вместе. Пустой объект не означает автоматическое угадывание параметров.",
        "dependentRequired": {
          "texture_coverage_width_m": [
            "texture_coverage_height_m"
          ],
          "texture_coverage_height_m": [
            "texture_coverage_width_m"
          ]
        }
      },
      "PrepareRequest": {
        "type": "object",
        "properties": {
          "profile": {
            "$ref": "#/components/schemas/NormalizationProfile"
          },
          "target_name": {
            "type": "string",
            "maxLength": 200
          }
        },
        "required": [
          "profile"
        ],
        "additionalProperties": false
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "source_asset_id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "source_version_id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "profile": {
            "$ref": "#/components/schemas/NormalizationProfile"
          },
          "profile_hash": {
            "type": "string"
          },
          "tool_version": {
            "type": "string"
          },
          "target_name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed",
              "manual_required"
            ]
          },
          "message": {
            "type": "string"
          },
          "target_asset_id": {
            "anyOf": [
              {
                "type": "string",
                "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
                "pattern": "^[a-f0-9]{32}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "source_asset_id",
          "source_version_id",
          "profile",
          "status",
          "message",
          "target_asset_id",
          "created_at",
          "updated_at"
        ]
      },
      "JobResult": {
        "type": "object",
        "properties": {
          "job": {
            "$ref": "#/components/schemas/Job"
          }
        },
        "required": [
          "job"
        ]
      },
      "Formats": {
        "type": "object",
        "properties": {
          "automatic": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "manual_required": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "limits": {
            "type": "object",
            "properties": {
              "source_bytes": {
                "type": "integer",
                "minimum": 0
              },
              "image_pixels": {
                "type": "integer",
                "minimum": 0
              },
              "model_vertices": {
                "type": "integer",
                "minimum": 0
              },
              "model_triangles": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "model_subset": {
            "type": "string"
          },
          "tool_version": {
            "type": "string"
          }
        },
        "required": [
          "automatic",
          "manual_required",
          "limits",
          "model_subset",
          "tool_version"
        ]
      },
      "PublicationRequest": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Ключ идемпотентности в пределах пользователя. Повторять можно только тот же полный payload.",
            "pattern": "^[A-Za-z0-9_-]{8,128}$"
          },
          "space": {
            "type": "string",
            "enum": [
              "general",
              "unity"
            ],
            "deprecated": true,
            "description": "Совместимость с v0.6: unity для engine=unity, general для остальных. Используйте engine."
          },
          "target_asset_id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "asset": {
            "$ref": "#/components/schemas/PublicationAsset"
          },
          "version": {
            "type": "string",
            "description": "Метка версии; порядок истории определяется временем загрузки, а не SemVer.",
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$",
            "maxLength": 80
          },
          "filename": {
            "type": "string",
            "description": "Имя файла без каталогов, /, \\, двоеточия и управляющих символов. Имена «.» и «..» запрещены.",
            "minLength": 1,
            "maxLength": 200
          },
          "content_base64": {
            "type": "string",
            "description": "Base64 непустого файла. Лимит декодированных байтов сообщает GET /api/health; по умолчанию 64 МиБ.",
            "contentEncoding": "base64"
          },
          "notes": {
            "type": "string",
            "maxLength": 10000
          },
          "publish_current": {
            "type": "boolean",
            "default": false,
            "description": "Только явное true одновременно назначает загруженную версию текущей."
          },
          "expected_current_id": {
            "anyOf": [
              {
                "type": "string",
                "description": "При publish_current=true обязательно: текущий ID для этого kind либо null, если указатель пуст. При false поле нужно опустить.",
                "pattern": "^[a-f0-9]{32}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "provenance": {
            "$ref": "#/components/schemas/ProvenanceInput"
          },
          "client_manifest": {
            "$ref": "#/components/schemas/ClientManifest"
          },
          "client_readiness": {
            "type": "string",
            "description": "Только заявление клиента, не результат серверной проверки.",
            "enum": [
              "",
              "unverified",
              "ready",
              "prepared",
              "engine_verified",
              "not_tested"
            ]
          },
          "engine": {
            "type": "string",
            "enum": [
              "generic",
              "unity"
            ],
            "description": "Целевая среда ассета и предпочтительный формат экспорта. Не определяет права доступа и не конвертирует существующие файлы."
          },
          "unity_recipe": {
            "$ref": "#/components/schemas/UnityRecipe"
          },
          "catalog": {
            "$ref": "#/components/schemas/CatalogDesignation",
            "description": "Необязательное назначение для нового рецепта Unity. Версия и описание сохраняются атомарно."
          }
        },
        "required": [
          "request_id",
          "version",
          "filename",
          "content_base64",
          "provenance"
        ],
        "additionalProperties": false,
        "oneOf": [
          {
            "required": [
              "asset"
            ],
            "not": {
              "required": [
                "target_asset_id"
              ]
            }
          },
          {
            "required": [
              "target_asset_id"
            ],
            "not": {
              "required": [
                "asset"
              ]
            }
          }
        ],
        "if": {
          "properties": {
            "publish_current": {
              "const": true
            }
          },
          "required": [
            "publish_current"
          ]
        },
        "then": {
          "required": [
            "expected_current_id"
          ]
        },
        "else": {
          "not": {
            "required": [
              "expected_current_id"
            ]
          }
        },
        "allOf": [
          {
            "anyOf": [
              {
                "required": [
                  "engine"
                ]
              },
              {
                "required": [
                  "space"
                ]
              }
            ]
          },
          {
            "if": {
              "properties": {
                "provenance": {
                  "properties": {
                    "export_kind": {
                      "const": "unity_recipe"
                    }
                  }
                }
              }
            },
            "then": {
              "required": [
                "unity_recipe"
              ],
              "properties": {
                "engine": {
                  "const": "unity"
                },
                "space": {
                  "const": "unity"
                },
                "filename": {
                  "const": "project-resources.zip"
                }
              }
            },
            "else": {
              "not": {
                "anyOf": [
                  {
                    "required": [
                      "unity_recipe"
                    ]
                  },
                  {
                    "required": [
                      "catalog"
                    ]
                  }
                ]
              }
            }
          }
        ]
      },
      "PublicationResult": {
        "type": "object",
        "properties": {
          "asset": {
            "$ref": "#/components/schemas/Asset"
          },
          "version": {
            "$ref": "#/components/schemas/Version"
          },
          "publication_id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "replayed": {
            "type": "boolean"
          }
        },
        "required": [
          "asset",
          "version",
          "publication_id",
          "replayed"
        ]
      },
      "Login": {
        "type": "object",
        "properties": {
          "username": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "password": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1024,
            "format": "password"
          }
        },
        "required": [
          "username",
          "password"
        ]
      },
      "NewUser": {
        "type": "object",
        "properties": {
          "username": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "password": {
            "type": "string",
            "minLength": 12,
            "maxLength": 1024,
            "format": "password"
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "editor",
              "viewer"
            ]
          }
        },
        "required": [
          "username",
          "password",
          "role"
        ]
      },
      "EditUser": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "editor",
              "viewer"
            ]
          },
          "disabled": {
            "type": "boolean"
          },
          "password": {
            "type": "string",
            "minLength": 12,
            "maxLength": 1024,
            "format": "password"
          }
        },
        "additionalProperties": false
      },
      "ContentEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "path": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 0
          },
          "type": {
            "type": "string"
          },
          "preview_kind": {
            "enum": [
              "image",
              "model",
              "audio",
              "material",
              "prefab",
              "text",
              null
            ]
          },
          "guid": {
            "type": [
              "string",
              "null"
            ]
          },
          "references": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
              "pattern": "^[a-f0-9]{32}$"
            }
          },
          "unresolved_references": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "atom_id": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "surface": {
            "type": "object",
            "description": "Сохранённые Unity shader (GUID), textures (slot → guid/UV scale/offset), colors (RGBA), values (числовые параметры)."
          },
          "nodes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Дерево статического prefab: id, parent, name, active, position, rotation, scale, mesh GUID и упорядоченные GUID материалов."
          },
          "resource_paths": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Нормализованные пути файлов OBJ/MTL/glTF, объявленные этим ресурсом."
          },
          "preview_of": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$",
            "description": "ID логического Unity-ресурса, для которого движок записал этот preview.png."
          },
          "download_url": {
            "type": "string",
            "description": "Путь для скачивания исходных байтов этого конкретного вхождения в версии, с исходным именем файла."
          }
        },
        "required": [
          "id",
          "path",
          "size_bytes",
          "type",
          "preview_kind",
          "guid",
          "references",
          "unresolved_references",
          "atom_id"
        ]
      },
      "PackageContents": {
        "type": "object",
        "properties": {
          "version_id": {
            "type": "string",
            "description": "Неизменяемый идентификатор: 32 строчных шестнадцатеричных символа.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "status": {
            "enum": [
              "ready",
              "unsupported",
              "unavailable"
            ]
          },
          "format": {
            "type": "string"
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContentEntry"
            }
          },
          "counts": {
            "type": "object",
            "additionalProperties": {
              "type": "integer",
              "minimum": 0
            }
          },
          "total_bytes": {
            "type": "integer",
            "minimum": 0
          },
          "message": {
            "type": "string"
          },
          "references_partial": {
            "type": "boolean"
          }
        },
        "required": [
          "version_id",
          "status",
          "format",
          "entries",
          "counts"
        ]
      },
      "AssetSource": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "unknown",
              "own",
              "fab",
              "sketchfab",
              "unity_asset_store",
              "polyhaven",
              "ambientcg",
              "github",
              "other"
            ],
            "default": "unknown"
          },
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "url": {
            "type": "string",
            "maxLength": 2000,
            "description": "Пустая строка или HTTP(S) URL без учётных данных. Сервер не загружает этот адрес."
          }
        }
      },
      "AtomOccurrence": {
        "type": "object",
        "properties": {
          "asset_id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "asset_name": {
            "type": "string"
          },
          "version_id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "version": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "source",
              "unity",
              "package"
            ]
          },
          "entry_id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "path": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "preview_kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "image",
              "model",
              "audio",
              "material",
              "prefab",
              "text",
              null
            ]
          },
          "engine": {
            "type": "string"
          },
          "is_current": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "download_url": {
            "type": "string",
            "description": "Путь для скачивания исходных байтов этого конкретного вхождения в версии, с исходным именем файла."
          }
        },
        "required": [
          "asset_id",
          "asset_name",
          "version_id",
          "version",
          "kind",
          "entry_id",
          "path",
          "type",
          "preview_kind",
          "engine",
          "is_current"
        ]
      },
      "Atom": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 0
          },
          "types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "occurrence_count": {
            "type": "integer",
            "minimum": 0
          },
          "package_count": {
            "type": "integer",
            "minimum": 0
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "representative": {
            "$ref": "#/components/schemas/AtomOccurrence"
          },
          "in_collection": {
            "type": "boolean",
            "description": "При запросе кандидатов — входит ли ресурс в подборку."
          }
        },
        "required": [
          "id",
          "name",
          "type",
          "size_bytes",
          "types",
          "occurrence_count",
          "package_count",
          "tags",
          "representative"
        ]
      },
      "AtomDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Atom"
          },
          {
            "type": "object",
            "properties": {
              "occurrences": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AtomOccurrence"
                },
                "maxItems": 500,
                "description": "До 500 доступных вхождений; occurrence_count содержит полное количество."
              }
            },
            "required": [
              "occurrences"
            ]
          }
        ]
      },
      "PackageReference": {
        "type": "object",
        "properties": {
          "version_id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "version": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "asset_id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "name": {
            "type": "string"
          },
          "engine": {
            "type": "string"
          }
        },
        "required": [
          "version_id",
          "version",
          "kind",
          "asset_id",
          "name",
          "engine"
        ]
      },
      "PackageDependencies": {
        "type": "object",
        "properties": {
          "revision": {
            "type": "integer",
            "minimum": 0
          },
          "requires": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PackageReference"
            }
          },
          "used_by": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PackageReference"
            }
          },
          "has_unavailable": {
            "type": "boolean"
          },
          "shares_atoms_with": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/PackageReference"
                },
                {
                  "type": "object",
                  "properties": {
                    "atom_count": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "atom_count"
                  ]
                }
              ]
            }
          }
        },
        "required": [
          "revision",
          "requires",
          "used_by",
          "has_unavailable",
          "shares_atoms_with"
        ]
      },
      "SetDependencies": {
        "type": "object",
        "properties": {
          "expected_revision": {
            "type": "integer",
            "minimum": 0
          },
          "targets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "asset_id": {
                  "type": "string",
                  "pattern": "^[a-f0-9]{32}$"
                },
                "version_id": {
                  "type": "string",
                  "pattern": "^[a-f0-9]{32}$"
                }
              },
              "required": [
                "asset_id",
                "version_id"
              ]
            },
            "maxItems": 32
          }
        },
        "required": [
          "expected_revision",
          "targets"
        ],
        "additionalProperties": false
      },
      "Collection": {
        "type": "object",
        "required": [
          "id",
          "name",
          "scope",
          "project",
          "can_edit",
          "package_count",
          "atom_count"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "scope": {
            "enum": [
              "generic",
              "project"
            ]
          },
          "project": {
            "type": "string"
          },
          "can_edit": {
            "type": "boolean"
          },
          "package_count": {
            "type": "integer",
            "minimum": 0
          },
          "atom_count": {
            "type": "integer",
            "minimum": 0
          },
          "contains": {
            "type": "boolean",
            "description": "Есть при фильтрации коллекций по конкретному ресурсу."
          }
        }
      },
      "NewCollection": {
        "type": "object",
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "scope": {
            "enum": [
              "generic",
              "project"
            ],
            "default": "generic"
          },
          "project": {
            "type": "string",
            "description": "Имя существующего проекта для scope=project."
          }
        }
      },
      "CatalogCurrentVersion": {
        "type": "object",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Version/properties/id"
          },
          "kind": {
            "$ref": "#/components/schemas/Version/properties/kind"
          },
          "version": {
            "$ref": "#/components/schemas/Version/properties/version"
          },
          "filename": {
            "$ref": "#/components/schemas/Version/properties/filename"
          },
          "created_at": {
            "$ref": "#/components/schemas/Version/properties/created_at"
          },
          "preview": {
            "type": "object",
            "properties": {
              "thumbnail_url": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "thumbnail_url"
            ]
          }
        },
        "required": [
          "id",
          "kind",
          "version",
          "filename",
          "created_at",
          "preview"
        ]
      },
      "CatalogCard": {
        "type": "object",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Asset/properties/id"
          },
          "name": {
            "$ref": "#/components/schemas/Asset/properties/name"
          },
          "type": {
            "$ref": "#/components/schemas/Asset/properties/type"
          },
          "engine": {
            "$ref": "#/components/schemas/Asset/properties/engine"
          },
          "space": {
            "$ref": "#/components/schemas/Asset/properties/space"
          },
          "scope": {
            "$ref": "#/components/schemas/Asset/properties/scope"
          },
          "project": {
            "$ref": "#/components/schemas/Asset/properties/project"
          },
          "tags": {
            "$ref": "#/components/schemas/Asset/properties/tags"
          },
          "description": {
            "$ref": "#/components/schemas/Asset/properties/description"
          },
          "created_at": {
            "$ref": "#/components/schemas/Asset/properties/created_at"
          },
          "theme": {
            "$ref": "#/components/schemas/Asset/properties/theme"
          },
          "style": {
            "$ref": "#/components/schemas/Asset/properties/style"
          },
          "source": {
            "$ref": "#/components/schemas/Asset/properties/source"
          },
          "version_count": {
            "$ref": "#/components/schemas/Asset/properties/version_count"
          },
          "latest_version": {
            "$ref": "#/components/schemas/Asset/properties/latest_version"
          },
          "current_versions": {
            "type": "object",
            "properties": {
              "source": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/CatalogCurrentVersion"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "unity": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/CatalogCurrentVersion"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "package": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/CatalogCurrentVersion"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "source",
              "unity",
              "package"
            ]
          },
          "thumbnail_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "in_collection": {
            "$ref": "#/components/schemas/Asset/properties/in_collection"
          }
        },
        "required": [
          "id",
          "name",
          "type",
          "engine",
          "space",
          "scope",
          "project",
          "tags",
          "description",
          "created_at",
          "theme",
          "style",
          "source",
          "version_count",
          "latest_version",
          "current_versions",
          "thumbnail_url"
        ]
      },
      "VersionHistoryEntry": {
        "type": "object",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Version/properties/id"
          },
          "asset_id": {
            "$ref": "#/components/schemas/Version/properties/asset_id"
          },
          "version": {
            "$ref": "#/components/schemas/Version/properties/version"
          },
          "kind": {
            "$ref": "#/components/schemas/Version/properties/kind"
          },
          "filename": {
            "$ref": "#/components/schemas/Version/properties/filename"
          },
          "notes": {
            "$ref": "#/components/schemas/Version/properties/notes"
          },
          "size_bytes": {
            "$ref": "#/components/schemas/Version/properties/size_bytes"
          },
          "sha256": {
            "$ref": "#/components/schemas/Version/properties/sha256"
          },
          "created_at": {
            "$ref": "#/components/schemas/Version/properties/created_at"
          },
          "download_url": {
            "$ref": "#/components/schemas/Version/properties/download_url"
          },
          "project_provenance": {
            "$ref": "#/components/schemas/Version/properties/project_provenance"
          }
        },
        "required": [
          "id",
          "asset_id",
          "version",
          "kind",
          "filename",
          "notes",
          "size_bytes",
          "sha256",
          "created_at",
          "download_url",
          "project_provenance"
        ]
      },
      "UnityRecipe": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schema",
          "roots",
          "files"
        ],
        "description": "Состав конкретной версии. ZIP содержит ровно перечисленные файлы и их исходные .meta. Пути и GUID уникальны. В roots входят выбранные в Unity файлы из provenance.selected_files, также описанные в files.",
        "properties": {
          "schema": {
            "type": "integer",
            "enum": [
              1,
              2
            ],
            "description": "2 добавляет packages: пакеты Unity, которые нужны файлам рецепта."
          },
          "roots": {
            "type": "array",
            "uniqueItems": true,
            "minItems": 1,
            "maxItems": 512,
            "items": {
              "type": "string",
              "pattern": "^Assets/",
              "maxLength": 495,
              "description": "Относительный путь файла внутри Assets/. Регистр всех сегментов должен совпадать. Путь .meta образуется добавлением суффикса к этому пути."
            }
          },
          "files": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1365,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "path",
                "guid",
                "sha256",
                "meta_sha256"
              ],
              "properties": {
                "path": {
                  "type": "string",
                  "pattern": "^Assets/",
                  "maxLength": 495,
                  "description": "Относительный путь файла внутри Assets/. Регистр всех сегментов должен совпадать. Путь .meta образуется добавлением суффикса к этому пути."
                },
                "guid": {
                  "type": "string",
                  "pattern": "^[a-f0-9]{32}$",
                  "not": {
                    "const": "00000000000000000000000000000000"
                  }
                },
                "sha256": {
                  "type": "string",
                  "pattern": "^[a-f0-9]{64}$"
                },
                "meta_sha256": {
                  "type": "string",
                  "pattern": "^[a-f0-9]{64}$"
                }
              }
            }
          },
          "packages": {
            "type": "array",
            "maxItems": 64,
            "description": "Только для schema 2. Перечисленные GUID считаются доступными после установки пакетов получателем. Файлы пакетов в снимок не входят.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "name",
                "version",
                "guids"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "maxLength": 214,
                  "pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)+$"
                },
                "version": {
                  "type": "string",
                  "maxLength": 64,
                  "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.+-]+)?$"
                },
                "guids": {
                  "type": "array",
                  "maxItems": 4096,
                  "uniqueItems": true,
                  "items": {
                    "type": "string",
                    "pattern": "^[a-f0-9]{32}$"
                  }
                }
              }
            }
          }
        }
      },
      "CatalogTaxonomy": {
        "type": "object",
        "properties": {
          "schema": {
            "const": 1,
            "type": "integer"
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "parent": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "label": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "parent",
                "label"
              ]
            }
          },
          "actions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "extensions": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              "required": [
                "id",
                "label",
                "extensions"
              ]
            }
          },
          "render_pipelines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "label"
              ]
            }
          }
        },
        "required": [
          "schema",
          "categories",
          "actions",
          "render_pipelines"
        ]
      },
      "CatalogEntrypoint": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "pattern": "^Assets/",
            "description": "Уникальный путь корня в этой версии."
          },
          "action": {
            "type": "string",
            "enum": [
              "place",
              "assign",
              "play",
              "open",
              "run"
            ]
          },
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          }
        },
        "required": [
          "path",
          "action",
          "label"
        ],
        "additionalProperties": false
      },
      "CatalogDesignation": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "minLength": 1,
            "description": "Стабильный id из GET /api/catalog/taxonomy."
          },
          "entrypoints": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogEntrypoint"
            },
            "minItems": 1,
            "maxItems": 64
          }
        },
        "required": [
          "category",
          "entrypoints"
        ],
        "additionalProperties": false
      },
      "CatalogDesignationUpdate": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "minLength": 1,
            "description": "Стабильный id из GET /api/catalog/taxonomy."
          },
          "entrypoints": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogEntrypoint"
            },
            "minItems": 1,
            "maxItems": 64
          },
          "expected_revision": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "category",
          "entrypoints",
          "expected_revision"
        ],
        "additionalProperties": false
      },
      "CatalogDesignationRevision": {
        "type": "object",
        "properties": {
          "catalog": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CatalogDesignation"
              },
              {
                "type": "null"
              }
            ]
          },
          "revision": {
            "type": "integer",
            "minimum": 0
          },
          "status": {
            "type": "string",
            "enum": [
              "none",
              "pending",
              "confirmed",
              "rejected"
            ],
            "description": "none: нет назначения. pending: ждёт проверки. confirmed: есть успешная проверка. rejected: проверяли, успешных результатов нет."
          },
          "inherited_from": {
            "type": [
              "string",
              "null"
            ],
            "description": "Версия, от которой унаследовано назначение."
          },
          "suggestion": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "from_version_id": {
                    "type": "string"
                  },
                  "category": {
                    "type": "string"
                  },
                  "entrypoints": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/CatalogEntrypoint"
                    }
                  }
                },
                "required": [
                  "from_version_id",
                  "category",
                  "entrypoints"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "Назначение соседней версии, если у этой его ещё нет."
          },
          "published_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "null у версий, загруженных до появления записи об авторе."
          },
          "environments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogEnvironment"
            }
          },
          "verifications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogVerificationRecord"
            }
          },
          "can_confirm": {
            "type": "boolean"
          }
        },
        "required": [
          "catalog",
          "revision",
          "status",
          "inherited_from",
          "suggestion",
          "published_by",
          "environments",
          "verifications",
          "can_confirm"
        ]
      },
      "CatalogReadyItem": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "minLength": 1,
            "description": "Стабильный id из GET /api/catalog/taxonomy."
          },
          "entrypoints": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogEntrypoint"
            },
            "minItems": 1,
            "maxItems": 64
          },
          "asset_id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "version_id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "scope": {
            "type": "string",
            "enum": [
              "generic",
              "project"
            ]
          },
          "project": {
            "type": "string"
          },
          "engine": {
            "type": "string",
            "const": "unity"
          },
          "version": {
            "type": "string"
          },
          "download_url": {
            "type": "string"
          },
          "thumbnail_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_current": {
            "type": "boolean",
            "description": "false, если текущая версия ещё не подтверждена и выдаётся последняя подтверждённая."
          },
          "verified": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/CatalogEnvironment"
            }
          },
          "requires_packages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UnityPackageRequirement"
            }
          }
        },
        "required": [
          "category",
          "entrypoints",
          "asset_id",
          "version_id",
          "name",
          "description",
          "type",
          "scope",
          "project",
          "engine",
          "version",
          "download_url",
          "thumbnail_url",
          "is_current",
          "verified",
          "requires_packages"
        ]
      },
      "ReadyCatalogPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogReadyItem"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "items",
          "has_more",
          "next_cursor"
        ]
      },
      "UnityPackageRequirement": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "version"
        ],
        "additionalProperties": false
      },
      "CatalogEnvironment": {
        "type": "object",
        "properties": {
          "unity_version": {
            "type": "string"
          },
          "render_pipeline": {
            "type": "string",
            "enum": [
              "builtin",
              "urp",
              "hdrp"
            ]
          },
          "verified_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "unity_version",
          "render_pipeline",
          "verified_at"
        ]
      },
      "CatalogVerificationRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "designation_revision": {
            "type": "integer",
            "minimum": 1
          },
          "current": {
            "type": "boolean",
            "description": "Запись относится к действующей редакции назначения."
          },
          "unity_version": {
            "type": "string"
          },
          "render_pipeline": {
            "type": "string",
            "enum": [
              "builtin",
              "urp",
              "hdrp"
            ]
          },
          "result": {
            "type": "string",
            "enum": [
              "passed",
              "failed"
            ]
          },
          "notes": {
            "type": "string"
          },
          "verified_by": {
            "type": "string"
          },
          "self_confirmed": {
            "type": "boolean",
            "description": "Администратор подтвердил версию, которую опубликовал сам."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "designation_revision",
          "current",
          "unity_version",
          "render_pipeline",
          "result",
          "notes",
          "verified_by",
          "self_confirmed",
          "created_at"
        ]
      },
      "CatalogVerificationCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "expected_revision",
          "unity_version",
          "render_pipeline",
          "result",
          "checked_paths"
        ],
        "properties": {
          "expected_revision": {
            "type": "integer",
            "minimum": 1
          },
          "unity_version": {
            "type": "string",
            "maxLength": 40,
            "pattern": "^[0-9]{4,}\\.[0-9]+\\.[0-9]+[abfpx][0-9]+$"
          },
          "render_pipeline": {
            "type": "string",
            "enum": [
              "builtin",
              "urp",
              "hdrp"
            ]
          },
          "result": {
            "type": "string",
            "enum": [
              "passed",
              "failed"
            ]
          },
          "checked_paths": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Все точки входа действующего назначения."
          },
          "notes": {
            "type": "string",
            "maxLength": 2000,
            "description": "Обязательно при result=failed."
          }
        }
      },
      "CatalogReviewItem": {
        "type": "object",
        "properties": {
          "asset_id": {
            "type": "string"
          },
          "version_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          },
          "project": {
            "type": "string"
          },
          "revision": {
            "type": "integer"
          },
          "category": {
            "type": "string"
          },
          "entrypoints": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogEntrypoint"
            }
          },
          "is_current": {
            "type": "boolean"
          },
          "inherited_from": {
            "type": [
              "string",
              "null"
            ]
          },
          "published_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "can_confirm": {
            "type": "boolean"
          }
        },
        "required": [
          "asset_id",
          "version_id",
          "name",
          "version",
          "scope",
          "project",
          "revision",
          "category",
          "entrypoints",
          "is_current",
          "inherited_from",
          "published_by",
          "can_confirm"
        ]
      },
      "CatalogReviewPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogReviewItem"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "items",
          "has_more",
          "next_cursor"
        ]
      }
    },
    "responses": {
      "400": {
        "description": "Некорректные параметры или JSON.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Некорректные параметры или JSON."
            }
          }
        }
      },
      "401": {
        "description": "Необходима действующая сессия или API-токен.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Необходима действующая сессия или API-токен."
            }
          }
        }
      },
      "403": {
        "description": "Нет прав, неверный CSRF или запрещённый origin/Host.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Нет прав, неверный CSRF или запрещённый origin/Host."
            }
          }
        }
      },
      "404": {
        "description": "Ресурс не найден.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Ресурс не найден."
            }
          }
        }
      },
      "409": {
        "description": "Конфликт версии, текущего указателя или уникальности.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Конфликт версии, текущего указателя или уникальности."
            }
          }
        }
      },
      "411": {
        "description": "Необходим Content-Length.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Необходим Content-Length."
            }
          }
        }
      },
      "413": {
        "description": "Превышен лимит загрузки.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Превышен лимит загрузки."
            }
          }
        }
      },
      "415": {
        "description": "Неподдерживаемый Content-Type или формат файла.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Неподдерживаемый Content-Type или формат файла."
            }
          }
        }
      },
      "429": {
        "description": "Слишком много запросов или заняты обработчики чтения пакетов. Повторите позднее.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Превышен лимит попыток входа или очередь заполнена."
            }
          }
        }
      },
      "500": {
        "description": "Внутренняя ошибка сервера.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Внутренняя ошибка сервера."
            }
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Публикация ассетов, таксономия и точки входа",
    "url": "/agent-guide.md"
  }
}
