openapi: 3.1.0
info:
  title: DemoQA AI API
  version: 1.0.0
  description: |
    Единый OpenAI-совместимый API DemoQA для текстовых и мультимодальных моделей.
    Все production-запросы требуют пользовательский Bearer key и могут расходовать кредиты.
  contact:
    name: DemoQA
    url: https://demoqa.ru/ai-api
servers:
  - url: https://demoqa.ru/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Models
    description: Каталог доступных моделей
  - name: Account
    description: Баланс аккаунта
  - name: Text
    description: Текстовые и мультимодальные ответы
  - name: Images
    description: Генерация изображений
  - name: Audio
    description: Транскрибация аудио
  - name: Video
    description: Асинхронная генерация видео
paths:
  /models:
    get:
      tags: [Models]
      operationId: listModels
      summary: Список моделей
      description: Возвращает только включённые для API модели и их фактические тарифные измерения.
      responses:
        '200':
          description: Каталог моделей
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelList'
              example:
                object: list
                data:
                  - id: openai/gpt-4.1-mini
                    object: model
                    created: 0
                    owned_by: demoqa
                    input_modalities: [text, image]
                    output_modalities: [text]
                    supported_endpoints: [chat/completions, responses]
                    enabled_endpoints: [chat/completions, responses]
                    pricing_dimensions: []
        default:
          $ref: '#/components/responses/Error'
  /user/balance:
    get:
      tags: [Account]
      operationId: getBalance
      summary: Текущий баланс
      responses:
        '200':
          description: Баланс в кредитах DemoQA
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Balance'
              example:
                object: balance
                available: '12.450000000'
                reserved: '0.000000000'
                currency: DQA_CREDIT
        default:
          $ref: '#/components/responses/Error'
  /chat/completions:
    post:
      tags: [Text]
      operationId: createChatCompletion
      summary: Chat Completions
      description: OpenAI-совместимый синхронный или streaming диалог.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
            examples:
              basic:
                summary: QA-проверки
                value:
                  model: openai/gpt-4.1-mini
                  messages:
                    - role: system
                      content: Ты опытный QA-инженер.
                    - role: user
                      content: Составь проверки поля age от 18 до 99.
                  temperature: 0.2
      responses:
        '200':
          description: JSON completion или поток SSE — зависит от `stream`.
          headers:
            X-DemoQA-Cost-Credits:
              $ref: '#/components/headers/CostCredits'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatCompletion'
            text/event-stream:
              schema:
                type: string
              example: "data: {\"id\":\"chatcmpl_01\",\"choices\":[{\"delta\":{\"content\":\"Привет\"}}]}\n\ndata: [DONE]\n\n"
        default:
          $ref: '#/components/responses/Error'
  /responses:
    post:
      tags: [Text]
      operationId: createResponse
      summary: Responses API
      description: Унифицированный ввод, tools, vision и streaming события.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResponseRequest'
            example:
              model: openai/gpt-4.1-mini
              instructions: Отвечай как QA-лид.
              input: Составь smoke checklist для релиза.
      responses:
        '200':
          description: JSON response или поток SSE — зависит от `stream`.
          headers:
            X-DemoQA-Cost-Credits:
              $ref: '#/components/headers/CostCredits'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponseObject'
            text/event-stream:
              schema:
                type: string
              example: "data: {\"type\":\"response.output_text.delta\",\"delta\":\"Проверка\"}\n\ndata: [DONE]\n\n"
        default:
          $ref: '#/components/responses/Error'
  /images/generations:
    post:
      tags: [Images]
      operationId: generateImages
      summary: Создать изображения
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImageGenerationRequest'
            example:
              model: image-model-id
              prompt: Минималистичная схема CI pipeline на светлом фоне
              n: 1
              size: 1024x1024
      responses:
        '200':
          description: Изображения в base64
          headers:
            X-DemoQA-Cost-Credits:
              $ref: '#/components/headers/CostCredits'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImageGenerationResponse'
        default:
          $ref: '#/components/responses/Error'
  /audio/transcriptions:
    post:
      tags: [Audio]
      operationId: createTranscription
      summary: Транскрибировать аудио
      description: Принимает ровно один файл не более 25 MiB.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/TranscriptionRequest'
            encoding:
              file:
                contentType: audio/mpeg, audio/mp4, audio/wav, audio/webm, audio/ogg
      responses:
        '200':
          description: Формат зависит от `response_format`.
          headers:
            X-DemoQA-Cost-Credits:
              $ref: '#/components/headers/CostCredits'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TranscriptionResponse'
            text/plain:
              schema:
                type: string
              example: Обсудили план регрессионного тестирования.
        default:
          $ref: '#/components/responses/Error'
  /videos:
    post:
      tags: [Video]
      operationId: createVideo
      summary: Создать video job
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VideoCreateRequest'
            example:
              model: video-model-id
              prompt: Анимация прохождения UI smoke test
              duration: 5
      responses:
        '202':
          description: Job принята
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoJob'
        default:
          $ref: '#/components/responses/Error'
  /videos/{id}:
    parameters:
      - $ref: '#/components/parameters/VideoId'
    get:
      tags: [Video]
      operationId: getVideo
      summary: Получить статус video job
      responses:
        '200':
          description: Актуальный статус
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoJob'
        default:
          $ref: '#/components/responses/Error'
    delete:
      tags: [Video]
      operationId: cancelVideo
      summary: Отменить video job
      responses:
        '200':
          description: Job отменена или уже была в терминальном состоянии
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoJob'
        default:
          $ref: '#/components/responses/Error'
  /videos/{id}/content:
    parameters:
      - $ref: '#/components/parameters/VideoId'
    get:
      tags: [Video]
      operationId: downloadVideo
      summary: Скачать готовое видео
      responses:
        '200':
          description: Бинарное содержимое завершённой job
          headers:
            Content-Disposition:
              schema:
                type: string
              example: inline; filename="video-video_01.mp4"
          content:
            video/mp4:
              schema:
                type: string
                format: binary
            application/octet-stream:
              schema:
                type: string
                format: binary
        default:
          $ref: '#/components/responses/Error'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: DemoQA API key
      description: Ключ вида `dqa_live_...` из раздела API-ключей DemoQA AI.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Устойчивый уникальный ключ одного логического billable-запроса.
      schema:
        type: string
        minLength: 1
        maxLength: 255
      example: 42dc6f91-e882-4e79-91af-b7bef0458887
    VideoId:
      name: id
      in: path
      required: true
      description: Идентификатор video job из `POST /videos`.
      schema:
        type: string
      example: video_01J6A2B3C4D5E6F7G8H9
  headers:
    CostCredits:
      description: Фактически списанные кредиты для синхронного ответа.
      schema:
        type: string
        pattern: '^\d+(?:\.\d{1,9})?$'
      example: '0.018700000'
  responses:
    Error:
      description: OpenAI-совместимая ошибка. Реальный HTTP status зависит от причины.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            unauthorized:
              summary: Неверный API-ключ
              value:
                error:
                  message: Invalid API key
                  type: authentication_error
                  param: null
                  code: invalid_api_key
            rateLimit:
              summary: Превышен лимит
              value:
                error:
                  message: Rate limit exceeded
                  type: rate_limit_error
                  param: null
                  code: rate_limit_exceeded
  schemas:
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [message, type, code]
          properties:
            message: { type: string }
            type: { type: string }
            param: { type: [string, 'null'] }
            code: { type: string }
          additionalProperties: true
      additionalProperties: false
    ModelList:
      type: object
      required: [object, data]
      properties:
        object: { type: string, const: list }
        data:
          type: array
          items: { $ref: '#/components/schemas/Model' }
    Model:
      type: object
      required: [id, object, created, owned_by, input_modalities, output_modalities, supported_endpoints, enabled_endpoints, pricing_dimensions]
      properties:
        id: { type: string }
        object: { type: string, const: model }
        created: { type: integer, const: 0 }
        owned_by: { type: string, const: demoqa }
        input_modalities: { type: array, items: { type: string } }
        output_modalities: { type: array, items: { type: string } }
        supported_endpoints: { type: array, items: { type: string } }
        enabled_endpoints: { type: array, items: { type: string } }
        pricing_dimensions:
          type: array
          items:
            type: object
            required: [dimension, variant_key, conditions, unit, price_attousd_per_unit, charge_attocredit_per_unit]
            properties:
              dimension: { type: string }
              variant_key: { type: string }
              conditions: { type: object, additionalProperties: true }
              unit: { type: string }
              price_attousd_per_unit: { type: string }
              charge_attocredit_per_unit: { type: string }
    Balance:
      type: object
      required: [object, available, reserved, currency]
      properties:
        object: { type: string, const: balance }
        available: { type: string }
        reserved: { type: string }
        currency: { type: string, const: DQA_CREDIT }
    Message:
      type: object
      required: [role, content]
      properties:
        role: { type: string, enum: [system, developer, user, assistant, tool] }
        content:
          oneOf:
            - type: string
            - type: array
              items: { type: object, additionalProperties: true }
        name: { type: string }
        tool_call_id: { type: string }
      additionalProperties: true
    Tool:
      type: object
      required: [type]
      properties:
        type: { type: string }
        function:
          type: object
          properties:
            name: { type: string }
            description: { type: string }
            parameters: { type: object, additionalProperties: true }
      additionalProperties: true
    ChatCompletionRequest:
      type: object
      required: [model, messages]
      properties:
        model: { type: string }
        messages: { type: array, minItems: 1, items: { $ref: '#/components/schemas/Message' } }
        stream: { type: boolean, default: false }
        temperature: { type: number }
        top_p: { type: number }
        max_tokens: { type: integer, minimum: 1 }
        tools: { type: array, items: { $ref: '#/components/schemas/Tool' } }
        tool_choice: {}
        response_format: { type: object, additionalProperties: true }
        user: { type: string }
      additionalProperties: true
    ChatCompletion:
      type: object
      required: [id, object, choices]
      properties:
        id: { type: string }
        object: { type: string }
        created: { type: integer }
        model: { type: string }
        choices:
          type: array
          items:
            type: object
            properties:
              index: { type: integer }
              message: { $ref: '#/components/schemas/Message' }
              finish_reason: { type: [string, 'null'] }
            additionalProperties: true
        usage: { type: object, additionalProperties: true }
      additionalProperties: true
    ResponseRequest:
      type: object
      required: [model, input]
      properties:
        model: { type: string }
        input:
          oneOf:
            - type: string
            - type: array
              items: { type: object, additionalProperties: true }
        instructions: { type: string }
        stream: { type: boolean, default: false }
        tools: { type: array, items: { $ref: '#/components/schemas/Tool' } }
        tool_choice: {}
        temperature: { type: number }
        max_output_tokens: { type: integer, minimum: 1 }
        metadata: { type: object, additionalProperties: true }
      additionalProperties: true
    ResponseObject:
      type: object
      required: [id, object, status, output]
      properties:
        id: { type: string }
        object: { type: string, const: response }
        status: { type: string }
        model: { type: string }
        output: { type: array, items: { type: object, additionalProperties: true } }
        output_text: { type: string }
        usage: { type: object, additionalProperties: true }
      additionalProperties: true
    ImageGenerationRequest:
      type: object
      additionalProperties: false
      required: [model, prompt]
      properties:
        model: { type: string }
        prompt: { type: string, minLength: 1, maxLength: 32000 }
        n: { type: integer, minimum: 1, maximum: 4, default: 1 }
        size: { type: string, pattern: '^(?:auto|[1-9][0-9]{1,4}x[1-9][0-9]{1,4}|[1-4]K)$' }
        quality: { type: string }
        output_format: { type: string }
        response_format: { type: string, const: b64_json }
        user: { type: string }
    ImageGenerationResponse:
      type: object
      required: [data]
      properties:
        created: { type: integer }
        data:
          type: array
          items:
            type: object
            required: [b64_json]
            properties:
              b64_json: { type: string, format: byte }
              mime_type: { type: string }
              revised_prompt: { type: string }
            additionalProperties: true
        usage: { type: object, additionalProperties: true }
      additionalProperties: true
    TranscriptionRequest:
      type: object
      additionalProperties: false
      required: [file, model]
      properties:
        file: { type: string, format: binary }
        model: { type: string }
        language: { type: string }
        response_format: { type: string, enum: [json, text], default: json }
        temperature: { type: number }
    TranscriptionResponse:
      type: object
      required: [text]
      properties:
        text: { type: string }
        usage: { type: object, additionalProperties: true }
      additionalProperties: true
    VideoCreateRequest:
      type: object
      additionalProperties: false
      required: [model, prompt]
      properties:
        model: { type: string }
        prompt: { type: string, minLength: 1, maxLength: 32000 }
        duration: { type: integer, minimum: 1, maximum: 120 }
        resolution: { type: string }
        aspect_ratio: { type: string }
        audio: { type: boolean }
        size: { type: string, pattern: '^[1-9]\d{1,4}x[1-9]\d{1,4}$' }
        seed: { type: integer, minimum: 0, maximum: 4294967295 }
        image_url: { type: string, format: uri }
        audio_url: { type: string, format: uri }
        video_url: { type: string, format: uri }
    VideoJob:
      type: object
      required: [id, status]
      properties:
        id: { type: string }
        object: { type: string }
        status: { type: string, enum: [queued, processing, completed, failed, cancelled] }
        model: { type: string }
        progress: { type: number, minimum: 0, maximum: 100 }
        created_at: { type: [integer, string] }
        completed_at: { type: [integer, string, 'null'] }
        error: { oneOf: [{ $ref: '#/components/schemas/ErrorResponse' }, { type: 'null' }] }
        content_url: { type: string, format: uri-reference }
      additionalProperties: true
