openapi: 3.1.0
info:
  title: AI Chaos API
  version: 1.0.0
  description: API для генерации и редактирования изображений, удаления фона и LLM-совместимого чата.
  license:
    name: Proprietary
    url: https://ai-chaos.com/terms
servers:
  - url: /
security:
  - ApiKeyAuth: []
  - BearerAuth: []
  - OAuth2: []
tags:
  - name: LLM
    description: Операции с языковыми моделями, совместимые с OpenAI и Anthropic.
  - name: Генерация изображений
    description: Создание, проверка и управление генерациями изображений.
  - name: Инструменты изображений
    description: Асинхронные операции редактирования и улучшения изображений.
  - name: Загрузки
    description: Библиотека сохраненных загрузок изображений аутентифицированного пользователя.
  - name: Хранилище
    description: Пользовательские S3-совместимые подключения для входных и выходных данных.
  - name: Аудио
    description: OpenAI-совместимые аудиомаршруты, пока недоступные из-за асинхронной очереди медиа.
  - name: Аккаунт
    description: Профиль аутентифицированного аккаунта, балансы Sparks, суммарное использование и использование по ключам. Требуется API-ключ, OAuth-токен с областью `account` или авторизованная сессия.
  - name: Система
    description: Проверка состояния публичных зависимостей сервиса.
  - name: Задания
    description: Этапы аккаунта с разовыми наградами бесплатными Sparks.
# Reusable per-operation security requirements. Every authenticated operation
# accepts an API key (X-API-Key or a user-owned Bearer key) OR an OAuth token
# carrying the scope listed next to `OAuth2`. Public operations override with
# `security: []`.
x-security-templates:
  api_key_only: &api_key_only
    - ApiKeyAuth: []
    - BearerAuth: []
  llm: &llm_security
    - ApiKeyAuth: []
    - BearerAuth: []
    - OAuth2: [llm]
  generations: &generations_security
    - ApiKeyAuth: []
    - BearerAuth: []
    - OAuth2: [generations]
  uploads: &uploads_security
    - ApiKeyAuth: []
    - BearerAuth: []
    - OAuth2: [uploads]
  storage: &storage_security
    - ApiKeyAuth: []
    - BearerAuth: []
    - OAuth2: [storage]
  account: &account_security
    - ApiKeyAuth: []
    - BearerAuth: []
    - OAuth2: [account]
