openapi: "3.0.0"
info:
  title: "API Documentation"
  version: "1.0"
  description: |
    Miraflow provides a suite of APIs for managing your avatars, video and media as well as
    generating new videos.

    These endpoints require authorization. Include your API key in the request by replacing
    API_KEY:
    ```
    curl -H "x-api-key: API_KEY" https://miraflow.ai/api/avatars
    ```

    **Usage Tracking:** All generation endpoints return a `usage` object in their success
    response containing your current credit balance, plan limits, and per-content-type usage.
    You can also call `GET /api/usage` at any time to check your usage without generating content.
tags:
  - name: Usage
    description: |
      Track your API credit usage and plan limits.

      All generation endpoints (video/create, image/generate, image/edit, image/inpaint,
      video/cinematic/create, mockup/generate, thumbnail/generate) include a `usage` object
      in their success responses, so you can track remaining credits after each request.

      You can also call `GET /api/usage` at any time to check your current usage without
      performing a generation.
  - name: Media Upload
    description: |
      Upload images and files for use with other API endpoints.
      
      **Direct Upload Workflow:**
      1. Call `/api/media/initialize` with file metadata
      2. Upload the file directly to the returned `uploadUrl` (S3 presigned URL)
      3. Call `/api/media/finalize` with the `mediaId`
      4. Use the `mediaId` in other API calls
  - name: Avatar Videos
    description: Create talking avatar videos with AI voices
  - name: Cinematic Videos
    description: |
      Text-to-video cinematic clips.

      **Quick start**

      1. **Create a job** — `POST /api/video/cinematic/create` with JSON body `name`, `prompt`, and optional settings
         (`aspectRatio`, `durationSeconds`, `resolution`, etc.).
         Optional images: upload via **Media Upload**, then pass `firstFrameImageMediaId` / `referenceImageMediaIds`.
      2. **Poll status** — `GET /api/video/{jobId}/status` until `terminal` is `true`.
      3. **Fetch the file** — on `inference_complete`, read `videoId`, then:
         - `GET /api/video/{videoId}` (JSON with `downloadUrl`), or
         - `GET /api/video/{videoId}/download` (raw MP4)

      **Example (replace API_KEY)**

      ```bash
      # 1) Create
      curl -sS -X POST "https://miraflow.ai/api/video/cinematic/create" \
        -H "Content-Type: application/json" \
        -H "x-api-key: YOUR_API_KEY" \
        -d '{"name":"My clip","prompt":"Slow dolly through empty cafe, morning light, cinematic, no text."}'

      # 2) Poll (use jobId from step 1)
      curl -sS "https://miraflow.ai/api/video/JOB_ID/status" -H "x-api-key: YOUR_API_KEY"

      # 3) When status is inference_complete, get URL (use videoId from status)
      curl -sS "https://miraflow.ai/api/video/VIDEO_ID" -H "x-api-key: YOUR_API_KEY"
      ```
  - name: Photo Avatars
    description: |
      Create photo-based avatars ("Design your avatar" and "From your selfie") without digital-twin training.
      Upload images and optional voice samples via Media Upload, then create jobs and poll `GET /api/avatar/jobs/{jobId}/status`.

      **Quick start**

      **Design your avatar**

      1. **Create job** — `POST /api/avatar/design/create` with `name`, `design`, and optional `audioMediaId`.
      2. **Poll** — `GET /api/avatar/jobs/{jobId}/status` until `status` is `variants_ready`.
         If the ML server is briefly busy you may see `inference_retrying` with `backendStatus: inference_error` — keep polling.
      3. **Finalize**
         - **Default (create both avatars):** `POST /api/avatar/design/confirm-both` with `{ "jobId": "..." }`
         - **Only one:** `POST /api/avatar/design/confirm` with `{ "jobId": "...", "variant": 1 | 2 }`

      **From your selfie (direct)**

      - `POST /api/avatar/from-selfie/create` with `name`, `photoMediaId`, optional `audioMediaId` (creates immediately).

      **From your selfie (with edit prompt)**

      1. **Create job** — `POST /api/avatar/from-selfie/enhance` with `name`, `prompt`, `photoMediaId`, optional `audioMediaId`.
      2. **Poll** — `GET /api/avatar/jobs/{jobId}/status` until `terminal` is `true`.

      **Example: design flow (replace API_KEY)**

      ```bash
      # 1) Start generation
      curl -sS -X POST "https://miraflow.ai/api/avatar/design/create" \
        -H "Content-Type: application/json" \
        -H "x-api-key: YOUR_API_KEY" \
        -d '{"name":"Host Ava","design":{"gender":"Female","age":"30s","ethnicity":"Any","outfit":"Business casual","background":"Studio","isPortrait":true}}'

      # 2) Poll until status is variants_ready
      curl -sS "https://miraflow.ai/api/avatar/jobs/JOB_ID/status" -H "x-api-key: YOUR_API_KEY"

      # 3) Create both avatars
      curl -sS -X POST "https://miraflow.ai/api/avatar/design/confirm-both" \
        -H "Content-Type: application/json" \
        -H "x-api-key: YOUR_API_KEY" \
        -d "{\"jobId\":\"JOB_ID\"}"
      ```
  - name: AI Images
    description: Generate and edit images with AI
  - name: AI Product Video Generator
    description: |
      Create product mockups and advertisement videos.
      
      **Workflow:**
      1. Generate or upload product/logo assets
      2. Create mockups by compositing logos onto products
      3. Generate 8-second advertisement videos from mockups
  - name: YouTube Thumbnail Maker
    description: |
      Generate eye-catching YouTube thumbnails with AI.
      
      **Basic Workflow (text prompt only):**
      1. Call `/api/thumbnail/generate` with a prompt
      2. Poll `/api/thumbnail/{jobId}` until complete
      3. Download the generated thumbnail
      
      **With Reference Images (e.g., from YouTube video):**
      1. Upload reference image using `/api/media/initialize` + `/api/media/finalize`
      2. Call `/api/thumbnail/generate` with `referenceImageMediaIds`
      3. Poll `/api/thumbnail/{jobId}` until complete
      
      **Using YouTube Video Thumbnail:**
      1. Extract video ID from YouTube URL (e.g., `v=abc123`)
      2. Fetch thumbnail from `https://img.youtube.com/vi/{videoId}/maxresdefault.jpg`
      3. Upload as reference image using media endpoints
      4. Generate new thumbnail using the reference
