openapi: 3.0.1
info:
  title: Koyal Film Arena Generation API
  description: >
    Thin async middleware over text-to-image and text-to-video model backends.
    Submit endpoints return a jobId immediately; poll /jobs/{jobId} until complete.
  version: "1.0.0"
servers:
  - url: https://YOUR-DEPLOYMENT-HOST/api/v1
    description: Film Arena generation API root (koyal-backend /api/v1)
paths:
  /models:
    get:
      operationId: listModels
      summary: List available image or video models.
      parameters:
        - name: type
          in: query
          required: true
          schema: { type: string, enum: [image, video] }
      responses:
        "200": { description: Model list }
  /images:
    post:
      operationId: generateImage
      summary: Submit a single image generation job.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, prompt]
              properties:
                model: { type: string, example: flux_1_1_pro_ultra }
                prompt: { type: string, example: "A neon city at dusk" }
                negative_prompt: { type: string }
                aspect_ratio: { type: string, example: "16:9" }
                seed: { type: integer }
      responses:
        "202": { description: Job queued — returns jobId }
  /videos:
    post:
      operationId: generateVideo
      summary: Submit a single video generation job.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, prompt]
              properties:
                model: { type: string, example: veo3_t2v }
                prompt: { type: string, example: "Drone over Tokyo at night" }
                duration: { type: string, example: "8s" }
                resolution: { type: string, example: "1080p" }
                aspect_ratio: { type: string, example: "16:9" }
                generate_audio: { type: boolean }
      responses:
        "202": { description: Job queued — returns jobId }
  /images/batch:
    post:
      operationId: compareImageModels
      summary: Submit one prompt across multiple image models (comparison battle).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [models, prompt]
              properties:
                models:
                  type: array
                  items: { type: string }
                  example: [flux_1_1_pro_ultra, ideogram_v3, imagen_4_ultra]
                prompt: { type: string }
                aspect_ratio: { type: string }
      responses:
        "202": { description: Jobs queued — returns jobs[] }
  /videos/batch:
    post:
      operationId: compareVideoModels
      summary: Submit one prompt across multiple video models (comparison battle).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [models, prompt]
              properties:
                models:
                  type: array
                  items: { type: string }
                  example: [veo3_t2v, kling_t2v, seedance_t2v]
                prompt: { type: string }
                aspect_ratio: { type: string }
      responses:
        "202": { description: Jobs queued — returns jobs[] }
  /jobs/{jobId}:
    get:
      operationId: getJob
      summary: Poll a generation job's status/result.
      parameters:
        - name: jobId
          in: path
          required: true
          schema: { type: string }
        - name: type
          in: query
          required: true
          schema: { type: string, enum: [image, video] }
      responses:
        "200": { description: "status: processing | complete | error" }
        "404": { description: Job not found }
  /healthz:
    get:
      operationId: health
      summary: Check whether the image and video upstreams are available.
      responses:
        "200": { description: ok }
        "503": { description: degraded }