paths:
  /v1/chat/completions:
    post:
      operationId: createChatCompletion
      security: *llm_security
      summary: Создать завершение чата, совместимое с OpenAI
      description: Accepts the OpenAI Chat Completions request format. Streaming responses are sent as server-sent events.
      tags: [LLM]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ChatCompletionRequest' }
      responses:
        '200':
          description: Chat completion
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChatCompletion' }
              example: { id: chatcmpl_123, object: chat.completion, created: 1735689600, model: qwen, choices: [{ index: 0, message: { role: assistant, content: Hello! }, finish_reason: stop }], usage: { prompt_tokens: 10, completion_tokens: 2, total_tokens: 12 } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '422': { $ref: '#/components/responses/ModelError' }
  /v1/messages:
    post: &anthropic_messages
      operationId: createMessage
      security: *llm_security
      summary: Создать сообщение, совместимое с Anthropic
      description: Accepts the Anthropic Messages API request format.
      tags: [LLM]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AnthropicMessageRequest' }
      responses:
        '200':
          description: Message response
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AnthropicMessage' }
              example: { id: msg_123, type: message, role: assistant, model: qwen, content: [{ type: text, text: Hello! }], stop_reason: end_turn, usage: { input_tokens: 10, output_tokens: 2 } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '422': { $ref: '#/components/responses/ModelError' }
  /anthropic/messages:
    post:
      <<: *anthropic_messages
      operationId: createAnthropicMessage
      summary: Создать сообщение, совместимое с Anthropic (базовый URL Anthropic)
  /v1/messages/count_tokens:
    post: &count_tokens
      operationId: countMessageTokens
      security: *llm_security
      summary: Подсчитать токены сообщения, совместимого с Anthropic
      tags: [LLM]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AnthropicMessageRequest' }
      responses:
        '200':
          description: Token count
          content:
            application/json:
              schema: { type: object, required: [input_tokens], properties: { input_tokens: { type: integer, example: 10 } } }
              example: { input_tokens: 10 }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /anthropic/messages/count_tokens:
    post:
      <<: *count_tokens
      operationId: countAnthropicMessageTokens
      summary: Подсчитать токены сообщения (базовый URL Anthropic)
  /api/v1/ais:
    get:
      operationId: listModels
      summary: Получить список доступных моделей
      tags: [LLM]
      security: []
      parameters:
        - { name: purpose, in: query, schema: { type: string, enum: [generation, video, chat, llm], default: generation } }
        - { name: provider, in: query, schema: { type: string }, description: Comma-separated provider codes. }
        - { name: family, in: query, schema: { type: string }, description: Comma-separated family names. }
        - { name: grouped, in: query, schema: { type: boolean, default: false } }
      responses:
        '200':
          description: Models, or groups when `grouped=true`
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ModelList' }
              example: { models: [{ id: 1, code: flux-schnell, name: FLUX Schnell, cost: 1, routes: [{ id: 2, provider_code: fal, priority: 1 }] }] }
        '400': { $ref: '#/components/responses/ModelError' }
  /api/v1/ais/families:
    get:
      operationId: listModelFamilies
      summary: Получить список семейств моделей генерации изображений
      tags: [LLM]
      security: []
      responses:
        '200':
          description: Model family names
          content:
            application/json:
              schema: { type: object, required: [families], properties: { families: { type: array, items: { type: string } } } }
              example: { families: [FLUX, SDXL] }
        '400': { $ref: '#/components/responses/ModelError' }
  /api/v1/generations:
    get:
      operationId: listGenerations
      summary: Получить список недавних генераций
      tags: [Генерация изображений]
      description: >
        Returns the authenticated caller's recent generation history. Anonymous
        requests receive an empty list. Because an authenticated call returns
        private per-user history, an OAuth token must carry the `generations`
        scope; API keys and signed-in sessions are unaffected.
      security: *generations_security
      parameters:
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 6 } }
        - { name: queue_type, in: query, schema: { type: string, enum: [generate, upscale, rembg, background, outpaint] } }
      responses:
        '200':
          description: Recent generations
          content:
            application/json:
              schema: { type: object, required: [generations], properties: { generations: { type: array, items: { $ref: '#/components/schemas/Generation' } } } }
              example: { generations: [{ id: 42, status: completed, image_url: /download/42/token/image.png, width: 1024, height: 1024, model: flux-schnell }] }
        '400': { $ref: '#/components/responses/ModelError' }
    post:
      operationId: createGeneration
      security: *generations_security
      summary: Создать генерацию изображения
      description: Submit text-to-image, image-to-image, or reference-resolution work. Supply at most one of `source_image` and `uploaded_image_id`.
      tags: [Генерация изображений]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema: { $ref: '#/components/schemas/GenerationRequest' }
            encoding: { source_image: { contentType: image/* }, mask: { contentType: image/* } }
      responses:
        '201':
          description: Generation queued
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GenerationCreated' }
              example: { id: 42, status: queued, batch_id: 9de8d8d7-7f2c-4dcb-aad9-60a5c5a6ed67, quantity: 1, generation_ids: [42] }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '422': { $ref: '#/components/responses/ModelError' }
  /api/v1/generations/{id}:
    parameters: [{ $ref: '#/components/parameters/GenerationId' }]
    get:
      operationId: getGeneration
      security: *generations_security
      summary: Получить статус и результат генерации
      tags: [Генерация изображений]
      responses:
        '200': { description: Generation, content: { application/json: { schema: { $ref: '#/components/schemas/Generation' }, example: { id: 42, status: generated, image_url: /download/42/token/image.png, mask_url: /api/v1/generations/42/mask, width: 1024, height: 1024 } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      operationId: deleteGeneration
      security: *generations_security
      summary: Безвозвратно удалить генерацию
      tags: [Генерация изображений]
      responses:
        '200': { description: Deleted, content: { application/json: { schema: { $ref: '#/components/schemas/Success' }, example: { success: true, id: 42, permanent: true } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/generations/{id}/cancel:
    post:
      operationId: cancelGeneration
      security: *generations_security
      summary: Отменить генерацию в очереди
      tags: [Генерация изображений]
      parameters: [{ $ref: '#/components/parameters/GenerationId' }]
      responses:
        '200': { description: Cancelled, content: { application/json: { schema: { $ref: '#/components/schemas/Success' }, example: { success: true, id: 42, status: cancelled } } } }
        '422': { $ref: '#/components/responses/ModelError' }
  /api/v1/generations/{id}/retry:
    post:
      operationId: retryGeneration
      security: *generations_security
      summary: Повторить неудачную генерацию
      tags: [Генерация изображений]
      parameters: [{ $ref: '#/components/parameters/GenerationId' }]
      responses:
        '201': { description: Retry queued, content: { application/json: { schema: { $ref: '#/components/schemas/GenerationCreated' }, example: { id: 43, status: pending, batch_id: 9de8d8d7-7f2c-4dcb-aad9-60a5c5a6ed67, quantity: 1, generation_ids: [43] } } } }
        '422': { $ref: '#/components/responses/ModelError' }
  /api/v1/generations/{id}/publish:
    post: &publish_generation
      operationId: publishGeneration
      security: *generations_security
      summary: Опубликовать завершенную генерацию
      tags: [Генерация изображений]
      parameters: [{ $ref: '#/components/parameters/GenerationId' }]
      responses:
        '200': { description: Publication updated, content: { application/json: { schema: { $ref: '#/components/schemas/Publication' }, example: { generation_ids: [42], private_generation_ids: [], publication_status: published, is_private: false } } } }
        '422': { $ref: '#/components/responses/ModelError' }
  /api/v1/generations/{id}/unpublish:
    post:
      <<: *publish_generation
      operationId: unpublishGeneration
      summary: Сделать генерацию приватной
  /api/v1/generations/{id}/upscale:
    post:
      operationId: upscaleGeneration
      security: *generations_security
      summary: Увеличить разрешение существующей генерации
      tags: [Генерация изображений]
      parameters: [{ $ref: '#/components/parameters/GenerationId' }]
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UpscaleGenerationRequest' }
      responses:
        '202': { description: Upscale queued, content: { application/json: { schema: { $ref: '#/components/schemas/AsyncGeneration' }, example: { id: 43, status: pending, status_url: /api/v1/generations/43 } } } }
        '422': { $ref: '#/components/responses/ModelError' }
  /api/v1/generations/{id}/mask:
    get:
      operationId: downloadGenerationMask
      security: *generations_security
      summary: Скачать маску генерации
      description: Returns the mask without changing generation state.
      tags: [Генерация изображений]
      parameters: [{ $ref: '#/components/parameters/GenerationId' }]
      responses:
        '200': { description: Mask image, content: { image/png: { schema: { type: string, format: binary } } } }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/rembg:
    post:
      operationId: removeBackground
      security: *generations_security
      summary: Удалить фон изображения
      tags: [Инструменты изображений]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema: { $ref: '#/components/schemas/RembgRequest' }
            encoding: { image: { contentType: image/* } }
      responses:
        '202': { description: Background removal queued, content: { application/json: { schema: { $ref: '#/components/schemas/AsyncGeneration' }, example: { id: 42, status: pending, status_url: /api/v1/generations/42 } } } }
        '403': { $ref: '#/components/responses/Forbidden' }
        '422': { $ref: '#/components/responses/ModelError' }
  /api/v1/background:
    post:
      operationId: generateBackground
      security: *generations_security
      summary: Сгенерировать и добавить новый фон изображения
      tags: [Инструменты изображений]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema: { $ref: '#/components/schemas/BackgroundRequest' }
            encoding: { image: { contentType: image/* } }
      responses:
        '202': { description: Background generation queued, content: { application/json: { schema: { $ref: '#/components/schemas/AsyncGeneration' } } } }
        '403': { $ref: '#/components/responses/Forbidden' }
        '422': { $ref: '#/components/responses/ModelError' }
  /api/v1/outpaint:
    post:
      operationId: outpaintImage
      security: *generations_security
      summary: Расширить холст изображения генеративным outpainting
      description: The target dimensions are derived from the source dimensions and pixel expansions. Supply exactly one source and at least one positive expansion; `width` and `height` are not accepted.
      tags: [Инструменты изображений]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema: { $ref: '#/components/schemas/OutpaintRequest' }
            encoding: { image: { contentType: image/* } }
      responses:
        '202': { description: Outpaint queued, content: { application/json: { schema: { $ref: '#/components/schemas/AsyncGeneration' }, example: { id: 44, status: pending, status_url: /api/v1/generations/44 } } } }
        '403': { $ref: '#/components/responses/Forbidden' }
        '422': { $ref: '#/components/responses/ModelError' }
  /api/v1/upscale:
    post:
      operationId: upscaleImage
      security: *generations_security
      summary: Увеличить разрешение загруженного изображения, URL или генерации
      tags: [Инструменты изображений]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema: { $ref: '#/components/schemas/UpscaleRequest' }
            encoding: { image: { contentType: image/* } }
          application/json:
            schema: { $ref: '#/components/schemas/UpscaleJsonRequest' }
      responses:
        '202': { description: Upscale queued, content: { application/json: { schema: { $ref: '#/components/schemas/AsyncGeneration' }, example: { id: 43, status: pending, status_url: /api/v1/generations/43 } } } }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '422': { $ref: '#/components/responses/ModelError' }
  /api/v1/storage_connectors:
    get:
      operationId: listStorageConnectors
      security: *storage_security
      summary: Получить список подключений хранилища текущего пользователя
      tags: [Хранилище]
      responses:
        '200': { description: Connector list, content: { application/json: { schema: { type: object, properties: { connectors: { type: array, items: { $ref: '#/components/schemas/StorageConnector' } } } } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
    post:
      operationId: createStorageConnector
      security: *storage_security
      summary: Подключить S3-совместимый бакет
      tags: [Хранилище]
      requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/StorageConnectorInput' } } } }
      responses:
        '201': { description: Connector created, content: { application/json: { schema: { $ref: '#/components/schemas/StorageConnector' } } } }
        '403': { $ref: '#/components/responses/Forbidden' }
        '422': { $ref: '#/components/responses/ModelError' }
  /api/v1/storage_connectors/{id}:
    parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
    get:
      operationId: getStorageConnector
      security: *storage_security
      summary: Получить подключение хранилища
      tags: [Хранилище]
      responses: { '200': { description: Connector, content: { application/json: { schema: { $ref: '#/components/schemas/StorageConnector' } } } }, '403': { $ref: '#/components/responses/Forbidden' }, '404': { $ref: '#/components/responses/NotFound' } }
    patch:
      operationId: updateStorageConnector
      security: *storage_security
      summary: Обновить или переподключить хранилище
      tags: [Хранилище]
      requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/StorageConnectorInput' } } } }
      responses: { '200': { description: Connector updated, content: { application/json: { schema: { $ref: '#/components/schemas/StorageConnector' } } } }, '403': { $ref: '#/components/responses/Forbidden' }, '422': { $ref: '#/components/responses/ModelError' } }
    delete:
      operationId: disconnectStorageConnector
      security: *storage_security
      summary: Отключить хранилище и очистить его учетные данные
      tags: [Хранилище]
      responses: { '200': { description: Connector disconnected }, '403': { $ref: '#/components/responses/Forbidden' }, '404': { $ref: '#/components/responses/NotFound' } }
  /api/v1/storage_connectors/{id}/test:
    post:
      operationId: testStorageConnector
      security: *storage_security
      summary: Проверить разрешенные операции подключения
      tags: [Хранилище]
      parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
      requestBody: { content: { application/json: { schema: { type: object, properties: { test_key: { type: string, description: Required when the connector access mode includes read access. } } } } } }
      responses: { '200': { description: Connection succeeded }, '403': { $ref: '#/components/responses/Forbidden' }, '422': { $ref: '#/components/responses/ModelError' } }
  /api/v1/generation_exports/{id}:
    get:
      operationId: getGenerationExport
      security: *storage_security
      summary: Получить статус экспорта и кратковременный URL скачивания
      tags: [Хранилище]
      parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
      responses:
        '200': { description: Export status, content: { application/json: { schema: { $ref: '#/components/schemas/GenerationExport' } } } }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '503': { $ref: '#/components/responses/ModelError' }
  /api/v1/uploads:
    get:
      operationId: listUploads
      security: *uploads_security
      summary: Получить список загруженных изображений аутентифицированного пользователя
      tags: [Загрузки]
      responses:
        '200':
          description: Upload library page
          content:
            application/json:
              schema: { type: object, required: [uploads], properties: { uploads: { type: array, items: { $ref: '#/components/schemas/Upload' } }, next_page: { type: [integer, 'null'] } } }
              example: { uploads: [{ id: 12, original_filename: reference.png, width: 1024, height: 768, preview_url: /api/v1/uploads/12/preview }], next_page: null }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/uploads/{id}/preview:
    get:
      operationId: previewUpload
      security: *uploads_security
      summary: Показать сохраненную загрузку изображения внутри страницы
      tags: [Загрузки]
      parameters:
        - { name: id, in: path, required: true, schema: { type: integer }, example: 12 }
      responses:
        '200': { description: Image preview, content: { image/jpeg: { schema: { type: string, format: binary } }, image/png: { schema: { type: string, format: binary } }, image/gif: { schema: { type: string, format: binary } }, image/webp: { schema: { type: string, format: binary } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '415': { description: Upload MIME type cannot be previewed inline. }
  /api/v1/account/profile:
    get:
      operationId: getAccountProfile
      security: *account_security
      summary: Получить профиль аутентифицированного аккаунта
      description: "Returns the account bound to the presented credential. A user API key or OAuth token resolves to its owner; a public API key (no owner) returns `id: null` plus its own key summary."
      tags: [Аккаунт]
      responses:
        '200': { description: Account profile, content: { application/json: { schema: { $ref: '#/components/schemas/AccountProfile' } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/account/balances:
    get:
      operationId: getAccountBalances
      security: *account_security
      summary: Получить балансы Sparks аутентифицированного аккаунта
      tags: [Аккаунт]
      responses:
        '200': { description: Sparks balances, content: { application/json: { schema: { $ref: '#/components/schemas/AccountBalances' } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/account/usage:
    get:
      operationId: getAccountUsage
      security: *account_security
      summary: Получить суммарное использование аккаунта
      description: Cumulative generation and LLM usage. `generation_spend_sparks` counts only successfully charged generations; LLM figures are metering (the public LLM proxy is not spark-billed).
      tags: [Аккаунт]
      responses:
        '200': { description: Aggregate usage, content: { application/json: { schema: { $ref: '#/components/schemas/AccountUsage' } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/account/keys:
    get:
      operationId: listAccountKeys
      security: *account_security
      summary: Получить список API-ключей аккаунта
      description: Each key includes its cumulative spend ledger (`spend`, `spend_reserved`, `spend_remaining`) and structured `permissions`. Public API keys list only themselves.
      tags: [Аккаунт]
      responses:
        '200': { description: API keys, content: { application/json: { schema: { type: object, required: [keys], properties: { keys: { type: array, items: { $ref: '#/components/schemas/ApiKeySummary' } } } } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/account/keys/{id}/usage:
    parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
    get:
      operationId: getApiKeyUsage
      security: *account_security
      summary: Получить использование и расходы по ключу
      tags: [Аккаунт]
      responses:
        '200': { description: Per-key usage, content: { application/json: { schema: { $ref: '#/components/schemas/ApiKeyUsage' } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /v1/completions:
    post:
      operationId: createCompletion
      security: *llm_security
      summary: Создать текстовое завершение, совместимое с OpenAI
      description: Legacy completions are translated to a chat request. `suffix`, `best_of`, `echo`, and `logprobs` are unsupported.
      tags: [LLM]
      requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/CompletionRequest' } } } }
      responses:
        '200': { description: Completion, content: { application/json: { schema: { $ref: '#/components/schemas/Completion' } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '422': { $ref: '#/components/responses/ModelError' }
  /v1/responses:
    post:
      operationId: createResponse
      security: *llm_security
      summary: Создать ответ, совместимый с OpenAI
      description: Stateless text input only. Streaming, tools, storage, conversations, and background execution are unsupported.
      tags: [LLM]
      requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ResponseRequest' } } } }
      responses:
        '200': { description: Completed response, content: { application/json: { schema: { $ref: '#/components/schemas/Response' } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '422': { $ref: '#/components/responses/ModelError' }
  /v1/embeddings:
    post:
      operationId: createEmbeddings
      security: *llm_security
      summary: Создать эмбеддинги, совместимые с OpenAI
      tags: [LLM]
      requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/CapabilityRequest' } } } }
      responses:
        '200': { description: Provider embedding response, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '422': { $ref: '#/components/responses/ModelError' }
  /anthropic/v1/messages:
    post:
      <<: *anthropic_messages
      operationId: createAnthropicV1Message
      summary: Создать сообщение, совместимое с Anthropic (версионированный псевдоним)
  /anthropic/v1/messages/count_tokens:
    post:
      <<: *count_tokens
      operationId: countAnthropicV1MessageTokens
      summary: Подсчитать токены сообщения (версионированный псевдоним Anthropic)
  /v1/rerank:
    post: &rerank
      operationId: rerankV1
      security: *llm_security
      summary: Переранжировать документы совместимой моделью
      tags: [LLM]
      requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/CapabilityRequest' } } } }
      responses: { '200': { description: Provider reranking response, content: { application/json: { schema: { type: object, additionalProperties: true } } } }, '401': { $ref: '#/components/responses/Unauthorized' }, '422': { $ref: '#/components/responses/ModelError' } }
  /v1/reranking:
    post:
      <<: *rerank
      operationId: rerankingV1
  /rerank:
    post:
      <<: *rerank
      operationId: rerank
      summary: Переранжировать документы (совместимость с llama.cpp)
  /reranking:
    post:
      <<: *rerank
      operationId: reranking
      summary: Переранжировать документы (псевдоним совместимости с llama.cpp)
  /infill:
    post: &llama_capability
      operationId: infill
      security: *llm_security
      summary: Выполнить запрос infill (совместимость с llama.cpp)
      tags: [LLM]
      requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/CapabilityRequest' } } } }
      responses: { '200': { description: Provider capability response, content: { application/json: { schema: { type: object, additionalProperties: true } } } }, '401': { $ref: '#/components/responses/Unauthorized' }, '422': { $ref: '#/components/responses/ModelError' } }
  /completion:
    post:
      <<: *llama_capability
      operationId: llamaCompletion
      summary: Завершить промпт (совместимость с llama.cpp)
  /props:
    get:
      operationId: getModelProps
      security: *llm_security
      summary: Получить свойства модели, совместимой с llama.cpp
      tags: [LLM]
      parameters: [{ name: model, in: query, required: true, schema: { type: string } }]
      responses: { '200': { description: Model properties, content: { application/json: { schema: { type: object, additionalProperties: true } } } }, '400': { $ref: '#/components/responses/ModelError' }, '404': { $ref: '#/components/responses/NotFound' } }
  /v1/models:
    get:
      operationId: listOpenAIModels
      summary: Получить список моделей, совместимых с OpenAI
      description: "Public model listing in the OpenAI `object: list` shape. Each entry adds capabilities such as input/output modalities, context length, per-token pricing, tool and reasoning support, model organization, and provider routes."
      tags: [LLM]
      security: []
      responses: { '200': { description: Model list, content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIModelList' } } } } }
  /v1/models/{id}:
    parameters: [{ name: id, in: path, required: true, schema: { type: string }, description: Model code. }]
    get:
      operationId: getOpenAIModel
      summary: Получить одну модель, совместимую с OpenAI
      description: Returns a single published model in the same shape as the `GET /v1/models` listing.
      tags: [LLM]
      security: []
      responses:
        '200': { description: Model, content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIModel' }, example: { id: qwen3-7-plus, object: model, created: 0, owned_by: Alibaba, capabilities: { purpose: chat, context_length: 1000000, supports_tools: true, supports_reasoning: false } } } } }
        '404': { $ref: '#/components/responses/NotFound' }
  /llms.txt:
    get:
      operationId: getLlmIndex
      summary: Получить индекс для LLM-агентов
      description: Plain-text `llms.txt` index that links developer resources such as API documentation, OpenAPI, the MCP server, model catalog, monitor, pricing, and data policy.
      tags: [LLM]
      security: []
      responses: { '200': { description: Agent index, content: { text/plain: { schema: { type: string } } } } }
  /v1/images/generations:
    post: &openai_image_generation
      operationId: createOpenAIImageGeneration
      security: *generations_security
      summary: Поставить в очередь генерацию изображения, совместимую с OpenAI
      description: This is asynchronous. A `202` response contains job IDs; poll each `status_url` for completion rather than expecting image data.
      tags: [Генерация изображений]
      requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIImageRequest' } } } }
      responses: { '202': { description: Image jobs queued, content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIImageJobResponse' } } } }, '401': { $ref: '#/components/responses/Unauthorized' }, '422': { $ref: '#/components/responses/ModelError' } }
  /v1/images/edits:
    post:
      <<: *openai_image_generation
      operationId: createOpenAIImageEdit
      summary: Поставить в очередь редактирование изображения, совместимое с OpenAI
      description: This is asynchronous. Submit a multipart `image` upload (or `uploaded_image_id`); poll the returned job `status_url`.
      requestBody: { required: true, content: { multipart/form-data: { schema: { $ref: '#/components/schemas/OpenAIImageEditRequest' }, encoding: { image: { contentType: image/* }, mask: { contentType: image/* } } } } }
  /v1/audio/speech:
    post: &unavailable_audio
      operationId: createSpeech
      security: *llm_security
      summary: Конечная точка совместимости синтеза речи
      description: Routed for client compatibility but not implemented because media work is asynchronous.
      tags: [Аудио]
      responses: { '501': { $ref: '#/components/responses/NotImplemented' } }
  /v1/audio/transcriptions:
    post:
      <<: *unavailable_audio
      operationId: createTranscription
      summary: Конечная точка совместимости транскрибации аудио
  /v1/audio/voices:
    get: &unavailable_voices
      operationId: listVoices
      security: *llm_security
      summary: Конечная точка совместимости каталога голосов
      description: Routed for client compatibility but not implemented because media work is asynchronous.
      tags: [Аудио]
      responses: { '501': { $ref: '#/components/responses/NotImplemented' } }
    post:
      <<: *unavailable_voices
      operationId: createVoice
  /sdapi/v1/txt2img:
    post: &sdapi_image
      operationId: sdapiTxt2Img
      security: *generations_security
      summary: Поставить в очередь задачу Stable Diffusion WebUI «текст в изображение»
      description: This compatibility endpoint is asynchronous. Poll the returned `status_url`; it does not return encoded images.
      tags: [Генерация изображений]
      requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/SdapiImageRequest' } } } }
      responses: { '202': { description: Image jobs queued, content: { application/json: { schema: { $ref: '#/components/schemas/SdapiJobResponse' } } } }, '401': { $ref: '#/components/responses/Unauthorized' }, '422': { $ref: '#/components/responses/ModelError' } }
  /sdapi/v1/img2img:
    post:
      <<: *sdapi_image
      operationId: sdapiImg2Img
      summary: Поставить в очередь задачу Stable Diffusion WebUI «изображение в изображение»
      requestBody: { required: true, content: { multipart/form-data: { schema: { $ref: '#/components/schemas/SdapiImageRequest' }, encoding: { init_images: { contentType: image/* }, mask: { contentType: image/* } } } } }
  /sdapi/v1/loras:
    get:
      operationId: listSdapiLoras
      security: *generations_security
      summary: Получить список LoRA для Stable Diffusion WebUI
      description: AI Chaos does not expose provider LoRA weights as runnable assets, so this always returns an empty list.
      tags: [LLM]
      responses: { '200': { description: Empty LoRA list, content: { application/json: { schema: { type: array, items: { type: object } } } } }, '401': { $ref: '#/components/responses/Unauthorized' } }
  /health:
    get:
      operationId: getHealth
      summary: Проверить состояние базы данных и Redis
      tags: [Система]
      security: []
      responses: { '200': { description: Dependencies are healthy, content: { application/json: { schema: { $ref: '#/components/schemas/Health' } } } }, '503': { description: One or more dependencies are unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/Health' } } } } }
  /api/v1/quests/catalog:
    get:
      operationId: listQuests
      security: *account_security
      summary: Получить каталог заданий
      description: Returns every code-defined quest with its one-time spark reward and target, regardless of the user's progress.
      tags: [Задания]
      responses:
        '200':
          description: Quest catalog
          content:
            application/json:
              schema: { $ref: '#/components/schemas/QuestCatalog' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/quests/progress:
    get:
      operationId: getQuestProgress
      security: *account_security
      summary: Получить прогресс заданий текущего пользователя
      description: Recomputes and returns normalized per-quest progress for the authenticated user. Completed quests are claimable until claimed.
      tags: [Задания]
      responses:
        '200':
          description: Per-quest progress
          content:
            application/json:
              schema: { $ref: '#/components/schemas/QuestProgressList' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/quests/{quest_key}/check:
    post:
      operationId: checkQuest
      security: *account_security
      summary: Повторно проверить одно задание
      description: Recomputes eligibility for one quest and returns its current state. Terminal quests are throttled by a short cooldown.
      tags: [Задания]
      parameters: [{ $ref: '#/components/parameters/QuestKey' }]
      responses:
        '200':
          description: Quest state after evaluation
          content:
            application/json:
              schema: { $ref: '#/components/schemas/QuestProgress' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/quests/{quest_key}/claim:
    post:
      operationId: claimQuestReward
      security: *account_security
      summary: Получить награду за выполненное задание
      description: Atomically credits the one-time spark reward exactly once and is idempotent — repeated claims are no-ops.
      tags: [Задания]
      parameters: [{ $ref: '#/components/parameters/QuestKey' }]
      responses:
        '200':
          description: Reward claimed (or already claimed)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/QuestClaim' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409':
          description: Quest is not completed yet
          content:
            application/json:
              schema: { $ref: '#/components/schemas/QuestClaim' }
components:
  securitySchemes:
    ApiKeyAuth: { type: apiKey, in: header, name: X-API-Key }
    BearerAuth: { type: http, scheme: bearer }
    OAuth2:
      type: oauth2
      description: >
        OAuth 2.0 authorization-code flow (PKCE is mandatory for public
        clients). Authorize a registered application at `/oauth/authorize` and
        exchange the code for tokens at `/oauth/token`. Tokens are issued
        hashed (opaque). Each operation below documents the OAuth scope it
        requires; an OAuth token without that scope is rejected with
        `403 insufficient_scope`.
      flows:
        authorizationCode:
          authorizationUrl: /oauth/authorize
          tokenUrl: /oauth/token
          scopes:
            public: Read-only public information (default scope, model listings and health)
            llm: Chat/completions API and LLM chat endpoints
            generations: Image generation, editing, rembg, upscale, background, and outpaint operations
            account: Account profile, Sparks balances, usage, API keys, and quests
            uploads: The user's image-upload library (list, create, preview)
            storage: S3-compatible storage connectors and generation exports
  parameters:
    GenerationId: { name: id, in: path, required: true, schema: { type: integer }, example: 42 }
    QuestKey: { name: quest_key, in: path, required: true, schema: { type: string }, example: first_generation }
  responses:
    Unauthorized: { description: "Missing, invalid, expired, or revoked API credentials. An expired API key returns `401` with `{ \"error\": \"API key expired\" }`; an expired or revoked OAuth token returns `401` with `{ \"error\": \"invalid_token\" }`.", content: { application/json: { schema: { $ref: '#/components/schemas/ModelError' }, example: { error: Unauthorized } } } }
    Forbidden: { description: API key lacks access to the owner resource, content: { application/json: { schema: { $ref: '#/components/schemas/ModelError' }, example: { error: Public API keys cannot use this endpoint } } } }
    NotFound: { description: Resource not found, content: { application/json: { schema: { $ref: '#/components/schemas/ModelError' }, example: { error: Generation not found } } } }
    ModelError: { description: Invalid request or model error, content: { application/json: { schema: { $ref: '#/components/schemas/ModelError' }, example: { error: Model not found } } } }
    NotImplemented: { description: The routed compatibility capability is not implemented, content: { application/json: { schema: { type: object, required: [error], properties: { error: { type: [string, object] } } }, example: { error: { message: Capability is not available, type: not_implemented } } } } }
  schemas:
    ChatCompletionRequest:
      type: object
      required: [model, messages]
      properties:
        model: { type: string, example: qwen }
        messages: { type: array, items: { $ref: '#/components/schemas/ChatMessage' } }
        max_tokens: { type: integer, minimum: 1, example: 512 }
        temperature: { type: number, minimum: 0, maximum: 2, example: 0.7 }
        stream: { type: boolean, default: false }
    ChatMessage:
      type: object
      required: [role, content]
      properties:
        role: { type: string, enum: [system, user, assistant] }
        content: { type: string }
    ChatCompletion:
      type: object
      required: [id, object, created, model, choices]
      properties:
        id: { type: string }
        object: { type: string, const: chat.completion }
        created: { type: integer }
        model: { type: string }
        choices: { type: array, items: { type: object } }
        usage: { type: object }
    CompletionRequest:
      type: object
      required: [model, prompt]
      properties:
        model: { type: string }
        prompt: { type: string }
        max_tokens: { type: integer, minimum: 1 }
        temperature: { type: number }
        stream: { type: boolean, default: false }
    Completion:
      type: object
      required: [id, object, created, model, choices]
      properties:
        id: { type: string }
        object: { type: string, const: text_completion }
        created: { type: integer }
        model: { type: string }
        choices: { type: array, items: { type: object } }
        usage: { type: object }
    ResponseRequest:
      type: object
      required: [model, input]
      properties:
        model: { type: string }
        input: { oneOf: [{ type: string }, { type: array, items: { type: object } }] }
        instructions: { type: string }
        max_output_tokens: { type: integer, minimum: 1 }
        temperature: { type: number }
    Response:
      type: object
      required: [id, object, status, model, output]
      properties:
        id: { type: string }
        object: { type: string, const: response }
        status: { type: string, const: completed }
        model: { type: string }
        output: { type: array, items: { type: object } }
        usage: { type: object }
    CapabilityRequest:
      type: object
      required: [model]
      additionalProperties: true
      properties:
        model: { type: string }
    OpenAIModelList:
      type: object
      required: [object, data]
      properties:
        object: { type: string, const: list }
        data: { type: array, items: { $ref: '#/components/schemas/OpenAIModel' } }
    OpenAIModel:
      type: object
      required: [id, object, created, owned_by, capabilities]
      properties:
        id: { type: string, description: Model code used as the `model` request parameter. }
        object: { type: string, const: model }
        created: { type: integer }
        owned_by: { type: string, description: "Model organization when known, otherwise `aichaos`." }
        capabilities:
          type: object
          properties:
            purpose: { type: string }
            input_modalities: { type: array, items: { type: string } }
            output_modalities: { type: array, items: { type: string } }
            modalities:
              type: object
              properties:
                input: { type: array, items: { type: string } }
                output: { type: array, items: { type: string } }
            context_length: { type: integer, description: "Model context window in tokens, when known." }
            supports_tools: { type: boolean }
            supports_reasoning: { type: boolean }
            pricing:
              type: object
              description: Catalog pricing in RUB. Per-token models expose per-million-token rates; other models expose a unit cost and pricing type.
              properties:
                currency: { type: string, const: RUB }
                input_per_million_tokens: { type: number }
                output_per_million_tokens: { type: number }
                cached_input_per_million_tokens: { type: number }
                pricing_type: { type: string }
                cost: { type: number }
            routes:
              type: array
              description: Active provider routes for the model.
              items:
                type: object
                properties:
                  id: { type: integer }
                  provider_code: { type: string }
                  provider_name: { type: string }
                  provider_model: { type: [string, 'null'] }
                  quant: { type: [string, 'null'] }
                  priority: { type: integer }
    OpenAIImageRequest:
      type: object
      required: [prompt]
      properties:
        prompt: { type: string }
        model: { type: string }
        n: { type: integer, minimum: 1 }
        size: { type: string, pattern: '^\\d+x\\d+$' }
    OpenAIImageEditRequest:
      allOf:
        - $ref: '#/components/schemas/OpenAIImageRequest'
        - type: object
          properties:
            image: { type: string, format: binary }
            uploaded_image_id: { type: integer }
            mask: { type: string, format: binary }
            strength: { type: number, minimum: 0, maximum: 1 }
          anyOf: [{ required: [image] }, { required: [uploaded_image_id] }]
    OpenAIImageJobResponse:
      type: object
      required: [created, data, id, status, status_url]
      properties:
        created: { type: integer }
        data: { type: array, items: { $ref: '#/components/schemas/AsyncGeneration' } }
        id: { type: string }
        status: { type: string, const: queued }
        status_url: { type: string, format: uri-reference }
    SdapiImageRequest:
      type: object
      required: [prompt]
      properties:
        prompt: { type: string }
        model: { type: string }
        width: { type: integer, minimum: 1 }
        height: { type: integer, minimum: 1 }
        batch_size: { type: integer, minimum: 1 }
        n_iter: { type: integer, minimum: 1 }
        init_images: { type: array, maxItems: 1, items: { type: string, format: binary } }
        uploaded_image_id: { type: integer }
        mask: { type: string, format: binary }
        denoising_strength: { type: number, minimum: 0, maximum: 1 }
        seed: { type: integer }
    SdapiJobResponse:
      type: object
      required: [id, status, status_url, jobs]
      properties:
        id: { type: string }
        status: { type: string, const: queued }
        status_url: { type: string, format: uri-reference }
        jobs: { type: array, items: { $ref: '#/components/schemas/AsyncGeneration' } }
    Health:
      type: object
      required: [healthy, timestamp, checks]
      properties:
        healthy: { type: boolean }
        timestamp: { type: string, format: date-time }
        checks: { type: object, required: [database, redis], properties: { database: { type: boolean }, redis: { type: boolean } } }
    AnthropicMessageRequest:
      type: object
      required: [model, messages, max_tokens]
      properties:
        model: { type: string, example: qwen }
        messages: { type: array, items: { $ref: '#/components/schemas/ChatMessage' } }
        max_tokens: { type: integer, minimum: 1, example: 512 }
        system: { type: string }
        stream: { type: boolean, default: false }
    AnthropicMessage:
      type: object
      required: [id, type, role, content, model]
      properties:
        id: { type: string }
        type: { type: string, const: message }
        role: { type: string, const: assistant }
        model: { type: string }
        content: { type: array, items: { type: object } }
        stop_reason: { type: [string, 'null'] }
        usage: { type: object }
    GenerationRequest:
      type: object
      required: [prompt]
      properties:
        model: { type: string, description: Model code. }
        prompt: { type: string }
        width: { type: integer, minimum: 1, example: 1024 }
        height: { type: integer, minimum: 1, example: 1024 }
        quantity: { type: integer, minimum: 1, maximum: 4, default: 1 }
        seed: { type: integer }
        processing_type: { type: string, enum: [none, improve, translate, auto, on, off] }
        source_image: { type: string, format: binary }
        uploaded_image_id: { type: integer }
        source_generation_id: { type: integer }
        mask: { type: string, format: binary, description: Optional mask for supported edit routes. }
        mask_invert: { type: boolean, default: false }
        save_to_library: { type: boolean, default: true, description: Retain an uploaded source in the caller's upload library. }
        strength: { type: number, minimum: 0, maximum: 1 }
        use_reference_resolution: { type: boolean }
        generation_mode: { type: string }
        cfg: { type: number }
        sampler: { type: string }
        steps: { type: integer, minimum: 1 }
        ai_provider_id: { type: integer }
        provider_parameters: { type: object, additionalProperties: true }
        parameter_schema_checksum: { type: string }
    GenerationCreated:
      type: object
      required: [id, status, batch_id, quantity, generation_ids]
      properties:
        id: { type: integer }
        status: { type: string }
        batch_id: { type: string, format: uuid }
        quantity: { type: integer }
        generation_ids: { type: array, items: { type: integer } }
    Generation:
      type: object
      required: [id, status]
      properties:
        id: { type: integer }
        status: { type: string, enum: [queued, pending, processing, generated, completed, failed, cancelled] }
        image_url: { type: [string, 'null'], format: uri-reference }
        mask_url: { type: [string, 'null'], format: uri-reference }
        video_url: { type: [string, 'null'], format: uri-reference }
        width: { type: integer }
        height: { type: integer }
        prompt: { type: string }
        model: { type: string }
        generation_kind: { type: string, enum: [generate, rembg, upscale, background, outpaint] }
        queue_type: { type: [string, 'null'], enum: [generate, rembg, upscale, background, outpaint, 'null'] }
        seed: { type: integer }
        error: { type: string }
        created_at: { type: string, format: date-time }
    AsyncGeneration:
      type: object
      required: [id, status, status_url]
      properties:
        id: { type: integer }
        status: { type: string, enum: [queued, pending] }
        status_url: { type: string, format: uri-reference }
        created_at: { type: string, format: date-time }
        export_id: { type: integer }
        export_status_url: { type: string, format: uri-reference }
    RembgRequest:
      type: object
      properties:
        image: { type: string, format: binary }
        image_url: { type: string, format: uri }
        model: { type: string }
        threshold: { type: integer, minimum: 0, maximum: 255, default: 160 }
        binary: { type: boolean }
        sticker: { type: boolean }
        compress: { type: boolean }
        output_format: { type: string, enum: [png, webp, jpeg], default: png }
        include_mask: { type: boolean }
        input_connector_id: { type: integer }
        input_object_key: { type: string }
        output_connector_id: { type: integer }
      dependentRequired:
        input_connector_id: [input_object_key]
        input_object_key: [input_connector_id]
        output_connector_id: [output_format]
      oneOf: [{ required: [image] }, { required: [image_url] }, { required: [input_connector_id, input_object_key] }]
    UpscaleGenerationRequest:
      type: object
      required: [upscaler]
      properties:
        upscaler: { type: string, description: ID of an advertised Krasichka upscaler. }
        provider_account_id: { type: integer, description: Optional eligible Krasichka account. }
        upscale_factor: { type: number, enum: [2, 4, 8] }
        target_width: { type: integer, minimum: 1, maximum: 16384 }
        target_height: { type: integer, minimum: 1, maximum: 16384 }
      oneOf:
        - required: [upscale_factor]
        - required: [target_width]
      dependentRequired:
        target_width: [target_height]
        target_height: [target_width]
    UpscaleRequest:
      type: object
      required: [upscaler]
      properties:
        image: { type: string, format: binary }
        image_url: { type: string, format: uri }
        generation_id: { type: integer }
        input_connector_id: { type: integer }
        input_object_key: { type: string }
        output_connector_id: { type: integer }
        output_format: { type: string, enum: [png, jpeg, webp] }
        upscaler: { type: string, description: ID of an advertised Krasichka upscaler. }
        provider_account_id: { type: integer, description: Optional eligible Krasichka account. }
        upscale_factor: { type: number, enum: [2, 4, 8] }
        target_width: { type: integer, minimum: 1, maximum: 16384 }
        target_height: { type: integer, minimum: 1, maximum: 16384 }
      oneOf:
        - required: [image]
        - required: [image_url]
        - required: [generation_id]
        - required: [input_connector_id, input_object_key]
      dependentRequired:
        input_connector_id: [input_object_key]
        input_object_key: [input_connector_id]
        output_connector_id: [output_format]
        output_format: [output_connector_id]
        target_width: [target_height]
        target_height: [target_width]
      allOf:
        - oneOf:
            - required: [upscale_factor]
            - required: [target_width]
    UpscaleJsonRequest:
      allOf:
        - $ref: '#/components/schemas/UpscaleRequest'
        - type: object
          properties: { image: false }
    BackgroundRequest:
      type: object
      required: [prompt]
      properties:
        image: { type: string, format: binary }
        image_url: { type: string, format: uri }
        source_generation_id: { type: integer }
        input_connector_id: { type: integer }
        input_object_key: { type: string }
        output_connector_id: { type: integer }
        output_format: { type: string, enum: [png, jpeg, webp] }
        model: { type: string, description: Published model code with an inpaint-capable Krasichka route. }
        prompt: { type: string }
        width: { type: integer, minimum: 1 }
        height: { type: integer, minimum: 1 }
        aspect_ratio: { type: string, enum: [square, portrait, landscape], description: Selects the first configured model size with this orientation. }
        ai_provider_id: { type: integer }
      oneOf: [{ required: [image] }, { required: [image_url] }, { required: [source_generation_id] }, { required: [input_connector_id, input_object_key] }]
      allOf:
        - oneOf:
            - required: [width, height]
            - required: [aspect_ratio]
      dependentRequired:
        input_connector_id: [input_object_key]
        input_object_key: [input_connector_id]
        output_connector_id: [output_format]
        output_format: [output_connector_id]
    OutpaintRequest:
      type: object
      additionalProperties: false
      required: [prompt]
      properties:
        image: { type: string, format: binary }
        image_url: { type: string, format: uri }
        source_generation_id: { type: integer }
        input_connector_id: { type: integer }
        input_object_key: { type: string }
        output_connector_id: { type: integer }
        output_format: { type: string, enum: [png, jpeg, webp] }
        model: { type: string, description: Published model code with an inpaint-capable Krasichka route. }
        prompt: { type: string, minLength: 1 }
        ai_provider_id: { type: integer }
        expand_top: { type: integer, minimum: 0, default: 0 }
        expand_right: { type: integer, minimum: 0, default: 0 }
        expand_bottom: { type: integer, minimum: 0, default: 0 }
        expand_left: { type: integer, minimum: 0, default: 0 }
      dependentRequired:
        input_connector_id: [input_object_key]
        input_object_key: [input_connector_id]
        output_connector_id: [output_format]
        output_format: [output_connector_id]
      oneOf:
        - required: [image]
        - required: [image_url]
        - required: [source_generation_id]
        - required: [input_connector_id, input_object_key]
      anyOf:
        - properties: { expand_top: { minimum: 1 } }
          required: [expand_top]
        - properties: { expand_right: { minimum: 1 } }
          required: [expand_right]
        - properties: { expand_bottom: { minimum: 1 } }
          required: [expand_bottom]
        - properties: { expand_left: { minimum: 1 } }
          required: [expand_left]
    StorageConnectorInput:
      type: object
      properties:
        name: { type: string }
        kind: { type: string, const: s3 }
        endpoint: { type: string, format: uri }
        region: { type: string }
        bucket: { type: string }
        prefix: { type: string }
        access_key_id: { type: string, writeOnly: true }
        secret_access_key: { type: string, writeOnly: true }
        access_mode: { type: string, enum: [read, write, read_write] }
        enabled: { type: boolean }
    StorageConnector:
      type: object
      required: [id, name, kind, bucket, access_mode, enabled, has_credentials]
      properties:
        id: { type: integer }
        name: { type: string }
        kind: { type: string, const: s3 }
        endpoint: { type: [string, 'null'], format: uri }
        region: { type: [string, 'null'] }
        bucket: { type: string }
        prefix: { type: string }
        access_mode: { type: string, enum: [read, write, read_write] }
        enabled: { type: boolean }
        has_credentials: { type: boolean }
        created_at: { type: string, format: date-time }
    GenerationExport:
      type: object
      required: [id, generation_id, state, format, object_key, created_at, updated_at]
      properties:
        id: { type: integer }
        generation_id: { type: integer }
        state: { type: string, enum: [queued, writing, completed, failed] }
        format: { type: string, enum: [png, jpeg, webp] }
        object_key: { type: string }
        byte_size: { type: [integer, 'null'] }
        error: { type: [string, 'null'] }
        url: { type: string, format: uri }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    Publication:
      type: object
      required: [generation_ids, publication_status, is_private]
      properties:
        generation_ids: { type: array, items: { type: integer } }
        private_generation_ids: { type: array, items: { type: integer } }
        publication_status: { type: string, enum: [pending, published, private] }
        is_private: { type: boolean }
    Success:
      type: object
      required: [success, id]
      properties:
        success: { type: boolean }
        id: { type: integer }
        status: { type: string }
        permanent: { type: boolean }
    Upload:
      type: object
      required: [id, original_filename, preview_url]
      properties:
        id: { type: integer }
        original_filename: { type: string }
        mime_type: { type: string }
        file_size: { type: integer }
        width: { type: integer }
        height: { type: integer }
        preview_url: { type: string, format: uri-reference }
    ModelList:
      type: object
      properties:
        models: { type: array, items: { $ref: '#/components/schemas/Model' } }
        grouped: { type: object, additionalProperties: { type: array, items: { $ref: '#/components/schemas/Model' } } }
    Model:
      type: object
      required: [id, code, name, cost, routes]
      properties:
        id: { type: integer }
        code: { type: string }
        name: { type: string }
        cost: { type: number }
        family: { type: string }
        purpose: { type: string }
        routes: { type: array, items: { type: object, properties: { id: { type: integer }, provider_code: { type: string }, priority: { type: integer } } } }
    QuestCatalog:
      type: object
      required: [quests]
      properties:
        quests: { type: array, items: { $ref: '#/components/schemas/Quest' } }
    Quest:
      type: object
      required: [key, title, description, reward, balance_type, target]
      properties:
        key: { type: string, enum: [create_first_api_key, first_generation, first_public_generation, try_second_model, connect_telegram] }
        title: { type: string }
        description: { type: string }
        reward: { type: integer, description: Spark amount credited to the free balance. }
        balance_type: { type: string, const: free }
        target: { type: integer, minimum: 1 }
    QuestProgressList:
      type: object
      required: [quests]
      properties:
        quests: { type: array, items: { $ref: '#/components/schemas/QuestProgress' } }
    QuestProgress:
      type: object
      required: [key, title, description, reward, balance_type, progress, target, state]
      properties:
        key: { type: string, enum: [create_first_api_key, first_generation, first_public_generation, try_second_model, connect_telegram] }
        title: { type: string }
        description: { type: string }
        reward: { type: integer }
        balance_type: { type: string, const: free }
        progress: { type: integer, minimum: 0 }
        target: { type: integer, minimum: 1 }
        state: { type: string, enum: [available, completed, claimed] }
        completed_at: { type: [string, 'null'], format: date-time }
        claimed_at: { type: [string, 'null'], format: date-time }
    QuestClaim:
      type: object
      required: [key, progress, target, state, claimed]
      properties:
        key: { type: string, enum: [create_first_api_key, first_generation, first_public_generation, try_second_model, connect_telegram] }
        title: { type: string }
        description: { type: string }
        reward: { type: integer }
        balance_type: { type: string, const: free }
        progress: { type: integer }
        target: { type: integer }
        state: { type: string, enum: [available, completed, claimed] }
        completed_at: { type: [string, 'null'], format: date-time }
        claimed_at: { type: [string, 'null'], format: date-time }
        claimed: { type: boolean }
        already_claimed: { type: boolean }
        error: { type: string }
    ModelError:
      type: object
      required: [error]
      properties:
        error: { type: string }
        code: { type: string }
        fields: { type: object }
    AccountProfile:
      type: object
      required: [id]
      properties:
        id: { type: [integer, 'null'] }
        slug: { type: string }
        profile_name: { type: string }
        tgusername: { type: string }
        email: { type: string }
        role: { type: string }
        paid: { type: boolean }
        created_at: { type: string, format: date-time }
        api_key: { $ref: '#/components/schemas/ApiKeySummary' }
    AccountBalances:
      type: object
      required: [free_sparks, paid_sparks, total_sparks]
      properties:
        free_sparks: { type: [number, 'null'] }
        paid_sparks: { type: [number, 'null'] }
        total_sparks: { type: [number, 'null'] }
    AccountUsage:
      type: object
      required: [generations_count, generation_spend_sparks, llm_requests, llm_tokens_used, llm_cost_rub, usage_count]
      properties:
        generations_count: { type: integer }
        generation_spend_sparks: { type: number, description: Sparks charged for successfully completed generations. }
        llm_requests: { type: integer, description: "Completed LLM proxy requests (metered, not spark-billed)." }
        llm_tokens_used: { type: integer }
        llm_cost_rub: { type: number }
        usage_count: { type: integer }
        last_used_at: { type: [string, 'null'], format: date-time }
        public_api_key: { type: boolean }
    ApiKeySummary:
      type: object
      required: [id, kind, usage_count, permissions]
      properties:
        id: { type: integer }
        name: { type: [string, 'null'] }
        kind: { type: string }
        created_at: { type: string, format: date-time }
        expires_at: { type: [string, 'null'], format: date-time }
        last_used_at: { type: [string, 'null'], format: date-time }
        usage_count: { type: integer }
        credit_limit: { type: [integer, 'null'], description: Cumulative Sparks spend cap. Null means unrestricted. }
        spend: { type: number, description: Cumulative Sparks charged. }
        spend_reserved: { type: number, description: Sparks reserved for in-flight generations. }
        spend_remaining: { type: [number, 'null'], description: "Spend left on the cap, or null when unrestricted." }
        permissions:
          type: object
          description: "Structured key permissions: {\"models\": [\"code\"], \"llm_proxy\": true, \"account\": [\"read\"]}. Empty means unrestricted."
          additionalProperties: true
    ApiKeyUsage:
      type: object
      required: [key, usage]
      properties:
        key:
          type: object
          required: [id]
          properties:
            id: { type: integer }
            name: { type: [string, 'null'] }
            kind: { type: string }
        usage:
          type: object
          required: [generations_count, generation_spend_sparks, llm_requests, llm_tokens_used, llm_cost_rub, usage_count]
          properties:
            generations_count: { type: integer }
            generation_spend_sparks: { type: number }
            llm_requests: { type: integer }
            llm_tokens_used: { type: integer }
            llm_cost_rub: { type: number }
            usage_count: { type: integer }
            last_used_at: { type: [string, 'null'], format: date-time }
            credit_limit: { type: [integer, 'null'] }
            spend: { type: number }
            spend_reserved: { type: number }
            spend_remaining: { type: [number, 'null'] }
