openapi: 3.1.0
info:
  title: GenAIPro API
  version: 1.0.0
  description: |
    GenAIPro provides APIs to integrate AI services including:
    - **Labs (Voice AI)** — High-quality Text-to-Speech with 1000+ voices
    - **Voice AI Beta** — Multi-provider Text-to-Speech (Nova, Lyra, Sirius, Vega) with voice cloning & voice design
    - **Veo** — AI video and image generation

    ## Base URL
    ```
    https://genaipro.io/api
    ```

    ## Authentication
    All API endpoints require a **Bearer Token** in the `Authorization` header.

    ### Get an API Token
    1. Visit [genaipro.io](https://genaipro.io) and sign in to your account
    2. Click your **avatar** → **Manage Account** → **API Key**
    3. Click **"Create API Key"** — the token will be displayed

    ### Usage
    ```bash
    curl -X GET https://genaipro.io/api/v2/me \
      --header 'Authorization: Bearer <token>'
    ```

    ## WebSocket Notifications
    GenAIPro provides real-time notifications for task progress, completion, and account events via WebSocket. Events are pushed only to the authenticated user that owns the underlying resource.

    ### Connection
    ```
    wss://genaipro.io/ws?token=<your_api_token>
    ```

    ### Message envelope

    All server-pushed events share the same envelope:

    ```json
    {
      "type": "<event_name>",
      "payload": { ... }
    }
    ```

    ### Events

    #### `labs_status_updated`
    Labs TTS task progress. `process_percentage` is `0`–`100`. When it reaches `100`, the audio is ready — fetch the result via `GET /v1/labs/task/{task_id}`.

    ```json
    {
      "type": "labs_status_updated",
      "payload": {
        "task_id": "bc69572b-c2d8-43c1-b913-14353e096622",
        "process_percentage": 43
      }
    }
    ```

    #### `elevenlabs_subtitle_complete`
    Sent after a Labs TTS subtitle export finishes (see `POST /v1/labs/task/subtitle/{task_id}`). `subtitle` contains the URL of the generated subtitle file.

    ```json
    {
      "type": "elevenlabs_subtitle_complete",
      "payload": {
        "task_id": "bc69572b-c2d8-43c1-b913-14353e096622",
        "subtitle": "https://media.genaipro.io/subtitle/bc69572b.vtt"
      }
    }
    ```

    #### `veo_v2_task_updated`
    Veo V2 task state changed (text-to-video, frames-to-video, ingredients-to-video, create-image). Use this instead of polling `GET /v2/veo/tasks/{id}` when you only need final state. `status` is one of `processing`, `completed`, `failed`. `file_urls` is empty until completion. `error` is only present on failure (credits are refunded automatically).

    ```json
    {
      "type": "veo_v2_task_updated",
      "payload": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "prompt": "A cat walking on the beach at sunset",
        "file_urls": ["https://files.genaipro.io/video_550e8400.mp4"],
        "status": "completed",
        "error": ""
      }
    }
    ```

    #### `gt_mage_task_updated`
    ChatGPT Image task state changed (`POST /v2/gt-mage/create-image`). Use this instead of polling `GET /v2/gt-mage/tasks/{id}` when you only need final state. Same payload shape as `veo_v2_task_updated`: `status` is one of `processing`, `completed`, `failed`; `file_urls` is empty until completion; `error` is only present on failure (credits are refunded automatically).

    ```json
    {
      "type": "gt_mage_task_updated",
      "payload": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "prompt": "A futuristic city at night",
        "file_urls": ["https://files.genaipro.io/gtmage_xxx_0.png"],
        "status": "completed",
        "error": ""
      }
    }
    ```

    #### `max_status_updated`
    Minimax TTS task progress. Same shape as `labs_status_updated`. `process_percentage` is `0`–`100`.

    ```json
    {
      "type": "max_status_updated",
      "payload": {
        "task_id": "max-789",
        "process_percentage": 75
      }
    }
    ```

    #### `tts_<provider>_task_updated`
    A Voice AI Beta task reached a terminal state (`completed` or `failed`). One event type per provider: `tts_nova_task_updated`, `tts_lyra_task_updated`, `tts_sirius_task_updated`, `tts_vega_task_updated`. The payload is the same task object returned by the provider's list endpoint — and the same object POSTed to `callback_url`.

    ```json
    {
      "type": "tts_nova_task_updated",
      "payload": {
        "id": "0198c2f1-7b7a-7c11-a3f2-3e9d2b1c4a55",
        "title": "Intro voiceover",
        "status": "completed",
        "provider": "nova",
        "result": "https://files.genaipro.io/0198c2f1.mp3",
        "total_length": 1234,
        "process_percentage": 100,
        "created_at": "2026-07-01T10:00:00Z",
        "updated_at": "2026-07-01T10:01:30Z"
      }
    }
    ```

    #### `tts_<provider>_progress`
    Advisory progress ticks for a running Voice AI Beta task (`tts_nova_progress`, `tts_lyra_progress`, `tts_sirius_progress`, `tts_vega_progress`). Sent when `status` or `process_percentage` changes; the terminal state is still delivered via `tts_<provider>_task_updated`.

    ```json
    {
      "type": "tts_nova_progress",
      "payload": {
        "task_id": "0198c2f1-7b7a-7c11-a3f2-3e9d2b1c4a55",
        "status": "processing",
        "process_percentage": 43
      }
    }
    ```

    #### `payment_notification`
    Sent when the authenticated user's balance is topped up (deposit confirmed). `amount` is the credited amount.

    ```json
    {
      "type": "payment_notification",
      "payload": {
        "amount": 50000,
        "user_id": "8aae1449-e136-4be4-b1ad-27fdc65a261f"
      }
    }
    ```
  contact:
    name: GenAIPro Support
    url: https://t.me/genaipro_vn
  x-logo:
    url: https://genaipro.io/favicon.png

servers:
  - url: https://genaipro.io/api
    description: Production

security:
  - bearerAuth: []

tags:
  - name: User
    description: User account information
  - name: Labs
    description: Voice AI — Text-to-Speech with 1000+ voices
  - name: Voice AI Beta
    description: |
      Multi-provider Text-to-Speech — the engine behind [genaipro.io/voice-ai](https://genaipro.io/voice-ai). Four providers are available, each with its own voice catalog and pricing, all sharing one task lifecycle, one credit pool and one voice-clone library.

      ## Providers

      | Capability | Nova | Lyra | Sirius | Vega |
      |---|---|---|---|---|
      | Create endpoint | `POST /v1/tts/nova` | `POST /v1/tts/lyra` | `POST /v1/tts/sirius` | `POST /v1/tts/vega` |
      | Preset voice field | `model_id` | `voice_id` | `voice` | `voice` |
      | Voice cloning (`voice_asset_id`) | ✅ | ✅ | ✅ | — |
      | Voice design (`voice_design_id`) | — | — | ✅ | — |
      | Extra controls | `normalize` | `language`, `speed`, `volume`, `emotion` | `style` | — |
      | Price (credits) | `⌈characters × 0.7⌉` | `characters` | `⌈characters / 2⌉` | `⌈characters / 10⌉` |
      | Create / Delete status | `200` / `200` | `200` / `200` | `201` / `204` | `201` / `204` |
      | Output format | mp3 | mp3 | mp3 | mp3 |

      ## Credits & pricing

      All four providers share one **Voice AI credit pool** — check your balance with `GET /v1/labs/credits`. A task's cost is computed from its character count using the per-provider formula above. Insufficient balance → `400` `"Not enough credit"`. Failed tasks are **refunded automatically**; deleting a task does **not** refund.

      **Character counting:** `characters` / `total_length` are measured in encoder units (≈ UTF-8 bytes): ASCII = 1, most CJK = 3, emoji = 4 — *not* Unicode codepoints. The estimate endpoints are **free** — call them first to know the exact cost.

      ## Task lifecycle

      ```
      pending → cloning (clone flow only) → queued → processing → merging → process_merging → completed | failed
      ```

      `process_percentage` goes `0`–`100`. `completed` tasks carry the audio URL in `result`; `failed` tasks carry a message in `error`.

      ## Getting results

      1. **Webhook** — pass `callback_url` when creating a task. When the task reaches a terminal state the server POSTs the task object (same shape as a list item, no envelope) to your URL: up to 5 attempts, 2/4/6/8 s backoff, 15 s timeout per attempt. The URL is validated at creation (`400` `"Invalid callback_url"`).
      2. **WebSocket** — connect `wss://genaipro.io/ws?token=<your_api_token>` and listen for `tts_<provider>_task_updated` / `tts_<provider>_progress` (see *WebSocket Notifications* above).
      3. **Polling** — `GET /v1/tts/{provider}/{id}`.

      ## Voice clones

      One provider-agnostic clone library lives at `/v1/tts/voice-assets`: upload a 10–90 s audio sample once, then pass its id as `voice_asset_id` to any provider that supports cloning (Nova, Lyra, Sirius). Limits: ≤ 32 MB, formats `mp3, wav, m4a, ogg, flac, webm`, max **20** assets per account.

      ## Rate limits

      | Endpoint | Limit | Window |
      |----------|-------|--------|
      | Create task (POST) | 30 requests | 60 seconds |
      | Estimate | 60 requests | 60 seconds |
      | List tasks / voices / history | 60 requests | 60 seconds |
      | Get task | 120 requests | 60 seconds |
      | Delete task | 30 requests | 60 seconds |
      | Voice cover / sample proxy | 300 / 180 requests | 60 seconds |
      | Voice asset upload | 60 requests | 1 hour |
      | Voice asset / design delete | 20 requests | 24 hours |
      | Voice design create | 10 requests | 1 hour |

      Rate limit response: `429 Too Many Requests` with headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`.
  - name: Voice AI Beta — Nova
    description: |
      Text-to-Speech with a large public voice catalog (browse via `GET /v1/tts/nova/voices`) and voice cloning. Balanced price/quality — `⌈characters × 0.7⌉` credits.

      See the **Voice AI Beta** tag for credits, character counting, task lifecycle, webhooks and rate limits.
  - name: Voice AI Beta — Lyra
    description: |
      Text-to-Speech with explicit language control (42 languages, default `vi`), speed / volume / emotion controls, and voice cloning. Price: `characters` credits (1 : 1).

      See the **Voice AI Beta** tag for credits, character counting, task lifecycle, webhooks and rate limits.
  - name: Voice AI Beta — Sirius
    description: |
      Cross-lingual Text-to-Speech: 9 curated preset voices (every voice speaks ~10 languages), voice cloning, **voice design** (describe a voice in text → generate it) and free-text `style` instructions for preset voices. Price: `⌈characters / 2⌉` credits.

      See the **Voice AI Beta** tag for credits, character counting, task lifecycle, webhooks and rate limits.
  - name: Voice AI Beta — Vega
    description: |
      Budget English Text-to-Speech with 27 curated voices (US/UK accents). The cheapest provider — `⌈characters / 10⌉` credits. No cloning.

      See the **Voice AI Beta** tag for credits, character counting, task lifecycle, webhooks and rate limits.
  - name: Veo V2
    description: |
      AI video and image generation (async JSON, polling).

      ## Usage Flow

      ```
      1. POST /v2/veo/text-to-video   →  202 { histories: [{id, status: "processing"}] }
      2. GET  /v2/veo/tasks/{id}       →  200 { status: "processing" }    (polling)
      3. GET  /v2/veo/tasks/{id}       →  200 { status: "completed", file_urls: [...] }
      ```

      ## Credits

      Each creation request costs **1 credit** (regardless of `number_of_videos` or `number_of_images`). Failed tasks are automatically refunded.

      ## Rate Limits

      | Endpoint | Limit | Window |
      |----------|-------|--------|
      | Create video/image (POST) | 30 requests | 60 seconds |
      | Histories (GET /histories) | 30 requests | 60 seconds |
      | Poll task (GET /tasks/:id) | 30 requests | 60 seconds |

      Rate limit response: `429 Too Many Requests` with headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`.
  - name: ChatGPT Image
    description: |
      AI image generation powered by ChatGPT (async multipart, polling). Shares the same Veo credit pool.

      ## Usage Flow

      ```
      1. POST /v2/gt-mage/create-image  →  202 { id, status: "processing" }
      2. GET  /v2/gt-mage/tasks/{id}     →  200 { status: "processing" }    (polling)
      3. GET  /v2/gt-mage/tasks/{id}     →  200 { status: "completed", file_urls: [...] }
      ```

      ## Credits

      Each creation request costs **1 credit** (regardless of `n`), drawn from the shared **Veo credit pool** (`GET /v2/veo/credits`). Failed tasks are automatically refunded.

      ## Rate Limits

      | Endpoint | Limit | Window |
      |----------|-------|--------|
      | Create image (POST) | 30 requests | 60 seconds |
      | Histories (GET /histories) | 30 requests | 60 seconds |
      | Poll task (GET /tasks/:id) | 30 requests | 60 seconds |

paths:
  # ─── User ───────────────────────────────────────────────
  /v2/me:
    get:
      operationId: getUserInfo
      summary: Get User Info
      description: Get basic user information including ID, username, and balance.
      tags: [User]
      responses:
        "200":
          description: User information
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: User ID
                    example: abc123
                  username:
                    type: string
                    description: Username
                    example: johndoe
                  balance:
                    type: number
                    description: Account balance
                    example: 50000

  # ─── Labs ───────────────────────────────────────────────
  /v1/labs/voices:
    get:
      operationId: getVoices
      summary: Get Voices
      description: |
        Get list of available voices for Text-to-Speech.

        > **Rate limit:** 60 requests/minute
      tags: [Labs]
      parameters:
        - name: page_size
          in: query
          schema:
            type: integer
            default: 30
            maximum: 100
          description: Number of voices to return. Maximum 100.
        - name: page
          in: query
          schema:
            type: integer
            default: 0
          description: Page number for pagination (0-indexed).
        - name: search
          in: query
          schema:
            type: string
          description: Search term to filter voices by name or description. If a 20-character voice ID is provided, it also searches default voices.
        - name: gender
          in: query
          schema:
            type: string
          description: "Filter by gender (e.g. `male`, `female`)."
        - name: age
          in: query
          schema:
            type: string
          description: "Filter by age group (e.g. `young`, `middle_aged`, `old`)."
        - name: accent
          in: query
          schema:
            type: string
          description: "Filter by accent (e.g. `american`, `british`, `australian`)."
        - name: language
          in: query
          schema:
            type: string
          description: "Filter by language (e.g. `en`, `vi`, `ja`)."
        - name: locale
          in: query
          schema:
            type: string
          description: Filter by locale.
        - name: category
          in: query
          schema:
            type: string
            enum: [professional, famous, high_quality]
          description: Filter by voice category.
        - name: use_cases
          in: query
          schema:
            type: string
          description: "Filter by use case (e.g. `narration`, `conversational`)."
        - name: descriptives
          in: query
          schema:
            type: string
          description: Filter by descriptive tags.
        - name: featured
          in: query
          schema:
            type: boolean
            default: false
          description: Filter featured voices only.
        - name: sort
          in: query
          schema:
            type: string
          description: Sort criteria for results.
      responses:
        "200":
          description: List of voices
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Voice"
              example:
                - public_owner_id: owner123
                  voice_id: JBFqnCBsd6RMkjVDRZzb
                  date_unix: 1700000000
                  name: George
                  accent: British
                  gender: male
                  age: middle_aged
                  descriptive: warm
                  use_case: narration
                  category: professional
                  language: en
                  locale: null
                  description: A warm, professional British male voice.
                  preview_url: https://example.com/preview.mp3
                  image_url: https://example.com/avatar.jpg
                  rate: 0.0
                  free_users_allowed: true
                  featured: false
                  verified_languages:
                    - language: en
                      model_id: eleven_multilingual_v2
                      accent: British
                      locale: null
                      preview_url: https://example.com/preview-en.mp3

  /v1/labs/credits:
    get:
      operationId: getLabsCredits
      summary: Get Labs Credits
      description: Get Voice AI credits information for current user.
      tags: [Labs]
      responses:
        "200":
          description: Credits list
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    amount:
                      type: number
                      description: Credit amount remaining
                      example: 100000
                    expire_at:
                      type: string
                      format: date-time
                      description: Expiration date (ISO 8601)
                      example: "2026-06-30T23:59:59Z"

  /v1/labs/task:
    post:
      operationId: createTtsTask
      summary: Create TTS Task
      description: Create a Text-to-Speech task with Labs.
      tags: [Labs]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [input, voice_id, model_id]
              properties:
                input:
                  type: string
                  description: Text to convert to speech
                  example: "Xin chào, đây là GenAIPro"
                voice_id:
                  type: string
                  description: "Voice ID (from `/v1/labs/voices`)"
                model_id:
                  type: string
                  description: Model ID
                  enum:
                    - eleven_multilingual_v2
                    - eleven_turbo_v2_5
                    - eleven_flash_v2_5
                    - eleven_v3
                  example: eleven_multilingual_v2
                stability:
                  type: number
                  default: 0.75
                  description: Stability (0-1)
                similarity:
                  type: number
                  default: 0.5
                  description: Similarity (0-1)
                style:
                  type: number
                  default: 0
                  description: Style exaggeration (0-1)
                speed:
                  type: number
                  default: 1
                  description: Speed (0.7-1.2)
                use_speaker_boost:
                  type: boolean
                  default: true
                  description: Speaker boost
                call_back_url:
                  type: string
                  description: Callback URL — will be called when the task completes
      responses:
        "200":
          description: Task created
          content:
            application/json:
              schema:
                type: object
                properties:
                  task_id:
                    type: string
                    description: ID of the created task
                    example: abc123-def456
    get:
      operationId: getTaskHistory
      summary: Task History
      description: Get TTS task history.
      tags: [Labs]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
          description: Page number
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
          description: Number of tasks per page
      responses:
        "200":
          description: Paginated task list
          content:
            application/json:
              schema:
                type: object
                properties:
                  tasks:
                    type: array
                    description: "List of tasks. Returns `[]` when there are no tasks."
                    items:
                      $ref: "#/components/schemas/LabsTask"
                  total:
                    type: number
                    description: Total number of tasks
                  page:
                    type: integer
                    description: Current page
                  limit:
                    type: integer
                    description: Items per page
              example:
                tasks:
                  - id: abc123-def456
                    input: Xin chào
                    voice_id: 21m00Tcm4TlvDq8ikWAM
                    model_id: eleven_multilingual_v2
                    style: 0
                    speed: 1.0
                    use_speaker_boost: true
                    similarity: 0.5
                    stability: 0.75
                    created_at: "2026-03-31T10:00:00Z"
                    status: completed
                    result: https://media.genaipro.io/audio/abc123.mp3
                    subtitle: ""
                total: 1
                page: 1
                limit: 20

  /v1/labs/task/{task_id}:
    get:
      operationId: getTask
      summary: Get Task
      description: Get details of a TTS task.
      tags: [Labs]
      parameters:
        - name: task_id
          in: path
          required: true
          schema:
            type: string
          description: Task ID
      responses:
        "200":
          description: Task details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LabsTask"
              example:
                id: abc123-def456
                input: "Xin chào, đây là GenAIPro"
                voice_id: 21m00Tcm4TlvDq8ikWAM
                model_id: eleven_multilingual_v2
                style: 0
                speed: 1.0
                use_speaker_boost: true
                similarity: 0.5
                stability: 0.75
                created_at: "2026-03-31T10:00:00Z"
                status: completed
                result: https://media.genaipro.io/audio/abc123.mp3
                subtitle: ""
    delete:
      operationId: deleteTask
      summary: Delete Task
      description: Delete a TTS task.
      tags: [Labs]
      parameters:
        - name: task_id
          in: path
          required: true
          schema:
            type: string
          description: Task ID to delete
      responses:
        "200":
          description: Task deleted successfully

  /v1/labs/task/subtitle/{task_id}:
    post:
      operationId: exportSubtitle
      summary: Export Subtitle
      description: Export subtitles from a TTS task.
      tags: [Labs]
      parameters:
        - name: task_id
          in: path
          required: true
          schema:
            type: string
          description: Task ID
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                max_characters_per_line:
                  type: number
                  description: Maximum characters per subtitle line
                  default: 42
                max_lines_per_cue:
                  type: number
                  description: Maximum lines per subtitle cue
                  default: 2
                max_seconds_per_cue:
                  type: number
                  description: Maximum seconds per subtitle cue
                  default: 7
      responses:
        "200":
          description: Subtitle exported successfully

  # ─── Voice AI Beta — History & Voice Clones ────────────
  /v1/tts/history:
    get:
      operationId: getTtsHistory
      summary: Task History (All Providers)
      description: |
        Merged task history across all Voice AI Beta providers, newest first. Use `provider` to filter to a single provider.
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
        - name: provider
          in: query
          description: "Filter by provider: `nova`, `lyra`, `sirius`, `vega`. Omit for all providers."
          schema:
            type: string
      responses:
        "200":
          description: Paginated task history
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsTaskListResponse"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/voice-assets:
    post:
      operationId: uploadVoiceAsset
      summary: Upload Voice Clone Sample
      description: |
        Upload an audio sample to the shared voice-clone library (`multipart/form-data`). The asset is ready immediately — pass its `id` as `voice_asset_id` when creating a task with Nova, Lyra or Sirius.

        Requirements: duration **10–90 seconds** · size ≤ **32 MB** · formats `mp3, wav, m4a, ogg, flac, webm` · max **20** assets per account.
        > **Rate limit:** 60 requests/hour
      tags: [Voice AI Beta]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [audio_file, title]
              properties:
                audio_file:
                  type: string
                  format: binary
                  description: Audio file (10–90 s, ≤ 32 MB)
                title:
                  type: string
                  maxLength: 200
                  description: Display name for the voice
      responses:
        "200":
          description: Asset uploaded (note — `200`, not `201`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVoiceAsset"
        "400":
          description: Invalid file (duration, format) or asset quota reached
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "You have reached the maximum of 20 voice assets"
        "413":
          description: File exceeds 32 MB
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
    get:
      operationId: listVoiceAssets
      summary: List Voice Clones
      description: |
        List your uploaded voice-clone samples (paginated).
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        "200":
          description: Paginated voice assets
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVoiceAssetListResponse"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/voice-assets/{id}:
    get:
      operationId: getVoiceAsset
      summary: Get Voice Clone
      description: |
        Get one voice asset. `sample_url` is a playback URL for the uploaded sample.
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Voice asset
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVoiceAsset"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
    put:
      operationId: renameVoiceAsset
      summary: Rename Voice Clone
      description: |
        Rename a voice asset (title only).
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title]
              properties:
                title:
                  type: string
                  maxLength: 200
      responses:
        "200":
          description: Updated voice asset
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVoiceAsset"
        "400":
          description: Invalid title
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
    delete:
      operationId: deleteVoiceAsset
      summary: Delete Voice Clone
      description: |
        Delete a voice asset. Fails with `409` while an unfinished task is still using it.
        > **Rate limit:** 60 requests/minute, and at most **20 deletes per 24 hours**
      tags: [Voice AI Beta]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deleted
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "409":
          description: Asset is in use by an active task
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "Voice asset is in use by an active task"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  # ─── Voice AI Beta — Nova ───────────────────────────────
  /v1/tts/nova:
    post:
      operationId: createNovaTask
      summary: Create Speech Task
      description: |
        Create a Text-to-Speech task with Nova. Provide **exactly one** of `model_id` (preset voice from `GET /v1/tts/nova/voices`) or `voice_asset_id` (your voice clone). Cost: `⌈characters × 0.7⌉` credits, deducted on creation and refunded automatically on failure.

        ### Example
        ```bash
        curl -X POST https://genaipro.io/api/v1/tts/nova \
          -H "Authorization: Bearer YOUR_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{"content": "Xin chào, đây là GenAIPro", "model_id": "e3cd384158934cc9a01029cd7d278634"}'
        ```
        > **Rate limit:** 30 requests/minute
      tags: [Voice AI Beta — Nova]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                title:
                  type: string
                  description: Optional title (derived from content when empty)
                content:
                  type: string
                  description: Text to convert to speech (no fixed length limit — bounded by your credit balance)
                  example: "Xin chào, đây là GenAIPro"
                model_id:
                  type: string
                  description: "Preset voice id (`_id` from `GET /v1/tts/nova/voices`). Provide exactly one of `model_id` / `voice_asset_id`."
                  example: e3cd384158934cc9a01029cd7d278634
                voice_asset_id:
                  type: string
                  description: "Voice clone id (from `POST /v1/tts/voice-assets`). Provide exactly one of `model_id` / `voice_asset_id`."
                normalize:
                  type: boolean
                  default: true
                  description: Normalize text before synthesis
                callback_url:
                  type: string
                  description: Optional webhook — receives the task object when the task completes or fails
      responses:
        "200":
          description: Task created — credits already deducted (`cost`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsTaskCreatedWithCost"
              example:
                id: 0198c2f1-7b7a-7c11-a3f2-3e9d2b1c4a55
                status: pending
                provider: nova
                total_length: 1234
                cost: 864
        "400":
          description: Invalid request (voice selection, credit, callback)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              examples:
                voice_selection:
                  value:
                    error: "Provide exactly one of model_id or voice_asset_id"
                credit:
                  value:
                    error: "Not enough credit"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
        "503":
          description: Voice verification temporarily unavailable — retry
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "Could not verify model_id, please try again"
    get:
      operationId: listNovaTasks
      summary: List Tasks
      description: |
        Your Nova task history (paginated, newest first).
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Nova]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        "200":
          description: Paginated tasks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsTaskListResponse"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/nova/estimate:
    post:
      operationId: estimateNovaTask
      summary: Estimate Cost
      description: |
        Estimate the character count and credit cost of a text **without creating a task**. Free — call it before `POST /v1/tts/nova`.
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Nova]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                content:
                  type: string
      responses:
        "200":
          description: Estimate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsEstimateWithCost"
        "400":
          description: Content is required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/nova/{id}:
    get:
      operationId: getNovaTask
      summary: Get Task
      description: |
        Task detail, including original `content`. When `status` is `completed` the audio URL is in `result`.
        > **Rate limit:** 120 requests/minute
      tags: [Voice AI Beta — Nova]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Task detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsNovaTaskDetail"
        "404":
          description: Not found (ids from other providers also return 404 here)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "Task not found"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
    delete:
      operationId: deleteNovaTask
      summary: Delete Task
      description: |
        Remove a task from history. Does **not** refund credits.
        > **Rate limit:** 30 requests/minute
      tags: [Voice AI Beta — Nova]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deleted
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/nova/voices:
    get:
      operationId: listNovaVoices
      summary: List Voices
      description: |
        Browse the Nova public voice catalog (cached 1 h server-side). Use an item's `_id` as `model_id` when creating a task.

        The response is relayed **verbatim** from the upstream catalog — the exact field set is not part of the GenAIPro API contract and may change. `cover_image` and `samples[].audio` are internal proxy paths: prepend the API base URL to fetch them.
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Nova]
      parameters:
        - name: page_size
          in: query
          schema:
            type: integer
            default: 10
        - name: page_number
          in: query
          schema:
            type: integer
            default: 1
        - name: title
          in: query
          description: Search by voice title
          schema:
            type: string
        - name: language
          in: query
          description: "Language code filter (e.g. `en`, `vi`, `de`)"
          schema:
            type: string
        - name: sort_by
          in: query
          schema:
            type: string
            enum: [score, task_count, created_at]
        - name: tag
          in: query
          description: Filter by tag
          schema:
            type: string
      responses:
        "200":
          description: Voice catalog page (upstream-verbatim; representative example below)
          content:
            application/json:
              schema:
                type: object
              example:
                total: 200
                has_more: true
                items:
                  - _id: e3cd384158934cc9a01029cd7d278634
                    title: Laura
                    description: A confident female narrator.
                    cover_image: /v1/tts/nova/voices/cover/e3cd384158934cc9a01029cd7d278634
                    tags: [female, middle-aged, narration, warm]
                    languages: [en]
                    samples:
                      - title: Sample
                        text: "When we talk about trust..."
                        audio: /v1/tts/nova/voices/sample/e821bef9c2c74a04a6f01e83b8cbc548
                    like_count: 686
                    task_count: 53478
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/nova/voices/cover/{id}:
    get:
      operationId: getNovaVoiceCover
      summary: Voice Cover Image
      description: |
        Proxy for a voice's cover image (`cover_image` paths returned by `GET /v1/tts/nova/voices`). Returns image bytes with browser-cache headers.
        > **Rate limit:** 300 requests/minute
      tags: [Voice AI Beta — Nova]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Image bytes
          content:
            image/*:
              schema:
                type: string
                format: binary
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"

  /v1/tts/nova/voices/sample/{id}:
    get:
      operationId: getNovaVoiceSample
      summary: Voice Sample Audio
      description: |
        Proxy for a voice's preview audio (`samples[].audio` paths returned by `GET /v1/tts/nova/voices`). Returns audio bytes.
        > **Rate limit:** 180 requests/minute
      tags: [Voice AI Beta — Nova]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Audio bytes
          content:
            audio/*:
              schema:
                type: string
                format: binary
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"

  # ─── Voice AI Beta — Lyra ───────────────────────────────
  /v1/tts/lyra:
    post:
      operationId: createLyraTask
      summary: Create Speech Task
      description: |
        Create a Text-to-Speech task with Lyra. Provide **exactly one** of `voice_id` (preset voice from `GET /v1/tts/lyra/voices`) or `voice_asset_id` (your voice clone). Lyra requires an explicit `language` (defaults to `vi`) and supports `speed`, `volume` and `emotion` controls. Cost: `characters` credits (1 : 1), refunded automatically on failure.

        ### Example
        ```bash
        curl -X POST https://genaipro.io/api/v1/tts/lyra \
          -H "Authorization: Bearer YOUR_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{"content": "Xin chào, đây là GenAIPro", "voice_id": "694f1b24-...", "language": "vi", "speed": 1.0}'
        ```
        > **Rate limit:** 30 requests/minute
      tags: [Voice AI Beta — Lyra]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                title:
                  type: string
                  description: Optional title (derived from content when empty)
                content:
                  type: string
                  description: Text to convert to speech
                  example: "Xin chào, đây là GenAIPro"
                voice_id:
                  type: string
                  description: "Preset voice id (from `GET /v1/tts/lyra/voices`). Provide exactly one of `voice_id` / `voice_asset_id`."
                voice_asset_id:
                  type: string
                  description: "Voice clone id (from `POST /v1/tts/voice-assets`). Provide exactly one of `voice_id` / `voice_asset_id`."
                language:
                  type: string
                  default: vi
                  description: "ISO-639-1 language code (42 supported, e.g. `vi`, `en`, `ja`, `ko`, `zh`, `fr`, `de`, `es`). Unsupported codes → `400`."
                speed:
                  type: number
                  default: 1
                  minimum: 0.6
                  maximum: 1.5
                  description: Speaking speed (values outside the range are clamped)
                volume:
                  type: number
                  default: 1
                  minimum: 0.5
                  maximum: 2
                  description: Output volume (values outside the range are clamped)
                emotion:
                  type: string
                  enum: [neutral, angry, excited, content, sad, scared]
                  description: Optional emotion (unknown values are ignored)
                callback_url:
                  type: string
                  description: Optional webhook — receives the task object when the task completes or fails
      responses:
        "200":
          description: Task created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsTaskCreated"
              example:
                id: 0198c2f1-8a2b-7d34-b1e0-55aa0c9d21f7
                status: pending
                provider: lyra
                total_length: 1234
        "400":
          description: Invalid request (voice selection, language, credit, callback)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              examples:
                voice_selection:
                  value:
                    error: "Provide exactly one of voice_id or voice_asset_id"
                language:
                  value:
                    error: "Unsupported language"
                credit:
                  value:
                    error: "Not enough credit"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
        "503":
          description: Voice verification temporarily unavailable — retry
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "Could not verify voice_id, please try again"
    get:
      operationId: listLyraTasks
      summary: List Tasks
      description: |
        Your Lyra task history (paginated, newest first).
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Lyra]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        "200":
          description: Paginated tasks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsTaskListResponse"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/lyra/estimate:
    post:
      operationId: estimateLyraTask
      summary: Estimate Characters
      description: |
        Count the billable characters of a text **without creating a task**. Free. Lyra is billed 1 credit per character, so the cost equals `characters`.
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Lyra]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                content:
                  type: string
      responses:
        "200":
          description: Estimate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsEstimate"
        "400":
          description: Content is required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/lyra/{id}:
    get:
      operationId: getLyraTask
      summary: Get Task
      description: |
        Task detail, including original `content`. When `status` is `completed` the audio URL is in `result`.
        > **Rate limit:** 120 requests/minute
      tags: [Voice AI Beta — Lyra]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Task detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsLyraTaskDetail"
        "404":
          description: Not found (ids from other providers also return 404 here)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "Task not found"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
    delete:
      operationId: deleteLyraTask
      summary: Delete Task
      description: |
        Remove a task from history. Does **not** refund credits.
        > **Rate limit:** 30 requests/minute
      tags: [Voice AI Beta — Lyra]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deleted
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/lyra/voices:
    get:
      operationId: listLyraVoices
      summary: List Voices
      description: |
        Browse the Lyra voice catalog (cached 1 h server-side). Use an item's `id` as `voice_id` when creating a task.

        The response is relayed **verbatim** from the upstream catalog — the exact field set is not part of the GenAIPro API contract and may change. Each voice's `preview_url` is an internal proxy path (`/v1/tts/lyra/voices/sample/{id}`): prepend the API base URL to fetch the preview audio. A few voices have no preview sample upstream — for those `preview_url` is an empty string. Lyra voices have no cover images.
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Lyra]
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
        - name: starting_after
          in: query
          description: Cursor for pagination (id of the last item of the previous page)
          schema:
            type: string
        - name: q
          in: query
          description: Search voices by name (server-side)
          schema:
            type: string
        - name: gender
          in: query
          schema:
            type: string
        - name: language
          in: query
          schema:
            type: string
        - name: is_starred
          in: query
          schema:
            type: boolean
        - name: "expand[]"
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Voice catalog page (upstream-verbatim)
          content:
            application/json:
              schema:
                type: object
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
        "502":
          description: Upstream catalog error — retry later
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "Failed to load voices"

  /v1/tts/lyra/voices/sample/{id}:
    get:
      operationId: getLyraVoiceSample
      summary: Voice Sample Audio
      description: |
        Proxy for a voice's preview audio (sample paths returned by `GET /v1/tts/lyra/voices`). Returns playable audio bytes (`audio/wav`).
        > **Rate limit:** 180 requests/minute
      tags: [Voice AI Beta — Lyra]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Audio bytes
          content:
            audio/*:
              schema:
                type: string
                format: binary
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"

  # ─── Voice AI Beta — Sirius ─────────────────────────────
  /v1/tts/sirius:
    post:
      operationId: createSiriusTask
      summary: Create Speech Task
      description: |
        Create a Text-to-Speech task with Sirius. Provide **exactly one** of `voice` (preset, from `GET /v1/tts/sirius/voices`), `voice_asset_id` (your voice clone) or `voice_design_id` (your saved voice design). Preset voices additionally accept free-text `style` instructions. Cost: `⌈characters / 2⌉` credits, refunded automatically on failure.

        ### Example
        ```bash
        curl -X POST https://genaipro.io/api/v1/tts/sirius \
          -H "Authorization: Bearer YOUR_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{"content": "Xin chào, đây là GenAIPro", "voice": "arcturus", "style": "speak softly, warm tone"}'
        ```
        > **Rate limit:** 30 requests/minute
      tags: [Voice AI Beta — Sirius]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                title:
                  type: string
                  description: Optional title (derived from content when empty)
                content:
                  type: string
                  description: Text to convert to speech
                  example: "Xin chào, đây là GenAIPro"
                voice:
                  type: string
                  description: "Preset voice id (from `GET /v1/tts/sirius/voices`). Provide exactly one of `voice` / `voice_asset_id` / `voice_design_id`."
                  example: arcturus
                voice_asset_id:
                  type: string
                  description: "Voice clone id (from `POST /v1/tts/voice-assets`)."
                voice_design_id:
                  type: string
                  description: "Saved voice design id (from `POST /v1/tts/sirius/voice-designs/{id}/save`)."
                style:
                  type: string
                  maxLength: 200
                  description: "Optional plain-text delivery instructions (e.g. `speak softly, sad tone`). Preset voices only — rejected with `400` when combined with a personal voice."
                callback_url:
                  type: string
                  description: Optional webhook — receives the task object when the task completes or fails
      responses:
        "201":
          description: Task created — credits already deducted (`cost`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsTaskCreatedWithCost"
              example:
                id: 0198c2f1-9c3d-7e56-c2f1-77bb1d0e32a8
                status: pending
                provider: sirius
                total_length: 1234
                cost: 617
        "400":
          description: Invalid request (voice selection, style, credit, callback)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              examples:
                voice_selection:
                  value:
                    error: "Provide exactly one of voice, voice_asset_id or voice_design_id"
                credit:
                  value:
                    error: "Not enough credit"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
    get:
      operationId: listSiriusTasks
      summary: List Tasks
      description: |
        Your Sirius task history (paginated, newest first).
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Sirius]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        "200":
          description: Paginated tasks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsTaskListResponse"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/sirius/estimate:
    post:
      operationId: estimateSiriusTask
      summary: Estimate Cost
      description: |
        Estimate the character count and credit cost of a text **without creating a task**. Free.
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Sirius]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                content:
                  type: string
      responses:
        "200":
          description: Estimate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsEstimateWithCost"
        "400":
          description: Content is required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/sirius/{id}:
    get:
      operationId: getSiriusTask
      summary: Get Task
      description: |
        Task detail, including original `content`. When `status` is `completed` the audio URL is in `result`.
        > **Rate limit:** 120 requests/minute
      tags: [Voice AI Beta — Sirius]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Task detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsSiriusTaskDetail"
        "404":
          description: Not found (ids from other providers also return 404 here)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "Task not found"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
    delete:
      operationId: deleteSiriusTask
      summary: Delete Task
      description: |
        Remove a task from history. Does **not** refund credits.
        > **Rate limit:** 30 requests/minute
      tags: [Voice AI Beta — Sirius]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted (no body)
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/sirius/voices:
    get:
      operationId: listSiriusVoices
      summary: List Voices
      description: |
        The Sirius preset catalog (9 cross-lingual voices — every voice speaks ~10 languages; `accent` is the voice's characteristic color: `english`, `chinese`, `japanese`, `korean`). `personal_voices` additionally lists **your** ready voice designs.
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Sirius]
      responses:
        "200":
          description: Preset catalog + your personal voices
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsSiriusVoicesResponse"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/sirius/voice-designs:
    post:
      operationId: createSiriusVoiceDesign
      summary: Create Voice Design Preview
      description: |
        Describe a voice in natural language and generate a **preview** asynchronously — free, no credits. Poll `GET /v1/tts/sirius/voice-designs/{id}` until `status` is `done`, listen to `preview_url`, then save the ones you like with `POST /v1/tts/sirius/voice-designs/{id}/save`. Pass the returned `seed` again to reproduce a preview.
        > **Rate limit:** 10 requests/hour
      tags: [Voice AI Beta — Sirius]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [instructions]
              properties:
                instructions:
                  type: string
                  maxLength: 500
                  description: Natural-language description of the voice
                  example: "A deep, calm male narrator with a slight rasp, mid-40s"
                seed:
                  type: integer
                  minimum: 0
                  description: Optional seed for reproducibility (`0` or omitted = random)
      responses:
        "201":
          description: Preview generation started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVoiceDesignPreviewCreated"
        "400":
          description: Invalid instructions or seed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
    get:
      operationId: listSiriusVoiceDesigns
      summary: List Saved Voice Designs
      description: |
        Your **saved** voice designs (paginated). Use an item's `id` as `voice_design_id` when creating a task.
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Sirius]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        "200":
          description: Paginated saved designs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVoiceDesignListResponse"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/sirius/voice-designs/{id}:
    get:
      operationId: getSiriusVoiceDesign
      summary: Get Voice Design Preview
      description: |
        Poll a preview's status: `pending` → `done` | `failed`. When `done`, `preview_url` holds a playable sample (valid ~7 days; a design can still be saved after the preview file expires — saving regenerates from the seed).
        > **Rate limit:** 120 requests/minute
      tags: [Voice AI Beta — Sirius]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Preview status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVoiceDesignPreview"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
    put:
      operationId: renameSiriusVoiceDesign
      summary: Rename Voice Design
      description: |
        Rename a saved voice design.
        > **Rate limit:** 60 requests/hour
      tags: [Voice AI Beta — Sirius]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title]
              properties:
                title:
                  type: string
                  maxLength: 200
      responses:
        "200":
          description: Updated design
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVoiceDesign"
        "400":
          description: Invalid title
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
    delete:
      operationId: deleteSiriusVoiceDesign
      summary: Delete Voice Design
      description: |
        Delete a saved voice design. Fails with `409` while an unfinished task is still using it.
        > **Rate limit:** 60 requests/hour, and at most **20 deletes per 24 hours**
      tags: [Voice AI Beta — Sirius]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deleted
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "409":
          description: Design is in use by an active task
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "Voice design is in use by an active task"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/sirius/voice-designs/{id}/save:
    post:
      operationId: saveSiriusVoiceDesign
      summary: Save Voice Design
      description: |
        Save a `done` preview as a persistent voice design (max **3** saved designs per account; each preview can be saved once). The saved design is `ready` immediately — use its `id` as `voice_design_id` when creating tasks.
        > **Rate limit:** 60 requests/hour
      tags: [Voice AI Beta — Sirius]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title]
              properties:
                title:
                  type: string
                  maxLength: 200
                  description: Display name for the saved design
      responses:
        "201":
          description: Design saved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVoiceDesign"
        "400":
          description: Preview not `done`, invalid title, or design quota (3) reached
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "409":
          description: This preview has already been saved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "Voice design has already been saved"

  # ─── Voice AI Beta — Vega ───────────────────────────────
  /v1/tts/vega:
    post:
      operationId: createVegaTask
      summary: Create Speech Task
      description: |
        Create a Text-to-Speech task with Vega — the cheapest provider (`⌈characters / 10⌉` credits, refunded automatically on failure). `voice` must come from the static catalog (`GET /v1/tts/vega/voices`); Vega has no cloning. Request body is capped at 32 MiB.

        ### Example
        ```bash
        curl -X POST https://genaipro.io/api/v1/tts/vega \
          -H "Authorization: Bearer YOUR_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{"content": "Hello from GenAIPro", "voice": "aurora"}'
        ```
        > **Rate limit:** 30 requests/minute
      tags: [Voice AI Beta — Vega]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content, voice]
              properties:
                title:
                  type: string
                  description: Optional title (derived from content when empty)
                content:
                  type: string
                  description: Text to convert to speech
                  example: "Hello from GenAIPro"
                voice:
                  type: string
                  description: "Voice id from the static catalog (`GET /v1/tts/vega/voices`)"
                  example: aurora
                callback_url:
                  type: string
                  description: Optional webhook — receives the task object when the task completes or fails
      responses:
        "201":
          description: Task created — credits already deducted (`cost`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsTaskCreatedWithCost"
              example:
                id: 0198c2f1-aa4e-7f78-d3a2-99cc2e1f43b9
                status: pending
                provider: vega
                total_length: 1234
                cost: 124
        "400":
          description: Invalid request (voice, credit, callback)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              examples:
                voice:
                  value:
                    error: "Invalid voice"
                credit:
                  value:
                    error: "Not enough credit"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
    get:
      operationId: listVegaTasks
      summary: List Tasks
      description: |
        Your Vega task history (paginated, newest first).
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Vega]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        "200":
          description: Paginated tasks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsTaskListResponse"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/vega/estimate:
    post:
      operationId: estimateVegaTask
      summary: Estimate Cost
      description: |
        Estimate the character count and credit cost of a text **without creating a task**. Free.
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Vega]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                content:
                  type: string
      responses:
        "200":
          description: Estimate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsEstimateWithCost"
        "400":
          description: Content is required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/vega/{id}:
    get:
      operationId: getVegaTask
      summary: Get Task
      description: |
        Task detail, including original `content`. When `status` is `completed` the audio URL is in `result`.
        > **Rate limit:** 120 requests/minute
      tags: [Voice AI Beta — Vega]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Task detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVegaTaskDetail"
        "404":
          description: Not found (ids from other providers also return 404 here)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
              example:
                error: "Task not found"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"
    delete:
      operationId: deleteVegaTask
      summary: Delete Task
      description: |
        Remove a task from history. Does **not** refund credits.
        > **Rate limit:** 30 requests/minute
      tags: [Voice AI Beta — Vega]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted (no body)
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsError"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  /v1/tts/vega/voices:
    get:
      operationId: listVegaVoices
      summary: List Voices
      description: |
        The static Vega catalog — 27 English voices (US/UK accents), sorted by quality grade. `cover_image` and `sample_url` are direct URLs (no proxy).
        > **Rate limit:** 60 requests/minute
      tags: [Voice AI Beta — Vega]
      responses:
        "200":
          description: Voice catalog
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsVegaVoicesResponse"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsRateLimitError"

  # ─── Veo V2 ─────────────────────────────────────────────
  /v2/veo/credits:
    get:
      operationId: getVeoCreditsV2
      summary: Get Veo Credits
      description: Get Veo credit quota and usage, broken down by package.
      tags: [Veo V2]
      responses:
        "200":
          description: Credits list by package
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    quota:
                      type: integer
                      description: Total credit quota for this package
                      example: 50
                    used:
                      type: integer
                      description: Number of credits used from this package
                      example: 10
                    expires_at:
                      type: ["string", "null"]
                      format: date-time
                      description: Package expiration date (ISO 8601). `null` if the package never expires.
                      example: "2026-06-01T00:00:00Z"
              example:
                - quota: 50
                  used: 10
                  expires_at: "2026-06-01T00:00:00Z"
                - quota: 50
                  used: 5
                  expires_at: "2026-07-01T00:00:00Z"

  /v2/veo/histories:
    get:
      operationId: getVeoHistoriesV2
      summary: Veo History
      description: |
        Get paginated video/image generation history.

        > **Rate limit:** 30 requests/minute
      tags: [Veo V2]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
          description: Page number for pagination
        - name: page_size
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
          description: Number of items per page (1–100)
      responses:
        "200":
          description: Paginated history
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/VeoV2TaskStatus"
                  total:
                    type: integer
                    description: Total number of items
                  page:
                    type: integer
                    description: Current page number
                  page_size:
                    type: integer
                    description: Number of items per page
                  total_pages:
                    type: integer
                    description: Total number of pages
              example:
                data:
                  - id: 550e8400-e29b-41d4-a716-446655440000
                    prompt: A cat walking on the beach at sunset
                    file_urls:
                      - https://files.genaipro.io/video_xxx.mp4
                    status: completed
                    created_at: "2026-04-03T10:00:00Z"
                  - id: 550e8400-e29b-41d4-a716-446655440001
                    prompt: A drone flying over mountains
                    file_urls: []
                    status: processing
                    created_at: "2026-04-03T10:05:00Z"
                total: 42
                page: 1
                page_size: 20
                total_pages: 3

  /v2/veo/tasks/{id}:
    get:
      operationId: getVeoTaskV2
      summary: Get Task Status
      description: |
        Poll the status of a video/image generation task. Use the task ID returned from any creation endpoint.

        ### Status values

        | Status | Description |
        |--------|-------------|
        | `processing` | Task is being processed |
        | `completed` | Done — `file_urls` contains the generated files |
        | `failed` | Error — see `error` field for details. Credits are automatically refunded |

        > **Rate limit:** 30 requests/minute
      tags: [Veo V2]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Task ID received from a creation endpoint
      responses:
        "200":
          description: Task status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2TaskStatus"
              examples:
                processing:
                  summary: Task is processing
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440000
                    prompt: A cat walking on the beach at sunset
                    file_urls: []
                    status: processing
                    created_at: "2026-04-03T10:00:00Z"
                completed_video:
                  summary: Video generation completed
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440000
                    prompt: A cat walking on the beach at sunset
                    file_urls:
                      - https://files.genaipro.io/video_550e8400.mp4
                    status: completed
                    created_at: "2026-04-03T10:00:00Z"
                completed_images:
                  summary: Image generation completed (multiple files)
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440002
                    prompt: A futuristic city at night
                    file_urls:
                      - https://files.genaipro.io/image_xxx_0.png
                      - https://files.genaipro.io/image_xxx_1.png
                    status: completed
                    created_at: "2026-04-03T10:00:00Z"
                failed:
                  summary: Task failed
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440003
                    prompt: A cat walking on the beach at sunset
                    file_urls: []
                    status: failed
                    error: Video generation timeout
                    created_at: "2026-04-03T10:00:00Z"
        "404":
          description: Task not found or does not belong to the authenticated user
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2RateLimitError"

  /v2/veo/text-to-video:
    post:
      operationId: textToVideoV2
      summary: Text to Video
      description: |
        Generate one or more videos from a text prompt. Returns immediately with `202 Accepted` and task IDs for polling.

        ### Example

        ```bash
        curl -X POST https://genaipro.io/api/v2/veo/text-to-video \
          -H "Authorization: Bearer YOUR_TOKEN" \
          -H "Content-Type: application/json" \
          -d '{
            "prompt": "A drone flying over mountains",
            "aspect_ratio": "VIDEO_ASPECT_RATIO_LANDSCAPE",
            "number_of_videos": 1
          }'
        ```

        > **Rate limit:** 30 requests/minute
      tags: [Veo V2]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [prompt, aspect_ratio, number_of_videos]
              properties:
                prompt:
                  type: string
                  description: Text description of the video to generate
                  example: A cat walking on the beach at sunset
                aspect_ratio:
                  type: string
                  description: Video aspect ratio
                  enum:
                    - VIDEO_ASPECT_RATIO_LANDSCAPE
                    - VIDEO_ASPECT_RATIO_PORTRAIT
                  example: VIDEO_ASPECT_RATIO_LANDSCAPE
                number_of_videos:
                  type: integer
                  description: Number of videos to generate (1–4)
                  minimum: 1
                  maximum: 4
                  example: 2
                upscale_resolution:
                  type: string
                  description: |
                    Optional upscale resolution. Leave empty or `none` to disable upscale.
                    `4k` is not supported for video and returns 400.
                  enum:
                    - ""
                    - none
                    - 1080p
                  default: ""
                callback_url:
                  type: string
                  description: Optional webhook URL. When the task reaches a terminal state (`completed` or `failed`), GenAIPro POSTs the task status (same shape as `GET /v2/veo/tasks/{id}`) to this URL.
      responses:
        "202":
          description: Task accepted for processing. Returns exactly one history item representing the parent task; use its `id` to poll `GET /v2/veo/tasks/{id}`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  histories:
                    type: array
                    items:
                      $ref: "#/components/schemas/VeoV2History"
              example:
                histories:
                  - id: 550e8400-e29b-41d4-a716-446655440000
                    prompt: A drone flying over mountains
                    file_url: ""
                    status: processing
                    created_at: "2026-04-03T10:00:00Z"
        "400":
          description: Invalid request (missing fields, invalid format)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2Error"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2RateLimitError"

  /v2/veo/frames-to-video:
    post:
      operationId: framesToVideoV2
      summary: Frames to Video
      description: |
        Generate video from a start image (required) and optional end image, guided by a text prompt. Returns immediately with `202 Accepted`.

        ### Example

        ```bash
        curl -X POST https://genaipro.io/api/v2/veo/frames-to-video \
          -H "Authorization: Bearer YOUR_TOKEN" \
          -F "start_image=@photo.jpg" \
          -F "prompt=Animate this photo with gentle motion" \
          -F "aspect_ratio=VIDEO_ASPECT_RATIO_LANDSCAPE" \
          -F "number_of_videos=1"
        ```

        > **Rate limit:** 30 requests/minute
      tags: [Veo V2]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [start_image, prompt, aspect_ratio, number_of_videos]
              properties:
                start_image:
                  type: string
                  format: binary
                  description: Start frame image file (max 20MB, JPEG/PNG/WEBP)
                end_image:
                  type: string
                  format: binary
                  description: End frame image file (optional, max 20MB, JPEG/PNG/WEBP)
                prompt:
                  type: string
                  description: Text description to guide the video generation
                aspect_ratio:
                  type: string
                  description: Video aspect ratio
                  enum:
                    - VIDEO_ASPECT_RATIO_LANDSCAPE
                    - VIDEO_ASPECT_RATIO_PORTRAIT
                number_of_videos:
                  type: string
                  description: Number of videos to generate (1–4). Sent as string in form data.
                upscale_resolution:
                  type: string
                  description: |
                    Optional upscale resolution. Leave empty or `none` to disable upscale.
                    `4k` is not supported for video and returns 400.
                  enum:
                    - ""
                    - none
                    - 1080p
                  default: ""
                callback_url:
                  type: string
                  description: Optional webhook URL. When the task reaches a terminal state (`completed` or `failed`), GenAIPro POSTs the task status (same shape as `GET /v2/veo/tasks/{id}`) to this URL.
      responses:
        "202":
          description: Task accepted for processing. Returns exactly one history item representing the parent task; use its `id` to poll `GET /v2/veo/tasks/{id}`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  histories:
                    type: array
                    items:
                      $ref: "#/components/schemas/VeoV2History"
        "400":
          description: Invalid request (missing fields, file too large, invalid format)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2Error"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2RateLimitError"

  /v2/veo/ingredients-to-video:
    post:
      operationId: ingredientsToVideoV2
      summary: Ingredients to Video
      description: |
        Generate video from 1–3 reference images and a text prompt. Returns immediately with `202 Accepted`.

        > **Rate limit:** 30 requests/minute
      tags: [Veo V2]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [reference_images, prompt, number_of_videos]
              properties:
                reference_images:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: Reference image files (1–3 images, each max 20MB)
                  minItems: 1
                  maxItems: 3
                prompt:
                  type: string
                  description: Text description to guide the video generation
                aspect_ratio:
                  type: string
                  description: Video aspect ratio
                  enum:
                    - VIDEO_ASPECT_RATIO_LANDSCAPE
                    - VIDEO_ASPECT_RATIO_PORTRAIT
                  default: VIDEO_ASPECT_RATIO_LANDSCAPE
                number_of_videos:
                  type: string
                  description: Number of videos to generate (1–4). Sent as string in form data.
                upscale_resolution:
                  type: string
                  description: |
                    Optional upscale resolution. Leave empty or `none` to disable upscale.
                    `4k` is not supported for video and returns 400.
                  enum:
                    - ""
                    - none
                    - 1080p
                  default: ""
                callback_url:
                  type: string
                  description: Optional webhook URL. When the task reaches a terminal state (`completed` or `failed`), GenAIPro POSTs the task status (same shape as `GET /v2/veo/tasks/{id}`) to this URL.
      responses:
        "202":
          description: Task accepted for processing. Returns exactly one history item representing the parent task; use its `id` to poll `GET /v2/veo/tasks/{id}`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  histories:
                    type: array
                    items:
                      $ref: "#/components/schemas/VeoV2History"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2Error"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2RateLimitError"

  /v2/veo/create-image:
    post:
      operationId: createImageV2
      summary: Create Image
      description: |
        Generate one or more AI images from a text prompt, with optional reference images. Returns immediately with `202 Accepted`.

        ### Example

        ```bash
        curl -X POST https://genaipro.io/api/v2/veo/create-image \
          -H "Authorization: Bearer YOUR_TOKEN" \
          -F "prompt=A futuristic city at night" \
          -F "number_of_images=2"
        ```

        ### Example — override model

        ```bash
        curl -X POST https://genaipro.io/api/v2/veo/create-image \
          -H "Authorization: Bearer YOUR_TOKEN" \
          -F "prompt=A futuristic city at night" \
          -F "number_of_images=2" \
          -F "model=nano_banana_2"
        ```

        > **Rate limit:** 30 requests/minute
      tags: [Veo V2]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [prompt]
              properties:
                prompt:
                  type: string
                  description: Text description of the image to generate
                reference_images:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: Optional reference image files (0–5 images, each max 20MB)
                  maxItems: 5
                aspect_ratio:
                  type: string
                  description: "Image aspect ratio. `SQUARE` = 1:1, `LANDSCAPE` = 16:9, `LANDSCAPE_FOUR_THREE` = 4:3, `PORTRAIT` = 9:16, `PORTRAIT_THREE_FOUR` = 3:4."
                  enum:
                    - IMAGE_ASPECT_RATIO_LANDSCAPE
                    - IMAGE_ASPECT_RATIO_LANDSCAPE_FOUR_THREE
                    - IMAGE_ASPECT_RATIO_SQUARE
                    - IMAGE_ASPECT_RATIO_PORTRAIT_THREE_FOUR
                    - IMAGE_ASPECT_RATIO_PORTRAIT
                  default: IMAGE_ASPECT_RATIO_LANDSCAPE
                number_of_images:
                  type: string
                  description: Number of images to generate (1–4). Sent as string in form data.
                  default: "1"
                model:
                  type: string
                  enum:
                    - nano_banana_pro
                    - nano_banana_2
                    - imagen_4
                  default: nano_banana_pro
                  example: nano_banana_2
                upscale_resolution:
                  type: string
                  description: |
                    Optional upscale resolution. Leave empty or `none` to disable upscale.
                  enum:
                    - ""
                    - none
                    - 2k
                    - 4k
                  default: ""
                callback_url:
                  type: string
                  description: Optional webhook URL. When the task reaches a terminal state (`completed` or `failed`), GenAIPro POSTs the task status (same shape as `GET /v2/veo/tasks/{id}`) to this URL.
      responses:
        "202":
          description: Task accepted for processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2TaskStatus"
              example:
                id: 550e8400-e29b-41d4-a716-446655440000
                prompt: A futuristic city at night
                file_urls: []
                status: processing
                created_at: "2026-04-03T10:00:00Z"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2Error"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2RateLimitError"

  # ─── ChatGPT Image ──────────────────────────────────────
  /v2/gt-mage/create-image:
    post:
      operationId: createChatgptImage
      summary: Create Image
      description: |
        Generate one or more AI images from a text prompt, with optional reference images. Returns immediately with `202 Accepted` and a task ID for polling.

        When one or more `reference_images` are supplied, the request is treated as an **image edit**; otherwise it is a plain **generation**.

        ### Example

        ```bash
        curl -X POST https://genaipro.io/api/v2/gt-mage/create-image \
          -H "Authorization: Bearer YOUR_TOKEN" \
          -F "prompt=A futuristic city at night" \
          -F "size=16:9" \
          -F "n=2"
        ```

        ### Example — with reference images (edit)

        ```bash
        curl -X POST https://genaipro.io/api/v2/gt-mage/create-image \
          -H "Authorization: Bearer YOUR_TOKEN" \
          -F "prompt=Turn this into a watercolor painting" \
          -F "reference_images=@photo.jpg"
        ```

        > **Rate limit:** 30 requests/minute
      tags: [ChatGPT Image]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [prompt]
              properties:
                prompt:
                  type: string
                  description: Text description of the image to generate
                reference_images:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: Optional reference image files (0–3 images, each max 50MB). When provided, the request runs as an image edit.
                  maxItems: 3
                size:
                  type: string
                  description: Image aspect ratio
                  enum:
                    - "1:1"
                    - "2:3"
                    - "3:2"
                    - "3:4"
                    - "4:3"
                    - "9:16"
                    - "16:9"
                  default: "1:1"
                n:
                  type: string
                  description: Number of images to generate (1–4). Sent as string in form data.
                  default: "1"
                callback_url:
                  type: string
                  description: Optional webhook URL. When the task reaches a terminal state (`completed` or `failed`), GenAIPro POSTs the task status (same shape as `GET /v2/gt-mage/tasks/{id}`) to this URL.
      responses:
        "202":
          description: Task accepted for processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2TaskStatus"
              example:
                id: 550e8400-e29b-41d4-a716-446655440000
                prompt: A futuristic city at night
                file_urls: []
                status: processing
                created_at: "2026-04-03T10:00:00Z"
        "400":
          description: Invalid request (missing prompt, invalid size/n, too many or oversized reference images)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2Error"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2RateLimitError"

  /v2/gt-mage/histories:
    get:
      operationId: getChatgptImageHistories
      summary: Image History
      description: |
        Get paginated image generation history.

        > **Rate limit:** 30 requests/minute
      tags: [ChatGPT Image]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
          description: Page number for pagination
        - name: page_size
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
          description: Number of items per page (1–100)
      responses:
        "200":
          description: Paginated history
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/VeoV2TaskStatus"
                  total:
                    type: integer
                    description: Total number of items
                  page:
                    type: integer
                    description: Current page number
                  page_size:
                    type: integer
                    description: Number of items per page
                  total_pages:
                    type: integer
                    description: Total number of pages
              example:
                data:
                  - id: 550e8400-e29b-41d4-a716-446655440000
                    prompt: A futuristic city at night
                    file_urls:
                      - https://files.genaipro.io/gtmage_xxx_0.png
                      - https://files.genaipro.io/gtmage_xxx_1.png
                    status: completed
                    created_at: "2026-04-03T10:00:00Z"
                total: 12
                page: 1
                page_size: 20
                total_pages: 1

  /v2/gt-mage/tasks/{id}:
    get:
      operationId: getChatgptImageTask
      summary: Get Task Status
      description: |
        Poll the status of an image generation task. Use the task ID returned from `POST /v2/gt-mage/create-image`.

        ### Status values

        | Status | Description |
        |--------|-------------|
        | `processing` | Task is being processed |
        | `completed` | Done — `file_urls` contains the generated images |
        | `failed` | Error — see `error` field for details. Credits are automatically refunded |

        > **Rate limit:** 30 requests/minute
      tags: [ChatGPT Image]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Task ID received from the create endpoint
      responses:
        "200":
          description: Task status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2TaskStatus"
              examples:
                processing:
                  summary: Task is processing
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440000
                    prompt: A futuristic city at night
                    file_urls: []
                    status: processing
                    created_at: "2026-04-03T10:00:00Z"
                completed:
                  summary: Image generation completed
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440000
                    prompt: A futuristic city at night
                    file_urls:
                      - https://files.genaipro.io/gtmage_xxx_0.png
                      - https://files.genaipro.io/gtmage_xxx_1.png
                    status: completed
                    created_at: "2026-04-03T10:00:00Z"
                failed:
                  summary: Task failed
                  value:
                    id: 550e8400-e29b-41d4-a716-446655440003
                    prompt: A futuristic city at night
                    file_urls: []
                    status: failed
                    error: Image generation failed, please try again
                    created_at: "2026-04-03T10:00:00Z"
        "404":
          description: Task not found or does not belong to the authenticated user
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VeoV2RateLimitError"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "Get your API token at [genaipro.io](https://genaipro.io) → Avatar → Manage Account → API Key"

  schemas:
    Voice:
      type: object
      properties:
        public_owner_id:
          type: string
        voice_id:
          type: string
          description: Unique voice identifier
        date_unix:
          type: integer
        name:
          type: string
          description: Voice display name
        accent:
          type: string
          description: Voice accent
        gender:
          type: string
          description: Voice gender
        age:
          type: string
          description: Voice age group
        descriptive:
          type: string
          description: Descriptive tags
        use_case:
          type: string
          description: Recommended use case
        category:
          type: string
          description: Voice category
          enum: [generated, cloned, premade, professional, famous, high_quality]
        language:
          type: string
          description: Primary language
        locale:
          type: ["string", "null"]
          description: Voice locale
        description:
          type: string
          description: Voice description
        preview_url:
          type: string
          description: URL to preview audio sample
        image_url:
          type: string
          description: Voice avatar image URL
        rate:
          type: number
          description: Voice usage rate
        free_users_allowed:
          type: boolean
        featured:
          type: boolean
        verified_languages:
          type: array
          items:
            type: object
            properties:
              language:
                type: string
                description: Language code
              model_id:
                type: string
                description: Model identifier
              accent:
                type: string
                description: Accent for this language
              locale:
                type: ["string", "null"]
              preview_url:
                type: string
                description: Preview audio URL for this language

    LabsTask:
      type: object
      properties:
        id:
          type: string
          description: Task ID
        input:
          type: string
          description: Input text
        voice_id:
          type: string
          description: Voice ID used
        model_id:
          type: string
          description: Model ID used
        style:
          type: number
          description: Style value
        speed:
          type: number
          description: Speed value
        use_speaker_boost:
          type: boolean
          description: Whether speaker boost was enabled
        similarity:
          type: number
          description: Similarity value
        stability:
          type: number
          description: Stability value
        created_at:
          type: string
          format: date-time
          description: Creation time (ISO 8601)
        status:
          type: string
          description: "Status: `processing`, `completed`"
          enum: [processing, completed]
        result:
          type: string
          description: Audio file URL (available when completed)
        subtitle:
          type: string
          description: Subtitle file URL (available when completed and exported)

    VeoV2History:
      type: object
      description: History item returned from V2 creation endpoints
      properties:
        id:
          type: string
          description: Task ID (use for polling via `GET /v2/veo/tasks/{id}`)
          example: 550e8400-e29b-41d4-a716-446655440000
        prompt:
          type: string
          description: The prompt used for generation
          example: A cat walking on the beach at sunset
        file_url:
          type: string
          description: URL of the generated file (empty while processing)
          example: ""
        status:
          type: string
          description: Task status
          enum: [processing, completed, failed]
          example: processing
        created_at:
          type: string
          format: date-time
          description: Timestamp when the task was created
          example: "2026-04-03T10:00:00Z"

    VeoV2TaskStatus:
      type: object
      description: Task status returned from polling and history endpoints
      properties:
        id:
          type: string
          description: Task ID
          example: 550e8400-e29b-41d4-a716-446655440000
        prompt:
          type: string
          description: The prompt used for generation
          example: A cat walking on the beach at sunset
        file_urls:
          type: array
          items:
            type: string
          description: URLs of the generated files (empty while processing)
          example: []
        status:
          type: string
          description: Task status
          enum: [processing, completed, failed]
        error:
          type: string
          description: Error message (only present when status is `failed`)
        created_at:
          type: string
          format: date-time
          description: Timestamp when the task was created

    VeoV2Error:
      type: object
      properties:
        error:
          type: string
          description: Error message
          example: "Invalid aspect_ratio value"

    VeoV2RateLimitError:
      type: object
      properties:
        error:
          type: string
          description: Error message
          example: "Too many requests, try again in 45s"
        message:
          type: string
          description: Detailed rate limit message
          example: "Rate limit exceeded. Try again in 45s"

    TtsTaskStatus:
      type: string
      description: |
        Voice AI Beta task lifecycle: `pending` → `cloning` (clone flow only) → `queued` → `processing` → `merging` → `process_merging` → `completed` | `failed`.
      enum: [pending, cloning, queued, processing, merging, process_merging, completed, failed]
      example: completed

    TtsTask:
      type: object
      description: |
        Voice AI Beta task (list item). This is also the payload of the `tts_<provider>_task_updated` WebSocket event and of the `callback_url` webhook POST.
      properties:
        id:
          type: string
          description: Task ID
          example: 0198c2f1-7b7a-7c11-a3f2-3e9d2b1c4a55
        title:
          type: string
          description: Task title
        status:
          $ref: "#/components/schemas/TtsTaskStatus"
        provider:
          type: string
          description: "Provider: `nova`, `lyra`, `sirius`, `vega` (other values may appear as new providers launch)"
          example: nova
        result:
          type: string
          description: Audio file URL (empty until `completed`)
          example: "https://files.genaipro.io/0198c2f1.mp3"
        total_length:
          type: integer
          description: Billable characters (encoder units ≈ UTF-8 bytes — see the Voice AI Beta tag)
          example: 1234
        process_percentage:
          type: integer
          minimum: 0
          maximum: 100
          description: Progress percentage
        error:
          type: string
          description: Failure reason (only when `status` is `failed`)
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    TtsTaskListResponse:
      type: object
      description: Paginated task list (per-provider lists and the merged `/v1/tts/history`)
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/TtsTask"
        total:
          type: integer
          description: Total number of tasks
        page:
          type: integer
        limit:
          type: integer

    TtsTaskCreated:
      type: object
      description: Create-task acknowledgement
      properties:
        id:
          type: string
          description: Task ID (poll via the provider's `GET /{id}`)
        status:
          $ref: "#/components/schemas/TtsTaskStatus"
        provider:
          type: string
          example: lyra
        total_length:
          type: integer
          description: Billable characters (encoder units ≈ UTF-8 bytes)

    TtsTaskCreatedWithCost:
      allOf:
        - $ref: "#/components/schemas/TtsTaskCreated"
        - type: object
          properties:
            cost:
              type: integer
              description: Credits deducted for this task

    TtsEstimate:
      type: object
      properties:
        characters:
          type: integer
          description: Billable characters (encoder units ≈ UTF-8 bytes — ASCII = 1, most CJK = 3, emoji = 4)
          example: 1234

    TtsEstimateWithCost:
      allOf:
        - $ref: "#/components/schemas/TtsEstimate"
        - type: object
          properties:
            cost:
              type: integer
              description: Credits this text would cost with this provider
              example: 864

    TtsNovaTaskDetail:
      allOf:
        - $ref: "#/components/schemas/TtsTask"
        - type: object
          properties:
            content:
              type: string
              description: Original input text
            model_id:
              type: string
              description: Preset voice used (empty when a clone was used)
            voice_asset_id:
              type: string
              description: Voice clone used (only present on clone tasks)
            format:
              type: string
              example: mp3
            normalize:
              type: boolean

    TtsLyraTaskDetail:
      allOf:
        - $ref: "#/components/schemas/TtsTask"
        - type: object
          properties:
            content:
              type: string
              description: Original input text
            voice_id:
              type: string
              description: Preset voice used (empty when a clone was used)
            voice_asset_id:
              type: string
              description: Voice clone used (only present on clone tasks)
            format:
              type: string
              example: mp3
            language:
              type: string
              example: vi
            speed:
              type: number
            volume:
              type: number
            emotion:
              type: string

    TtsSiriusTaskDetail:
      allOf:
        - $ref: "#/components/schemas/TtsTask"
        - type: object
          properties:
            content:
              type: string
              description: Original input text
            voice:
              type: string
              description: Preset voice used (empty when a personal voice was used)
            voice_asset_id:
              type: string
              description: Voice clone used (only present on clone tasks)
            voice_design_id:
              type: string
              description: Voice design used (only present on design tasks)
            style:
              type: string
              description: Style instructions applied (only present when provided)
            format:
              type: string
              example: mp3

    TtsVegaTaskDetail:
      allOf:
        - $ref: "#/components/schemas/TtsTask"
        - type: object
          properties:
            content:
              type: string
              description: Original input text
            voice:
              type: string
              description: Catalog voice used
              example: aurora
            format:
              type: string
              example: mp3

    TtsVoiceAsset:
      type: object
      description: An uploaded voice-clone sample (shared across Nova, Lyra and Sirius)
      properties:
        id:
          type: string
          description: Use as `voice_asset_id` when creating a task
        title:
          type: string
        format:
          type: string
          example: mp3
        size_bytes:
          type: integer
        duration_ms:
          type: integer
        cover_image:
          type: string
          description: Cover image URL
        sample_url:
          type: string
          description: Playback URL of the uploaded sample
        status:
          type: string
          enum: [ready, failed]
        created_at:
          type: string
          format: date-time

    TtsVoiceAssetListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/TtsVoiceAsset"
        total:
          type: integer
        page:
          type: integer
        limit:
          type: integer

    TtsSiriusVoice:
      type: object
      description: A Sirius preset voice (cross-lingual — speaks ~10 languages)
      properties:
        id:
          type: string
          description: Use as `voice` when creating a task
          example: arcturus
        name:
          type: string
          example: Arcturus
        gender:
          type: string
          enum: [female, male]
        accent:
          type: string
          description: "Characteristic accent color: `english`, `chinese`, `japanese`, `korean`"
        style:
          type: string
          description: Short voice descriptor (display only)
          example: warm
        grade:
          type: string
          description: Quality grade (display only)
          example: A
        cover_image:
          type: string
        sample_url:
          type: string
          description: Preview audio URL (may be empty)
        tags:
          type: array
          items:
            type: string

    TtsPersonalVoice:
      type: object
      description: One of your saved voice designs, as listed in `personal_voices`
      properties:
        id:
          type: string
          description: Use as `voice_design_id` when creating a task
        title:
          type: string
        cover_image:
          type: string
        created_at:
          type: string
          format: date-time

    TtsSiriusVoicesResponse:
      type: object
      properties:
        items:
          type: array
          description: Public preset catalog
          items:
            $ref: "#/components/schemas/TtsSiriusVoice"
        total:
          type: integer
        personal_voices:
          type: array
          description: Your ready voice designs (empty when none)
          items:
            $ref: "#/components/schemas/TtsPersonalVoice"

    TtsVegaVoice:
      type: object
      description: A Vega catalog voice
      properties:
        id:
          type: string
          description: Use as `voice` when creating a task
          example: aurora
        name:
          type: string
          example: Aurora
        gender:
          type: string
          enum: [female, male]
        accent:
          type: string
          enum: [us, uk]
        grade:
          type: string
          description: Quality grade (catalog is sorted by grade, descending)
          example: A
        cover_image:
          type: string
        sample_url:
          type: string
          description: Preview audio URL

    TtsVegaVoicesResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/TtsVegaVoice"
        total:
          type: integer
          example: 27

    TtsVoiceDesignPreviewCreated:
      type: object
      description: Acknowledgement of a voice-design preview request
      properties:
        id:
          type: string
          description: Preview ID (poll via `GET /v1/tts/sirius/voice-designs/{id}`)
        seed:
          type: integer
          description: Final seed used (your seed, or a random one when omitted/`0`)
        status:
          type: string
          example: pending

    TtsVoiceDesignPreview:
      type: object
      description: Voice-design preview status
      properties:
        id:
          type: string
        instructions:
          type: string
        seed:
          type: integer
        status:
          type: string
          enum: [pending, done, failed]
        preview_url:
          type: string
          description: Playable sample (present when `done`; the file expires after ~7 days)
        error:
          type: string
          description: Failure reason (only when `failed`)
        created_at:
          type: string
          format: date-time

    TtsVoiceDesign:
      type: object
      description: A saved voice design
      properties:
        id:
          type: string
          description: Use as `voice_design_id` when creating a task
        title:
          type: string
        design_instructions:
          type: string
        cover_image:
          type: string
        sample_url:
          type: string
          description: Preview audio URL (may be empty)
        status:
          type: string
          enum: [ready, failed]
        created_at:
          type: string
          format: date-time

    TtsVoiceDesignListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/TtsVoiceDesign"
        total:
          type: integer
        page:
          type: integer
        limit:
          type: integer

    TtsError:
      type: object
      properties:
        error:
          type: string
          description: Error message
          example: "Not enough credit"

    TtsRateLimitError:
      type: object
      properties:
        error:
          type: string
          description: Error message
          example: "Too many requests, try again in 45s"
        message:
          type: string
          description: Detailed rate limit message
          example: "Rate limit exceeded. Try again in 45s"