paths:
  # ==================== Usage API ====================
  /api/usage:
    get:
      tags:
        - Usage
      summary: "Get current usage and credit balance"
      description: |
        Returns your current plan, credit balance, and per-content-type usage for the current billing period.
        Use this to check your remaining credits before making generation requests.

        **Example:**
        ```bash
        curl -sS "https://miraflow.ai/api/usage" -H "x-api-key: YOUR_API_KEY"
        ```
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: "Current usage and credit information"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageMetadata"
              examples:
                paidPlan:
                  summary: "Paid plan with usage"
                  value:
                    plan: "Pro"
                    credits_used: 45
                    credits_limit: 570
                    credits_remaining: 525
                    usage:
                      ai_actor_videos:
                        used: 12
                        limit: "unlimited"
                      ai_images:
                        used: 30
                        limit: 300
                      cinematic_videos:
                        used: 5
                        limit: 90
                    usage_reset_date: "2025-02-15"
                freePlan:
                  summary: "Free plan"
                  value:
                    plan: "Free"
                    credits_used: 3
                    credits_limit: 5
                    credits_remaining: 2
                    usage:
                      ai_actor_videos:
                        used: 1
                        limit: 1
                      ai_images:
                        used: 1
                        limit: 1
                      cinematic_videos:
                        used: 1
                        limit: 1
                    usage_reset_date: "2025-02-15"
        "401":
          description: "Invalid or missing API key"
        "404":
          description: "User not found"
  # ==================== Media Upload APIs ====================
  /api/media/initialize:
    post:
      tags:
        - Media Upload
      summary: "Initialize media upload"
      description: |
        Initializes a media upload and returns a presigned URL for direct upload to S3.
        
        **Workflow:**
        1. Call this endpoint with file metadata (name, type, size)
        2. Upload the file directly to the returned `uploadUrl` using a PUT request
        3. Call `/api/media/finalize` with the `mediaId` to complete the upload
        
        **Example upload to S3:**
        ```bash
        curl -X PUT -H "Content-Type: image/png" --data-binary @image.png "$uploadUrl"
        ```
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - fileName
                - contentType
                - fileSize
              properties:
                fileName:
                  type: string
                  example: "my-image.png"
                  description: "Name of the file"
                contentType:
                  type: string
                  example: "image/png"
                  description: "MIME type of the file"
                fileSize:
                  type: integer
                  example: 1024000
                  description: "Size of the file in bytes"
      responses:
        "200":
          description: "Upload initialized successfully"
          content:
            application/json:
              schema:
                type: object
                properties:
                  mediaId:
                    type: string
                    example: "cm42ab3ou0008aggajj0jy5e2"
                    description: "ID to use for finalization and in other API calls"
                  uploadUrl:
                    type: string
                    example: "https://s3.amazonaws.com/bucket/path?signature=..."
                    description: "Presigned URL for direct file upload"
        "400":
          description: "Invalid request body"
        "401":
          description: "Invalid or missing API key"
  /api/media/finalize:
    post:
      tags:
        - Media Upload
      summary: "Finalize media upload"
      description: |
        Finalizes a media upload after the file has been uploaded to S3.
        Call this after successfully uploading the file to the `uploadUrl` from `/api/media/initialize`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - mediaId
              properties:
                mediaId:
                  type: string
                  example: "cm42ab3ou0008aggajj0jy5e2"
                  description: "The media ID from the initialize response"
      responses:
        "200":
          description: "Upload finalized successfully"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "400":
          description: "Invalid request body"
        "401":
          description: "Invalid or missing API key"
  # ==================== Avatar Video APIs ====================
  /api/avatars:
    get:
      tags:
        - Avatar Videos
      summary: "List avatars"
      description: |
        Lists the available avatars. These include stock avatars as well as avatars created by the account associated with the supplied API key.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: "The list of avatars."
          content:
            application/json:
              schema:
                - type: object
                  properties:
                    avatars:
                      type: array
                      items:
                        $ref: "#/components/schemas/Avatar"
              examples:
                result:
                  value:
                    avatars:
                      - id: "cm2yaz9j10008l0qg9npyjgs3"
                        name: "Aerin"
                        createdAt: "2024-11-01T05:37:46.194Z"
                        voiceId: "cm1lgilny0005m2lnvv3vgtgv"
                        isPhoto: true
  /api/avatar/design/create:
    post:
      tags:
        - Photo Avatars
      summary: "Start design-your-avatar job"
      description: |
        Starts generation of two portrait variants from structured design fields (in-app "Design your avatar" flow).
        Poll `GET /api/avatar/jobs/{jobId}/status` until `status` is `variants_ready`, then call `POST /api/avatar/design/confirm-both`
        to create **two** avatars (recommended for API), or `POST /api/avatar/design/confirm` with `variant` 1 or 2 for a single avatar.
        Unpaid accounts consume one general (monthly) credit when the job is created, same as the web app.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePhotoAvatarDesignJob"
      responses:
        "200":
          description: "Job created, or JSON failure"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
                      details: {}
        "400":
          description: "Invalid body, media error, or not enough credits"
        "401":
          description: "Authentication required"
  /api/avatar/design/confirm:
    post:
      tags:
        - Photo Avatars
      summary: "Confirm design-your-avatar variant"
      description: |
        After status `variants_ready`, select variant `1` or `2` (matches `variantPreviewUrls` from the status endpoint).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConfirmPhotoAvatarJob"
      responses:
        "200":
          description: "Avatar created, or JSON failure"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      avatarId:
                        type: string
                      mediaId:
                        type: string
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
        "400":
          description: "Job not ready, already confirmed, or invalid variant"
        "401":
          description: "Authentication required"
  /api/avatar/design/confirm-both:
    post:
      tags:
        - Photo Avatars
      summary: "Confirm both design-your-avatar variants"
      description: |
        After `variants_ready`, creates **two** photo avatars from variant 1 and variant 2 (`{name} · 1` and `{name} · 2`).
        Mutually exclusive with `POST /api/avatar/design/confirm` for the same job.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConfirmPhotoAvatarBothJob"
      responses:
        "200":
          description: "Two avatars created, or JSON failure"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      avatarIds:
                        type: array
                        minItems: 2
                        maxItems: 2
                        items:
                          type: string
                      mediaIds:
                        type: array
                        minItems: 2
                        maxItems: 2
                        items:
                          type: string
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
        "400":
          description: "Job not ready, already finalized, or download/upload error"
        "401":
          description: "Authentication required"
  /api/avatar/from-selfie/create:
    post:
      tags:
        - Photo Avatars
      summary: "Create avatar from selfie (direct)"
      description: |
        Creates a photo avatar immediately from an uploaded image (`photoMediaId`), with optional `audioMediaId` for a custom voice
        sample. No polling. This matches the in-app "From your selfie" flow when you do not use an edit prompt.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSelfieAvatarDirect"
      responses:
        "200":
          description: "Avatar created, or JSON failure"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      avatarId:
                        type: string
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
        "400":
          description: "Invalid body or media type"
        "401":
          description: "Authentication required"
  /api/avatar/from-selfie/enhance:
    post:
      tags:
        - Photo Avatars
      summary: "Start from-selfie enhancement job"
      description: |
        Starts ML enhancement from `photoMediaId` and `prompt`; when inference completes, a photo avatar is created automatically.
        Poll `GET /api/avatar/jobs/{jobId}/status` until `terminal` is true. Unpaid accounts consume one general credit at job creation.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSelfieAvatarEnhanceJob"
      responses:
        "200":
          description: "Job created, or JSON failure"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
        "400":
          description: "Invalid body, media error, or not enough credits"
        "401":
          description: "Authentication required"
  /api/avatar/jobs/{jobId}/status:
    get:
      tags:
        - Photo Avatars
      summary: "Photo avatar job status"
      description: |
        Poll **design-your-avatar** jobs and **from-selfie (with prompt)** jobs only.

        **Statuses:** `queued`, `inference_started`, `inference_working`, `inference_retrying` (transient ML busy/error;
        `backendStatus` is `inference_error` — keep polling), `variants_ready` (design only; includes
        `variantPreviewUrls`), `inference_complete`, `inference_failed`.

        Stop polling when `terminal` is `true`. On `inference_complete`, `avatarId` is the first finalized avatar and `avatarIds`
        lists all of them (one for single confirm, two for `confirm-both`).
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: jobId
          required: true
          schema:
            type: string
          description: "Job id from design/create or from-selfie/enhance"
      responses:
        "200":
          description: "Current job state"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/PhotoAvatarJobStatusSuccess"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
        "400":
          description: "Job is not a photo-avatar creation job"
        "401":
          description: "Authentication required"
        "403":
          description: "Forbidden"
        "404":
          description: "Job not found"
  /api/voices:
    get:
      tags:
        - Avatar Videos
      summary: "List voices"
      description: |
        Lists the available voices. These include stock voices as well as voices created by the account associated with the supplied API key.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: "The list of voices."
          content:
            application/json:
              schema:
                - type: object
                  properties:
                    voices:
                      type: array
                      items:
                        $ref: "#/components/schemas/Voice"
              examples:
                result:
                  value:
                    avatars:
                      - id: "cm5n372yq0003vow86ukx8zfe"
                        name: "Belinda - Curious and Soft"
                        createdAt: "2025-01-07T23:13:33.103Z"
  /api/videos:
    get:
      tags:
        - Avatar Videos
      summary: "List videos"
      description: |
        Lists the videos that have completed generation.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: "The list of videos."
          content:
            application/json:
              schema:
                - type: object
                  properties:
                    videos:
                      type: array
                      items:
                        $ref: "#/components/schemas/Video"
              examples:
                result:
                  value:
                    videos:
                      - id: "cm5n3bnpa000avow8t6p5yc4l"
                        name: "Test video"
                        sourceAvatarId: "cm2yaz9j10008l0qg9npyjgs3"
                        createdAt: "2025-01-07T23:17:06.601Z"
  /api/video/{id}:
    get:
      tags:
        - Avatar Videos
      summary: "Fetch video metadata"
      description: |
        Fetches metadata for a single completed video.
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
          description: "The ID of the desired video"
      responses:
        "200":
          description: "The video metadata."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VideoInfo"
              examples:
                result:
                  value:
                    id: "cm5n3bnpa000avow8t6p5yc4l"
                    name: "Test video"
                    sourceAvatarId: "cm2yaz9j10008l0qg9npyjgs3"
                    createdAt: "2025-01-07T23:17:06.601Z"
                    downloadUrl: "https://miraflow-media.s3.us-west-2.amazonaws.com/cm5n3bnpa000avow8t6p5yc4l.mp4"
        "400":
          description: "Missing required parameter: id"
        "404":
          description: "No video found with the provided ID"
  /api/video/{id}/download:
    get:
      tags:
        - Avatar Videos
      summary: "Download video"
      description: |
        Downloads a single completed video.
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
          description: "The ID of the desired video"
      responses:
        "200":
          description: "The video file."
          content:
            video/mp4:
              schema:
                type: string
                format: binary
        "400":
          description: "Missing required parameter: id"
        "404":
          description: "No video found with the provided ID"
  /api/video/{id}/status:
    get:
      tags:
        - Avatar Videos
      summary: "Check video generation status"
      description: |
        Checks the status of a **video generation job** (avatar/lip-sync **or** cinematic). Use the `jobId` returned from
        `POST /api/video/create` or `POST /api/video/cinematic/create`.

        The status will be one of:
        - "inference_started": Job has been queued (non-terminal; keep polling)
        - "inference_retrying": Transient/recoverable state while the inference service retries (non-terminal; keep polling). The field `backendStatus` is `inference_error` when this occurs.
        - "inference_working": Job is in progress (non-terminal; keep polling)
        - "inference_complete": Video generation is complete (terminal)
        - "inference_failed": Job failed (terminal)

        Every successful response includes `terminal` (boolean). Stop polling only when `terminal` is true.

        Note: The ID parameter should be the jobId that was returned from the video creation endpoint.
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
          description: "The Job ID returned from the video creation endpoint"
      responses:
        "200":
          description: "The current status of the video generation job."
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      status:
                        type: string
                        enum:
                          [
                            inference_started,
                            inference_retrying,
                            inference_working,
                            inference_complete,
                            inference_failed,
                          ]
                      terminal:
                        type: boolean
                        description: "If true, the job is finished (success or failure); stop polling."
                      progress:
                        type: string
                        description: "Progress percentage when status is inference_working"
                      reason:
                        type: string
                        description: "Optional message from the inference service"
                      backendStatus:
                        type: string
                        enum: [inference_error]
                        description: "Present when `status` is `inference_retrying`; raw inference status was `inference_error`."
                      videoId:
                        type: string
                        description: "Present when generation completed and the video record is available"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
              examples:
                success:
                  value:
                    result: "success"
                    status: "inference_working"
                    terminal: false
                    progress: "45%"
                retrying:
                  value:
                    result: "success"
                    status: "inference_retrying"
                    terminal: false
                    backendStatus: "inference_error"
                    reason: "Transient inference issue; retrying"
                failure:
                  value:
                    result: "failure"
                    error: "Video job not found"
        "400":
          description: "Missing required parameter: id"
        "401":
          description: "Invalid or missing API key"
        "500":
          description: "Internal server error"
  /api/video/create:
    post:
      tags:
        - Avatar Videos
      summary: "Create a new video"
      description: |
        Starts a new video creation job. The specified avatar will be used, and either a speech can be created using a specified voice and script, or an uploaded audio track can be used.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: "#/components/schemas/CreateVideoWithVoice"
                - $ref: "#/components/schemas/CreateVideoWithSpeech"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/CreateVideoWithSpeechFile"
            examples:
              speechFileExample:
                summary: "Upload speech file example"
                value:
                  avatarId: "cm2yaz9j10008l0qg9npyjgs3"
                  name: "My new video"
                  speechFile: "@path-to-speech-file.mp3"
                  im2vid_full: true
      responses:
        "200":
          description: "Video job started, or failure. Toggle the dropdown below to see examples of both types of responses."
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                        example: "cm42ab3ou0008aggajj0jy5e2"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
              examples:
                success:
                  value:
                    result: "success"
                    jobId: "cm42ab3ou0008aggajj0jy5e2"
                    usage:
                      plan: "Pro"
                      credits_used: 46
                      credits_limit: 570
                      credits_remaining: 524
                      usage:
                        ai_actor_videos: { used: 13, limit: "unlimited" }
                        ai_images: { used: 30, limit: 300 }
                        cinematic_videos: { used: 5, limit: 90 }
                      usage_reset_date: "2025-02-15"
                failure:
                  value:
                    result: "failure"
                    error: "Avatar not found"
        "400":
          description: "Invalid request body, not enough credits, or other error"
  /api/video/cinematic/create:
    post:
      tags:
        - Cinematic Videos
      summary: "Create a cinematic text-to-video job"
      description: |
        Starts a **cinematic** text-to-video generation job, subject to your plan's cinematic video quota
        (same limits as the in-app cinematic creator).

        **Workflow**
        1. Optionally upload reference or first-frame images via `/api/media/initialize` and `/api/media/finalize`, then pass their `mediaId` values here.
        2. Call this endpoint with `name`, `prompt`, and optional generation settings.
        3. Poll `GET /api/video/{jobId}/status` until `terminal` is true and `status` is `inference_complete`.
        4. Use `videoId` from the status response with `GET /api/video/{videoId}` (JSON with `downloadUrl`) or `GET /api/video/{videoId}/download` (raw MP4).

        Authentication: `x-api-key` or signed-in session (same as other video APIs).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCinematicVideo"
      responses:
        "200":
          description: "Job created or validation error returned as JSON failure."
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
              examples:
                success:
                  value:
                    result: "success"
                    jobId: "cm42ab3ou0008aggajj0jy5e2"
                failure:
                  value:
                    result: "failure"
                    error: "Monthly limit reached for cinematic videos"
        "400":
          description: "Invalid body, media permission error, or quota exceeded"
        "401":
          description: "Authentication required"
  /api/image/generate:
    post:
      tags:
        - AI Images
      summary: "Create an image generation job"
      description: |
        Starts a new text-to-image generation job. Provide a text prompt describing the image you want to generate.

        This endpoint generates images from text descriptions without requiring any reference images.

        **Workflow:**
        1. Call this endpoint to create a job. You'll receive a `jobId` in the response.
        2. Use the `jobId` with `GET /api/image/{jobId}` to check the job status.
        3. Poll the status endpoint until the job is complete (status: "inference_complete").
        4. When complete, the response will include a `downloadUrl` field containing the image URL.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateImageGeneration"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/CreateImageGeneration"
      responses:
        "200":
          description: "Image generation job started, or failure."
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                        example: "cm42ab3ou0008aggajj0jy5e2"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
              examples:
                success:
                  value:
                    result: "success"
                    jobId: "cm42ab3ou0008aggajj0jy5e2"
                failure:
                  value:
                    result: "failure"
                    error: "Not enough credits"
        "400":
          description: "Invalid request body, not enough credits, or other error"
        "401":
          description: "Invalid or missing API key"
  /api/image/edit:
    post:
      tags:
        - AI Images
      summary: "Create an image editing job"
      description: |
        Starts a new image-to-image editing job. Upload one or more reference images and provide a prompt
        describing how you want to edit them. The AI will transform the images based on your prompt.

        You can either upload files directly (multipart/form-data) or provide media IDs of previously
        uploaded images (application/json). You can provide a single image or multiple images.

        **Workflow:**
        1. Call this endpoint to create a job. You'll receive a `jobId` in the response.
        2. Use the `jobId` with `GET /api/image/{jobId}` to check the job status.
        3. Poll the status endpoint until the job is complete (status: "inference_complete").
        4. When complete, the response will include a `downloadUrl` field containing the image URL.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: "#/components/schemas/CreateImageEditWithMediaId"
                - $ref: "#/components/schemas/CreateImageEditWithMediaIds"
          multipart/form-data:
            schema:
              oneOf:
                - $ref: "#/components/schemas/CreateImageEditWithFile"
                - $ref: "#/components/schemas/CreateImageEditWithFiles"
            examples:
              singleFileExample:
                summary: "Upload single file example"
                value:
                  referenceImage: "@path-to-image.jpg"
                  prompt: "Turn this photo into a professional business headshot"
                  name: "My image editing job"
                  aspectRatio: "1:1"
              multipleFilesExample:
                summary: "Upload multiple files example"
                value:
                  referenceImages:
                    ["@path-to-image1.jpg", "@path-to-image2.jpg"]
                  prompt: "Transform these images with cinematic lighting"
                  name: "My batch editing job"
      responses:
        "200":
          description: "Image editing job started, or failure."
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                        example: "cm42ab3ou0008aggajj0jy5e2"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
              examples:
                success:
                  value:
                    result: "success"
                    jobId: "cm42ab3ou0008aggajj0jy5e2"
                failure:
                  value:
                    result: "failure"
                    error: "Not enough credits"
        "400":
          description: "Invalid request body, not enough credits, or other error"
        "401":
          description: "Invalid or missing API key"
        "404":
          description: "Reference image not found or you don't have permission to use it"
  /api/image/inpaint:
    post:
      tags:
        - AI Images
      summary: "Create an inpainting job"
      description: |
        Starts a new image inpainting job. Upload an original image and a mask image (black and white),
        then specify what you want to replace in the masked areas. The mask should have white areas
        indicating what to replace and black areas indicating what to keep.

        You can either upload files directly (multipart/form-data) or provide media IDs of previously
        uploaded images (application/json).

        To create a mask image, you can use the frontend interface at `/create-ai-image`:
        1. Upload your original image
        2. Switch to Inpaint mode
        3. Draw mask areas with the blue brush
        4. Click "Download Mask" to download the black/white mask image
        5. Use the downloaded mask image in your API request

        **Workflow:**
        1. Call this endpoint to create a job. You'll receive a `jobId` in the response.
        2. Use the `jobId` with `GET /api/image/{jobId}` to check the job status.
        3. Poll the status endpoint until the job is complete (status: "inference_complete").
        4. When complete, the response will include a `downloadUrl` field containing the image URL.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateInpaintWithMediaIds"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/CreateInpaintWithFiles"
            examples:
              fileUploadExample:
                summary: "Upload files example"
                value:
                  originalImage: "@path-to-original-image.jpg"
                  maskImage: "@path-to-mask-image.png"
                  prompt: "Change to blue"
                  name: "My inpainting job"
                  aspectRatio: "1:1"
      responses:
        "200":
          description: "Inpainting job started, or failure."
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                        example: "cm42ab3ou0008aggajj0jy5e2"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
              examples:
                success:
                  value:
                    result: "success"
                    jobId: "cm42ab3ou0008aggajj0jy5e2"
                failure:
                  value:
                    result: "failure"
                    error: "Not enough credits"
        "400":
          description: "Invalid request body, not enough credits, or other error"
        "401":
          description: "Invalid or missing API key"
  /api/image/{jobId}:
    get:
      tags:
        - AI Images
      summary: "Get job status or result"
      description: |
        Gets the status or result of any job. This is the general entry point for job status management.

        When the job is in progress or failed, returns status information:
        - "inference_started": Job has been queued
        - "inference_working": Job is in progress (may include progress percentage)
        - "inference_failed": Job failed

        When the job is completed:
        - For image jobs: Returns image metadata and download URL
        - For other job types: Returns completion status

        Note: The jobId parameter should be the jobId that was returned from the job creation endpoint.
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: jobId
          schema:
            type: string
          required: true
          description: "The ID of the job"
      responses:
        "200":
          description: |
            Returns status information when the job is in progress or failed, or returns image metadata and download URL when the job is completed.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ImageInfo"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      status:
                        type: string
                        enum:
                          [
                            inference_started,
                            inference_working,
                            inference_failed,
                          ]
                      progress:
                        type: string
                        description: "Progress percentage when status is inference_working"
                        example: "45%"
              examples:
                completed:
                  summary: "Job completed - returns image info"
                  value:
                    id: "cm42ab3ou0008aggajj0jy5e2"
                    name: "My inpainting job"
                    prompt: "Change to blue"
                    createdAt: "2025-01-07T23:17:06.601Z"
                    downloadUrl: "https://miraflow-media.s3.us-west-2.amazonaws.com/cm42ab3ou0008aggajj0jy5e2.png"
                    mediaId: "cm42ab3ou0008aggajj0jy5e3"
                inProgress:
                  summary: "Job in progress - returns status"
                  value:
                    result: "success"
                    status: "inference_working"
                    progress: "45%"
                failed:
                  summary: "Job failed - returns status"
                  value:
                    result: "success"
                    status: "inference_failed"
        "400":
          description: "Missing job ID, invalid job type, or other validation error"
        "401":
          description: "Invalid or missing API key"
        "403":
          description: "Not authorized to access this job"
        "404":
          description: "Job not found or image not available"
        "500":
          description: "Internal server error"
  # ==================== AI Product Video Generator APIs ====================
  /api/mockup/generate:
    post:
      tags:
        - AI Product Video Generator
      summary: "Generate a product mockup"
      description: |
        Generates a product mockup by compositing logos onto a product image.
        
        **Workflow:**
        1. First upload your product and logo images using the media upload endpoints
        2. Call this endpoint with the media IDs and placement instructions
        3. The mockup is generated synchronously and returned with an image URL
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateMockup"
      responses:
        "200":
          description: "Mockup generated successfully"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                        example: "cm42ab3ou0008aggajj0jy5e2"
                      imageUrl:
                        type: string
                        example: "/api/media/cm42ab3ou0008aggajj0jy5e3/view"
                      mediaId:
                        type: string
                        example: "cm42ab3ou0008aggajj0jy5e3"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
              examples:
                success:
                  value:
                    result: "success"
                    jobId: "cm42ab3ou0008aggajj0jy5e2"
                    imageUrl: "/api/media/cm42ab3ou0008aggajj0jy5e3/view"
                    mediaId: "cm42ab3ou0008aggajj0jy5e3"
        "400":
          description: "Invalid request body or not enough credits"
        "401":
          description: "Invalid or missing API key"
        "404":
          description: "Product media not found"
  /api/mockup/video:
    post:
      tags:
        - AI Product Video Generator
      summary: "Generate video from mockup"
      description: |
        Creates an 8-second product advertisement video from a saved mockup.
        
        **Animation Styles:**
        - `luxury_macro`: Luxury macro studio film
        - `neo_noir_night`: Neo-noir night street set
        - `warm_lifestyle_sunset`: Golden-hour lifestyle cinematic
        - `futuristic_clean_studio`: Ultra-clean futuristic studio
        - `cyberpunk_showcase`: Cyberpunk display pedestal
        - `film_35mm_editorial`: 35mm film editorial look
        - `high_fashion_runway`: High-fashion spotlight stage
        - `nature_premium`: Premium nature set
        - `custom`: Custom prompt only
        
        **Workflow:**
        1. Call this endpoint with a mockup ID and animation style
        2. A job ID is returned immediately
        3. Poll `/api/mockup/{jobId}/status` to check progress
        4. When complete, get the video URL from `/api/mockup/videos`
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateMockupVideo"
      responses:
        "200":
          description: "Video generation job started"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                        example: "cm42ab3ou0008aggajj0jy5e2"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
        "400":
          description: "Invalid request body"
        "401":
          description: "Invalid or missing API key"
        "404":
          description: "Mockup not found"
  /api/mockup/asset/generate:
    post:
      tags:
        - AI Product Video Generator
      summary: "Generate logo or product asset"
      description: |
        Generates a new logo or product image from a text prompt.
        
        **Workflow:**
        1. Call this endpoint to start generation
        2. A job ID is returned immediately
        3. Poll `/api/mockup/{jobId}/status` to check progress
        4. When complete, retrieve the asset from `/api/mockup/assets`
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAsset"
      responses:
        "200":
          description: "Asset generation job started"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                        example: "cm42ab3ou0008aggajj0jy5e2"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
        "400":
          description: "Invalid request body"
        "401":
          description: "Invalid or missing API key"
  /api/mockup/asset/edit:
    post:
      tags:
        - AI Product Video Generator
      summary: "Edit existing asset"
      description: |
        Edits an existing image using AI based on a text prompt.
        
        You can provide the image in three ways:
        - `imageMediaId`: Media ID of a previously uploaded image
        - `imageBase64`: Base64-encoded image data
        - `image`: File upload (multipart/form-data)
        
        **Workflow:**
        1. Call this endpoint to start editing
        2. A job ID is returned immediately
        3. Poll `/api/mockup/{jobId}/status` to check progress
        4. When complete, retrieve the asset from `/api/mockup/assets`
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EditAsset"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/EditAssetWithFile"
      responses:
        "200":
          description: "Asset edit job started"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                        example: "cm42ab3ou0008aggajj0jy5e2"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
        "400":
          description: "Invalid request body"
        "401":
          description: "Invalid or missing API key"
        "404":
          description: "Image media not found"
  /api/mockups:
    get:
      tags:
        - AI Product Video Generator
      summary: "List saved mockups"
      description: |
        Lists all saved product mockups for the authenticated user.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: "The list of mockups"
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    enum: [success]
                  mockups:
                    type: array
                    items:
                      $ref: "#/components/schemas/MockupItem"
              examples:
                result:
                  value:
                    result: "success"
                    mockups:
                      - id: "cm42ab3ou0008aggajj0jy5e2"
                        imageUrl: "/api/media/cm42ab3ou0008aggajj0jy5e3/view"
                        prompt: "Place logo on t-shirt"
                        createdAt: "2025-01-07T23:17:06.601Z"
        "401":
          description: "Invalid or missing API key"
  /api/mockup/assets:
    get:
      tags:
        - AI Product Video Generator
      summary: "List saved assets"
      description: |
        Lists all saved logo and product assets for the authenticated user.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: "The list of assets"
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    enum: [success]
                  assets:
                    type: array
                    items:
                      $ref: "#/components/schemas/AssetItem"
        "401":
          description: "Invalid or missing API key"
  /api/mockup/videos:
    get:
      tags:
        - AI Product Video Generator
      summary: "List mockup videos"
      description: |
        Lists all generated product advertisement videos for the authenticated user.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: "The list of videos"
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    enum: [success]
                  videos:
                    type: array
                    items:
                      $ref: "#/components/schemas/MockupVideoItem"
        "401":
          description: "Invalid or missing API key"
  /api/mockup/{id}/status:
    get:
      tags:
        - AI Product Video Generator
      summary: "Check mockup job status"
      description: |
        Checks the status of a mockup-related job (mockup generation, asset generation, or video generation).
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
          description: "The job ID"
      responses:
        "200":
          description: "The job status"
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    enum: [success]
                  status:
                    type: string
                    enum: [waiting, started, completed, failed]
                  progress:
                    type: number
                    example: 45
                  imageUrl:
                    type: string
                    description: "Available when status is completed"
                  mediaId:
                    type: string
                    description: "Available when status is completed"
                  reason:
                    type: string
                    description: "Error reason when status is failed"
        "401":
          description: "Invalid or missing API key"
        "404":
          description: "Job not found"
  # ==================== YouTube Thumbnail Maker APIs ====================
  /api/thumbnail/generate:
    post:
      tags:
        - YouTube Thumbnail Maker
      summary: "Generate a YouTube thumbnail"
      description: |
        Generates a YouTube thumbnail from a text prompt, optionally using reference images.
        
        **Basic Workflow:**
        1. Call this endpoint with a text prompt
        2. A job ID is returned immediately
        3. Poll `/api/thumbnail/{jobId}` to check status
        4. When complete, the response includes the download URL
        
        **With Reference Images:**
        1. Upload reference images using `/api/media/initialize` and `/api/media/finalize`
        2. Include the `mediaId` values in `referenceImageMediaIds` array
        3. The AI will use these images as style/content references
        
        **Using YouTube Video Thumbnails as Reference:**
        1. Extract video ID from YouTube URL (e.g., `watch?v=abc123` → `abc123`)
        2. Download thumbnail: `https://img.youtube.com/vi/{videoId}/maxresdefault.jpg`
        3. Upload via media endpoints to get a `mediaId`
        4. Pass `mediaId` in `referenceImageMediaIds` to generate a similar thumbnail
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateThumbnail"
      responses:
        "200":
          description: "Thumbnail generation job started"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      jobId:
                        type: string
                        example: "cm42ab3ou0008aggajj0jy5e2"
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [failure]
                      error:
                        type: string
        "400":
          description: "Invalid request body or not enough credits"
        "401":
          description: "Invalid or missing API key"
  /api/thumbnails:
    get:
      tags:
        - YouTube Thumbnail Maker
      summary: "List generated thumbnails"
      description: |
        Lists all generated YouTube thumbnails for the authenticated user.
      security:
        - ApiKeyAuth: []
      parameters:
        - in: query
          name: page
          schema:
            type: integer
            default: 1
          description: "Page number for pagination"
      responses:
        "200":
          description: "The list of thumbnails"
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    enum: [success]
                  thumbnails:
                    type: array
                    items:
                      $ref: "#/components/schemas/ThumbnailItem"
                  page:
                    type: integer
                    example: 1
                  totalPages:
                    type: integer
                    example: 5
                  totalCount:
                    type: integer
                    example: 100
        "401":
          description: "Invalid or missing API key"
  /api/thumbnail/{id}:
    get:
      tags:
        - YouTube Thumbnail Maker
      summary: "Get thumbnail status or details"
      description: |
        Gets the status of a thumbnail generation job, or the thumbnail details if completed.
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
          description: "The job ID"
      responses:
        "200":
          description: "The thumbnail status or details"
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      status:
                        type: string
                        enum: [waiting, started]
                      progress:
                        type: number
                        example: 45
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      status:
                        type: string
                        enum: [completed]
                      id:
                        type: string
                      name:
                        type: string
                      prompt:
                        type: string
                      imageUrl:
                        type: string
                      downloadUrl:
                        type: string
                      mediaId:
                        type: string
                      aspectRatio:
                        type: string
                      createdAt:
                        type: string
                  - type: object
                    properties:
                      result:
                        type: string
                        enum: [success]
                      status:
                        type: string
                        enum: [failed]
                      reason:
                        type: string
              examples:
                inProgress:
                  value:
                    result: "success"
                    status: "started"
                    progress: 45
                completed:
                  value:
                    result: "success"
                    status: "completed"
                    id: "cm42ab3ou0008aggajj0jy5e2"
                    name: "Gaming Thumbnail"
                    prompt: "Epic gaming moment"
                    imageUrl: "/api/media/cm42ab3ou0008aggajj0jy5e3/view"
                    downloadUrl: "/api/media/cm42ab3ou0008aggajj0jy5e3/view"
                    mediaId: "cm42ab3ou0008aggajj0jy5e3"
                    aspectRatio: "16:9"
                    createdAt: "2025-01-07T23:17:06.601Z"
        "401":
          description: "Invalid or missing API key"
        "404":
          description: "Thumbnail not found"
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
  schemas:
    UsageMetadata:
      type: object
      description: "Credit usage and plan information. Included in all generation API success responses."
      properties:
        plan:
          type: string
          example: "Pro"
          description: "Current subscription plan name (Free, Essential, Pro, Team, or Enterprise)"
        credits_used:
          type: integer
          example: 45
          description: "Credits consumed in the current billing period"
        credits_limit:
          oneOf:
            - type: integer
            - type: string
              enum: ["unlimited"]
          example: 570
          description: "Total credits available per billing period (number or \"unlimited\" for Enterprise)"
        credits_remaining:
          oneOf:
            - type: integer
            - type: string
              enum: ["unlimited"]
          example: 525
          description: "Credits remaining in the current billing period"
        usage:
          type: object
          properties:
            ai_actor_videos:
              type: object
              properties:
                used:
                  type: integer
                  example: 12
                limit:
                  oneOf:
                    - type: integer
                    - type: string
                      enum: ["unlimited"]
                  example: "unlimited"
            ai_images:
              type: object
              properties:
                used:
                  type: integer
                  example: 30
                limit:
                  oneOf:
                    - type: integer
                    - type: string
                      enum: ["unlimited"]
                  example: 300
            cinematic_videos:
              type: object
              properties:
                used:
                  type: integer
                  example: 5
                limit:
                  oneOf:
                    - type: integer
                    - type: string
                      enum: ["unlimited"]
                  example: 90
        usage_reset_date:
          type: string
          format: date
          nullable: true
          example: "2025-02-15"
          description: "When the current billing period started. Usage resets ~30 days after this date."
    Avatar:
      type: object
      properties:
        id:
          type: string
          example: "cm2yaz9j10008l0qg9npyjgs3"
        name:
          type: string
          example: "Aerin"
        createdAt:
          type: string
          format: date
          example: "2024-11-01T05:37:46.194Z"
    PhotoAvatarDesignFields:
      type: object
      required:
        - gender
        - age
        - ethnicity
        - outfit
        - background
        - isPortrait
      properties:
        gender:
          type: string
          example: "Female"
        age:
          type: string
          example: "30s"
        ethnicity:
          type: string
        outfit:
          type: string
        background:
          type: string
        moreDetails:
          type: string
        companyName:
          type: string
        isPortrait:
          type: boolean
    CreatePhotoAvatarDesignJob:
      type: object
      required:
        - name
        - design
      properties:
        name:
          type: string
        design:
          $ref: "#/components/schemas/PhotoAvatarDesignFields"
        audioMediaId:
          type: string
          description: "Optional uploaded audio sample for custom voice (Media Upload `mediaId`)"
    ConfirmPhotoAvatarJob:
      type: object
      required:
        - jobId
        - variant
      properties:
        jobId:
          type: string
        variant:
          type: integer
          enum: [1, 2]
    ConfirmPhotoAvatarBothJob:
      type: object
      required:
        - jobId
      properties:
        jobId:
          type: string
    CreateSelfieAvatarDirect:
      type: object
      required:
        - name
        - photoMediaId
      properties:
        name:
          type: string
        photoMediaId:
          type: string
        audioMediaId:
          type: string
    CreateSelfieAvatarEnhanceJob:
      type: object
      required:
        - name
        - prompt
        - photoMediaId
      properties:
        name:
          type: string
        prompt:
          type: string
        photoMediaId:
          type: string
        audioMediaId:
          type: string
    PhotoAvatarJobStatusSuccess:
      type: object
      required:
        - result
        - jobId
        - kind
        - status
        - terminal
      properties:
        result:
          type: string
          enum: [success]
        jobId:
          type: string
        kind:
          type: string
          enum: [design_avatar, selfie_enhancement]
        status:
          type: string
          enum:
            - queued
            - inference_started
            - inference_working
            - inference_retrying
            - variants_ready
            - inference_complete
            - inference_failed
        terminal:
          type: boolean
        progress:
          type: integer
        avatarId:
          type: string
        avatarIds:
          type: array
          items:
            type: string
          description: "All avatar ids from this job after finalize (length 1 or 2)"
        variantPreviewUrls:
          type: object
          properties:
            variant1:
              type: string
              format: uri
            variant2:
              type: string
              format: uri
        reason:
          type: string
        backendStatus:
          type: string
          enum: [inference_error]
          description: "Present when `status` is `inference_retrying`."
    Voice:
      type: object
      properties:
        id:
          type: string
          example: "cm5n372yq0003vow86ukx8zfe"
        name:
          type: string
          example: "Belinda - Curious and Soft"
        createdAt:
          type: string
          format: date
          example: "2025-01-07T23:13:33.103Z"
    Video:
      type: object
      properties:
        id:
          type: string
          example: "cm5n3bnpa000avow8t6p5yc4l"
        name:
          type: string
          example: "Test video"
        sourceAvatarId:
          type: string
          example: "cm2yaz9j10008l0qg9npyjgs3"
        createdAt:
          type: string
          format: date
          example: "2025-01-07T23:17:06.601Z"
    VideoInfo:
      type: object
      properties:
        id:
          type: string
          example: "cm5n3bnpa000avow8t6p5yc4l"
        name:
          type: string
          example: "Test video"
        sourceAvatarId:
          type: string
          example: "cm2yaz9j10008l0qg9npyjgs3"
        createdAt:
          type: string
          format: date
          example: "2025-01-07T23:17:06.601Z"
        downloadUrl:
          type: string
          format: url
          example: "https://miraflow-media.s3.us-west-2.amazonaws.com/cm5n3bnpa000avow8t6p5yc4l.mp4"
    CreateVideoWithSpeech:
      type: object
      properties:
        avatarId:
          type: string
          example: "cm2yaz9j10008l0qg9npyjgs3"
          required: true
        name:
          type: string
          example: "My new video"
        speechId:
          type: string
          example: "cm1miwee60002dkbtwxso4fu1"
        im2vid_full:
          type: boolean
          example: true
    CreateVideoWithVoice:
      type: object
      properties:
        avatarId:
          type: string
          example: "cm2yaz9j10008l0qg9npyjgs3"
          required: true
        name:
          type: string
          example: "My new video"
        voiceId:
          type: string
          example: "cm1lgilny0005m2lnvv3vgtgv"
        text:
          type: string
          example: "This is the text of the script to be generated in the specified voice."
        im2vid_full:
          type: boolean
          example: true
    CreateVideoWithSpeechFile:
      type: object
      properties:
        avatarId:
          type: string
          example: "cm2yaz9j10008l0qg9npyjgs3"
        name:
          type: string
          example: "My new video"
        speechFile:
          type: string
          format: binary
          description: "ElevenLabs speech file (mp3 format)"
        im2vid_full:
          type: boolean
          example: true
    CreateCinematicVideo:
      type: object
      required:
        - name
        - prompt
      properties:
        name:
          type: string
          example: "Sunset product reel"
          description: "Display name for the generated video"
        prompt:
          type: string
          example: "Slow dolly shot across a glass table at golden hour, soft bokeh, cinematic lighting. No text."
          description: "Text prompt describing the cinematic video"
        aspectRatio:
          type: string
          enum: ["16:9", "9:16", "1:1"]
          default: "16:9"
        durationSeconds:
          type: integer
          minimum: 4
          maximum: 8
          description: "Clip length in seconds (model-dependent; typically 8)"
        resolution:
          type: string
          enum: ["720p", "1080p"]
          default: "720p"
        enhancePrompt:
          type: boolean
          description: "Let the model expand/improve the prompt"
        generateAudio:
          type: boolean
          description: "Generate audio with the video (when supported)"
        negativePrompt:
          type: string
          description: "What to avoid in the generation"
        sampleCount:
          type: integer
          minimum: 1
          maximum: 4
          description: "Number of variants (uses additional quota if > 1)"
        folderId:
          type: string
          description: "Optional folder to file the result under"
        firstFrameImageMediaId:
          type: string
          description: "Optional first frame from a finalized media upload"
        firstFrameImageMimeType:
          type: string
          example: "image/png"
          description: "Required when firstFrameImageMediaId is set"
        lastFrameImageMediaId:
          type: string
        lastFrameImageMimeType:
          type: string
          example: "image/png"
          description: "Required when lastFrameImageMediaId is set"
        referenceImageMediaIds:
          type: array
          maxItems: 3
          items:
            type: string
          description: "Up to 3 reference images (finalized media IDs you own)"
    CreateInpaintWithFiles:
      type: object
      required:
        - originalImage
        - maskImage
        - prompt
        - name
      properties:
        originalImage:
          type: string
          format: binary
          description: "The original image file to inpaint"
        maskImage:
          type: string
          format: binary
          description: "The mask image (black and white). White areas indicate what to replace, black areas indicate what to keep."
        prompt:
          type: string
          example: "Change to blue"
          description: "Description of what to replace the masked areas with"
        name:
          type: string
          example: "My inpainting job"
          description: "Name for the inpainting job"
        aspectRatio:
          type: string
          enum: ["1:1", "16:9", "9:16", "4:3", "3:4"]
          example: "1:1"
          description: "Aspect ratio for the output image"
        negativePrompt:
          type: string
          example: "blurry, low quality"
          description: "Optional negative prompt to exclude certain elements"
        seed:
          type: integer
          example: 12345
          description: "Optional seed for reproducible results"
    CreateInpaintWithMediaIds:
      type: object
      required:
        - originalImageMediaId
        - maskImageMediaId
        - prompt
        - name
      properties:
        originalImageMediaId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e1"
          description: "Media ID of the previously uploaded original image"
        maskImageMediaId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e2"
          description: "Media ID of the previously uploaded mask image"
        prompt:
          type: string
          example: "Change to blue"
          description: "Description of what to replace the masked areas with"
        name:
          type: string
          example: "My inpainting job"
          description: "Name for the inpainting job"
        aspectRatio:
          type: string
          enum: ["1:1", "16:9", "9:16", "4:3", "3:4"]
          example: "1:1"
          description: "Aspect ratio for the output image"
        negativePrompt:
          type: string
          example: "blurry, low quality"
          description: "Optional negative prompt to exclude certain elements"
        seed:
          type: integer
          example: 12345
          description: "Optional seed for reproducible results"
    ImageInfo:
      type: object
      properties:
        id:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e2"
        name:
          type: string
          example: "My inpainting job"
        prompt:
          type: string
          example: "Change to blue"
        createdAt:
          type: string
          format: date-time
          example: "2025-01-07T23:17:06.601Z"
        downloadUrl:
          type: string
          format: url
          example: "https://miraflow-media.s3.us-west-2.amazonaws.com/cm42ab3ou0008aggajj0jy5e2.png"
        mediaId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e3"
    CreateImageGeneration:
      type: object
      required:
        - prompt
        - name
      properties:
        prompt:
          type: string
          example: "A photorealistic portrait of an adult person with natural lighting"
          description: "Text description of the image to generate"
        name:
          type: string
          example: "My generated image"
          description: "Name for the image generation job"
        aspectRatio:
          type: string
          enum: ["1:1", "16:9", "9:16", "4:3", "3:4"]
          example: "1:1"
          description: "Aspect ratio for the output image"
        negativePrompt:
          type: string
          example: "blurry, low quality, distorted"
          description: "Optional negative prompt to exclude certain elements"
        seed:
          type: integer
          example: 12345
          description: "Optional seed for reproducible results"
    CreateImageEditWithFile:
      type: object
      required:
        - referenceImage
        - prompt
        - name
      properties:
        referenceImage:
          type: string
          format: binary
          description: "The reference image file to edit"
        prompt:
          type: string
          example: "Turn this photo into a professional business headshot"
          description: "Description of how to edit the image"
        name:
          type: string
          example: "My image editing job"
          description: "Name for the image editing job"
        aspectRatio:
          type: string
          enum: ["1:1", "16:9", "9:16", "4:3", "3:4"]
          example: "1:1"
          description: "Aspect ratio for the output image"
        negativePrompt:
          type: string
          example: "blurry, low quality"
          description: "Optional negative prompt to exclude certain elements"
        seed:
          type: integer
          example: 12345
          description: "Optional seed for reproducible results"
    CreateImageEditWithFiles:
      type: object
      required:
        - referenceImages
        - prompt
        - name
      properties:
        referenceImages:
          type: array
          items:
            type: string
            format: binary
          description: "Array of reference image files to edit"
        prompt:
          type: string
          example: "Transform these images with cinematic lighting"
          description: "Description of how to edit the images"
        name:
          type: string
          example: "My batch editing job"
          description: "Name for the image editing job"
        aspectRatio:
          type: string
          enum: ["1:1", "16:9", "9:16", "4:3", "3:4"]
          example: "1:1"
          description: "Aspect ratio for the output image"
        negativePrompt:
          type: string
          example: "blurry, low quality"
          description: "Optional negative prompt to exclude certain elements"
        seed:
          type: integer
          example: 12345
          description: "Optional seed for reproducible results"
    CreateImageEditWithMediaId:
      type: object
      required:
        - referenceImageMediaId
        - prompt
        - name
      properties:
        referenceImageMediaId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e1"
          description: "Media ID of the previously uploaded reference image"
        prompt:
          type: string
          example: "Turn this photo into a professional business headshot"
          description: "Description of how to edit the image"
        name:
          type: string
          example: "My image editing job"
          description: "Name for the image editing job"
        aspectRatio:
          type: string
          enum: ["1:1", "16:9", "9:16", "4:3", "3:4"]
          example: "1:1"
          description: "Aspect ratio for the output image"
        negativePrompt:
          type: string
          example: "blurry, low quality"
          description: "Optional negative prompt to exclude certain elements"
        seed:
          type: integer
          example: 12345
          description: "Optional seed for reproducible results"
    CreateImageEditWithMediaIds:
      type: object
      required:
        - referenceImageMediaIds
        - prompt
        - name
      properties:
        referenceImageMediaIds:
          type: array
          items:
            type: string
          example: ["cm42ab3ou0008aggajj0jy5e1", "cm42ab3ou0008aggajj0jy5e2"]
          description: "Array of media IDs of previously uploaded reference images"
        prompt:
          type: string
          example: "Transform these images with cinematic lighting"
          description: "Description of how to edit the images"
        name:
          type: string
          example: "My batch editing job"
          description: "Name for the image editing job"
        aspectRatio:
          type: string
          enum: ["1:1", "16:9", "9:16", "4:3", "3:4"]
          example: "1:1"
          description: "Aspect ratio for the output image"
        negativePrompt:
          type: string
          example: "blurry, low quality"
          description: "Optional negative prompt to exclude certain elements"
        seed:
          type: integer
          example: 12345
          description: "Optional seed for reproducible results"
    # ==================== Mockup API Schemas ====================
    CreateMockup:
      type: object
      required:
        - productMediaId
        - instruction
      properties:
        productMediaId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e1"
          description: "Media ID of the product image"
        logoMediaIds:
          type: array
          items:
            type: string
          example: ["cm42ab3ou0008aggajj0jy5e2"]
          description: "Array of media IDs for logo images to composite"
        layers:
          type: array
          items:
            $ref: "#/components/schemas/LayerPlacement"
          description: "Placement data for each logo"
        instruction:
          type: string
          example: "Place the logo on the front of the t-shirt"
          description: "Instructions for mockup generation"
    LayerPlacement:
      type: object
      properties:
        assetId:
          type: string
          description: "ID of the logo asset"
        x:
          type: number
          minimum: 0
          maximum: 100
          example: 50
          description: "Horizontal position as percentage (0-100)"
        y:
          type: number
          minimum: 0
          maximum: 100
          example: 30
          description: "Vertical position as percentage (0-100)"
        scale:
          type: number
          example: 1
          description: "Scale factor (1 = 100%)"
        rotation:
          type: number
          example: 0
          description: "Rotation in degrees"
    CreateMockupVideo:
      type: object
      required:
        - mockupId
        - animationStyle
      properties:
        mockupId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e1"
          description: "ID of the saved mockup"
        animationStyle:
          type: string
          enum: [luxury_macro, neo_noir_night, warm_lifestyle_sunset, futuristic_clean_studio, cyberpunk_showcase, film_35mm_editorial, high_fashion_runway, nature_premium, custom]
          example: "luxury_macro"
          description: "Animation style preset"
        userPrompt:
          type: string
          example: "Focus on the logo details"
          description: "Optional custom prompt (required if animationStyle is 'custom')"
        aspectRatio:
          type: string
          enum: ["16:9", "9:16"]
          default: "9:16"
          description: "Video aspect ratio"
    CreateAsset:
      type: object
      required:
        - prompt
        - type
      properties:
        prompt:
          type: string
          example: "minimalist tech company logo"
          description: "Description of the asset to generate"
        type:
          type: string
          enum: [logo, product]
          example: "logo"
          description: "Type of asset to generate"
    EditAsset:
      type: object
      required:
        - prompt
        - type
      properties:
        imageMediaId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e1"
          description: "Media ID of the image to edit"
        imageBase64:
          type: string
          description: "Base64-encoded image data (alternative to imageMediaId)"
        prompt:
          type: string
          example: "Remove the background"
          description: "Edit instructions"
        type:
          type: string
          enum: [logo, product]
          example: "logo"
          description: "Type of asset"
    EditAssetWithFile:
      type: object
      required:
        - image
        - prompt
        - type
      properties:
        image:
          type: string
          format: binary
          description: "Image file to edit"
        prompt:
          type: string
          example: "Remove the background"
          description: "Edit instructions"
        type:
          type: string
          enum: [logo, product]
          example: "logo"
          description: "Type of asset"
    MockupItem:
      type: object
      properties:
        id:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e2"
        imageUrl:
          type: string
          example: "/api/media/cm42ab3ou0008aggajj0jy5e3/view"
        prompt:
          type: string
          example: "Place logo on t-shirt"
        createdAt:
          type: string
          format: date-time
          example: "2025-01-07T23:17:06.601Z"
        productMediaId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e1"
        logoMediaIds:
          type: array
          items:
            type: string
    AssetItem:
      type: object
      properties:
        id:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e2"
        type:
          type: string
          enum: [logo, product]
          example: "logo"
        name:
          type: string
          example: "AI Generated logo"
        imageUrl:
          type: string
          example: "/api/media/cm42ab3ou0008aggajj0jy5e3/view"
        mediaId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e3"
        createdAt:
          type: string
          format: date-time
          example: "2025-01-07T23:17:06.601Z"
    MockupVideoItem:
      type: object
      properties:
        id:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e2"
        name:
          type: string
          example: "Product Ad - luxury macro"
        status:
          type: string
          enum: [completed, in_progress, failed]
          example: "completed"
        videoUrl:
          type: string
          example: "https://miraflow-media.s3.us-west-2.amazonaws.com/video.mp4"
        mockupId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e1"
        mockupImageUrl:
          type: string
          example: "/api/media/cm42ab3ou0008aggajj0jy5e3/view"
        aspectRatio:
          type: string
          enum: ["16:9", "9:16"]
          example: "9:16"
        createdAt:
          type: string
          format: date-time
          example: "2025-01-07T23:17:06.601Z"
        progress:
          type: number
          example: 75
    # ==================== Thumbnail API Schemas ====================
    CreateThumbnail:
      type: object
      required:
        - prompt
        - name
      properties:
        prompt:
          type: string
          example: "Epic gaming moment with explosion and character in action pose"
          description: "Description of the thumbnail to generate"
        name:
          type: string
          example: "Gaming Video Thumbnail"
          description: "Name for the thumbnail"
        aspectRatio:
          type: string
          enum: ["16:9", "9:16"]
          default: "16:9"
          description: "Aspect ratio (16:9 for standard, 9:16 for Shorts)"
        referenceImageMediaIds:
          type: array
          items:
            type: string
          description: "Optional media IDs of reference images"
        negativePrompt:
          type: string
          example: "blurry, low quality"
          description: "Elements to avoid"
        seed:
          type: integer
          example: 12345
          description: "Seed for reproducible results"
    ThumbnailItem:
      type: object
      properties:
        id:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e2"
        name:
          type: string
          example: "Gaming Video Thumbnail"
        prompt:
          type: string
          example: "Epic gaming moment"
        imageUrl:
          type: string
          example: "/api/media/cm42ab3ou0008aggajj0jy5e3/view"
        mediaId:
          type: string
          example: "cm42ab3ou0008aggajj0jy5e3"
        aspectRatio:
          type: string
          example: "16:9"
        createdAt:
          type: string
          format: date-time
          example: "2025-01-07T23:17:06.601Z"
