{
  "openapi": "3.1.0",
  "info": {
    "title": "UGC Copilot Public REST API",
    "description": "Programmatic access to UGC Copilot's market research, script generation, image generation,\nand video rendering pipeline. The same backend that powers the web app at https://www.ugccopilot.ai.\n\nAuthentication is a single bearer API key (prefix `ugc_live_`) issued from the web UI under\n**Profile → API Keys**. Pay-as-you-go: any account with a positive credit balance can call\nthe API — no subscription required.\n\nErrors follow a uniform `{ error: { type, code, message, request_id, ... } }` shape. Use\n`Idempotency-Key` on mutating calls to dedupe retries. Long-running video renders are async\n— register a webhook to skip polling, or poll `proxyCheckVideoStatus` with the cadence\ndocumented in the API reference.\n\nSee `docs/api-reference.md` for narrative documentation, recipes, and gotchas.\n",
    "version": "2026-04-29",
    "contact": {
      "name": "UGC Copilot Support",
      "url": "https://www.ugccopilot.ai/feedback"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.ugccopilot.ai/terms"
    }
  },
  "servers": [
    {
      "url": "https://us-central1-viral-ugc-copilot.cloudfunctions.net",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "market-research",
      "description": "Trending products and market analysis for an industry."
    },
    {
      "name": "scripts",
      "description": "AI-generated viral scripts and parsing."
    },
    {
      "name": "images",
      "description": "Scene image, twin avatar, and social asset generation."
    },
    {
      "name": "video",
      "description": "Async video render lifecycle (start, check, fetch)."
    },
    {
      "name": "post-production",
      "description": "Stitching, text overlays, overlay suggestions."
    },
    {
      "name": "twins",
      "description": "Reusable AI Twin (creator persona) CRUD."
    },
    {
      "name": "analysis",
      "description": "Voice analysis and reference-video understanding."
    },
    {
      "name": "library",
      "description": "Saved video retrieval."
    }
  ],
  "paths": {
    "/proxyFetchTopSellingProducts": {
      "post": {
        "tags": [
          "market-research"
        ],
        "summary": "Fetch trending products in an industry",
        "description": "Returns 5–8 trending products with brand, price, ad-creative angle, and ideal influencer profile.\nCost: 1 credit (first call ever on the account is free via `hasUsedFreeAnalysis` flag).\n",
        "operationId": "fetchTopSellingProducts",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "industry"
                ],
                "properties": {
                  "industry": {
                    "type": "string",
                    "description": "Free-form industry label (e.g. \"fitness\", \"skincare\", \"AI tools\").",
                    "minLength": 1,
                    "maxLength": 100
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Array of products with creative metadata.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "products": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TrendingProduct"
                      }
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyFetchFullMarketAnalysis": {
      "post": {
        "tags": [
          "market-research"
        ],
        "summary": "Fetch a full market analysis for an industry",
        "description": "Returns trending products, competitor angles, audience segments, and creative recommendations.\nCost: 1 credit.\n",
        "operationId": "fetchFullMarketAnalysis",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "industry"
                ],
                "properties": {
                  "industry": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Full market analysis result.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MarketAnalysis"
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyGenerateViralScript": {
      "post": {
        "tags": [
          "scripts"
        ],
        "summary": "Generate a viral script",
        "description": "Returns hook variations, scenes (visual prompts + script text + sound cues), CTAs,\nand platform variations. Cost: 1 credit.\n",
        "operationId": "generateViralScript",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScriptGenerateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Script result.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScriptResult"
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyRegenerateScriptWithTone": {
      "post": {
        "tags": [
          "scripts"
        ],
        "summary": "Regenerate a script with a different tone",
        "description": "Cost 1 credit. Provide the original script and a target tone (e.g. \"humorous\", \"urgent\", \"educational\").",
        "operationId": "regenerateScriptWithTone",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "script",
                  "tone"
                ],
                "properties": {
                  "script": {
                    "$ref": "#/components/schemas/ScriptResult"
                  },
                  "tone": {
                    "type": "string",
                    "description": "Target tone keyword.",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Regenerated script.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScriptResult"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyParseOwnScript": {
      "post": {
        "tags": [
          "scripts"
        ],
        "summary": "Parse a user-written script into structured format",
        "description": "Takes raw script text (10–10,000 chars) and returns the structured ScriptResult shape with\nauto-generated visuals, sounds, and platform variations. Cost: 1 credit.\n",
        "operationId": "parseOwnScript",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rawScript"
                ],
                "properties": {
                  "rawScript": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 10000
                  },
                  "twinDescription": {
                    "type": "string"
                  },
                  "productDescription": {
                    "type": "string"
                  },
                  "isFaceless": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Parsed script.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScriptResult"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyGenerateImage": {
      "post": {
        "tags": [
          "images"
        ],
        "summary": "Generate a product or scene image",
        "description": "Cost: 1 credit (standard) / 2 credits (HQ). First image generation ever on the account\nis free via `hasUsedFreeImageGen` flag.\n",
        "operationId": "generateImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "prompt"
                ],
                "properties": {
                  "prompt": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4000
                  },
                  "quality": {
                    "type": "string",
                    "enum": [
                      "standard",
                      "hq"
                    ],
                    "default": "standard"
                  },
                  "aspectRatio": {
                    "type": "string",
                    "enum": [
                      "1:1",
                      "9:16",
                      "16:9",
                      "4:5"
                    ],
                    "default": "9:16"
                  },
                  "referenceImageUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "Optional reference image (e.g. product photo) to guide generation."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Permanent Firebase Storage URL for the generated image (JSON-encoded string).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "format": "uri",
                  "description": "HTTPS URL to the generated image hosted on Firebase Storage."
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyGenerateSceneImage": {
      "post": {
        "tags": [
          "images"
        ],
        "summary": "Generate an image for a specific scene",
        "description": "Tied to a script's scene context. Cost: 1 credit (standard) / 2 credits (HQ).\n",
        "operationId": "generateSceneImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "visualPrompt"
                ],
                "properties": {
                  "visualPrompt": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4000
                  },
                  "quality": {
                    "type": "string",
                    "enum": [
                      "standard",
                      "hq"
                    ],
                    "default": "standard"
                  },
                  "aspectRatio": {
                    "type": "string",
                    "enum": [
                      "1:1",
                      "9:16",
                      "16:9",
                      "4:5"
                    ],
                    "default": "9:16"
                  },
                  "influencerImageUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "productImageUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "referenceImageUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "projectMode": {
                    "type": "string",
                    "enum": [
                      "product-ad",
                      "ugc-creator",
                      "podcast-style",
                      "influencer-noproduct",
                      "vlog",
                      "clone-video",
                      "own-script"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Permanent Firebase Storage URL for the generated scene image (JSON-encoded string).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "format": "uri",
                  "description": "HTTPS URL to the generated scene image hosted on Firebase Storage."
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyEditSceneImage": {
      "post": {
        "tags": [
          "images"
        ],
        "summary": "Edit an existing scene image",
        "description": "Modify an existing image with a feedback prompt (e.g. \"make the background darker\").",
        "operationId": "editSceneImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "existingImageUrl",
                  "editPrompt"
                ],
                "properties": {
                  "existingImageUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "editPrompt": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  },
                  "quality": {
                    "type": "string",
                    "enum": [
                      "standard",
                      "hq"
                    ],
                    "default": "standard"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Permanent Firebase Storage URL for the edited image (JSON-encoded string).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "format": "uri",
                  "description": "HTTPS URL to the edited image hosted on Firebase Storage."
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyGenerateTwinImage": {
      "post": {
        "tags": [
          "images"
        ],
        "summary": "Generate or edit an AI Twin avatar",
        "description": "Two modes — generate from reference photos, or edit an existing twin image with feedback.\nCost: 2 credits (always HQ for face consistency).\n",
        "operationId": "generateTwinImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "twinDescription": {
                    "type": "string"
                  },
                  "nicheTopics": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "contentPillars": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "voiceDescription": {
                    "type": "string"
                  },
                  "ethnicity": {
                    "type": "string"
                  },
                  "gender": {
                    "type": "string"
                  },
                  "ageRange": {
                    "type": "string"
                  },
                  "referenceImageUrls": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uri"
                    },
                    "description": "Required for generation mode (1–5 reference photos of the creator)."
                  },
                  "existingImageUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "For edit mode — the existing twin image to modify."
                  },
                  "editPrompt": {
                    "type": "string",
                    "description": "For edit mode — feedback like \"make hair shorter\"."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Permanent Firebase Storage URL for the generated twin image (JSON-encoded string).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "format": "uri",
                  "description": "HTTPS URL to the generated twin avatar hosted on Firebase Storage."
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyGenerateSocialImage": {
      "post": {
        "tags": [
          "images"
        ],
        "summary": "Generate a social-platform-optimized image",
        "description": "Returns an image sized for a specific platform (Instagram post, TikTok cover, etc.).",
        "operationId": "generateSocialImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "prompt"
                ],
                "properties": {
                  "prompt": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4000
                  },
                  "platform": {
                    "type": "string",
                    "enum": [
                      "instagram-post",
                      "instagram-story",
                      "tiktok-cover",
                      "youtube-thumbnail",
                      "twitter-post",
                      "pinterest-pin"
                    ]
                  },
                  "quality": {
                    "type": "string",
                    "enum": [
                      "standard",
                      "hq"
                    ],
                    "default": "standard"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Permanent Firebase Storage URL for the generated social image (JSON-encoded string).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "format": "uri",
                  "description": "HTTPS URL to the generated social-platform image hosted on Firebase Storage."
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyStartVideoGeneration": {
      "post": {
        "tags": [
          "video"
        ],
        "summary": "Start an asynchronous video render",
        "description": "**Credits are deducted at this call.** Returns an `operation` reference that you poll with\n`proxyCheckVideoStatus`, OR receive via webhook (`video.completed` / `video.failed`).\n\nCost is engine-specific via `getVideoCreditCost(engine, quality, duration)` — see the\n`Pricing reference` in `docs/api-reference.md`. For 8s renders: Sora std=18, Sora HQ=65,\nVeo std=40, Veo HQ=130, Kling std~31, Kling HQ~63, Seedance std=36, Seedance HQ=70.\nVeo is fixed-cost per render regardless of duration; the others scale linearly.\n\n**Strongly recommend** sending `Idempotency-Key` to prevent network-retry double-charges.\n",
        "operationId": "startVideoGeneration",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StartVideoRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation handle for polling.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "operation"
                  ],
                  "properties": {
                    "operation": {
                      "type": "object",
                      "required": [
                        "name"
                      ],
                      "properties": {
                        "name": {
                          "type": "string",
                          "description": "Opaque, engine-specific operation identifier. Pass back verbatim\nto `proxyCheckVideoStatus` and `proxyFetchVideo` along with the\n`engine` field — never parse this string.\n"
                        }
                      }
                    },
                    "assembledPrompt": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The final prompt sent to the engine after server-side post-processing."
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyCheckVideoStatus": {
      "post": {
        "tags": [
          "video"
        ],
        "summary": "Poll a video render's status",
        "description": "**No credits.** Returns `{ done: false }` while in progress (with optional `progress` 0–100\nfor Sora/Kling/Seedance). On completion: `{ done: true, response: { generatedVideos: [...] } }`.\nOn failure: `{ done: true, error: { code, message } }` — credits auto-refunded.\n\nSuggested cadence: 15s start, ×1.2 backoff up to 60s for standard renders; 30s start for HQ.\n",
        "operationId": "checkVideoStatus",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "operationName",
                  "engine"
                ],
                "properties": {
                  "operationName": {
                    "type": "string"
                  },
                  "engine": {
                    "$ref": "#/components/schemas/Engine"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation status.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "done"
                      ],
                      "properties": {
                        "done": {
                          "type": "boolean",
                          "enum": [
                            false
                          ]
                        },
                        "progress": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100,
                          "description": "Best-effort progress percentage. Only emitted by Sora/Kling/Seedance."
                        },
                        "message": {
                          "type": "string",
                          "description": "Optional human-readable status hint emitted on engine-internal\nretries (e.g. \"Product image triggered face filter — retrying\nwithout image...\"). Safe to log/display; not a failure signal.\n"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "done",
                        "response"
                      ],
                      "properties": {
                        "done": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "response": {
                          "type": "object",
                          "required": [
                            "generatedVideos"
                          ],
                          "properties": {
                            "generatedVideos": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "video": {
                                    "type": "object",
                                    "properties": {
                                      "uri": {
                                        "type": "string",
                                        "format": "uri"
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "done",
                        "error"
                      ],
                      "properties": {
                        "done": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "error": {
                          "type": "object",
                          "required": [
                            "code",
                            "message"
                          ],
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "SAFETY_BLOCK",
                                "MODERATION_BLOCKED",
                                "GENERATION_FAILED"
                              ]
                            },
                            "message": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyFetchVideo": {
      "post": {
        "tags": [
          "video"
        ],
        "summary": "Download the finished video",
        "description": "**No credits.** Returns a Firebase Storage signed URL (typical 7-day expiry). For long-term\nretention, copy bytes to your own storage on receipt.\n\nFor Sora extend flows, pass `isExtension: true` so server-side FFmpeg trims correctly.\n",
        "operationId": "fetchVideo",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "operationName",
                  "engine"
                ],
                "properties": {
                  "operationName": {
                    "type": "string"
                  },
                  "engine": {
                    "$ref": "#/components/schemas/Engine"
                  },
                  "duration": {
                    "type": "integer",
                    "minimum": 4,
                    "maximum": 20
                  },
                  "isExtension": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed URL to the rendered video.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "videoUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "mimeType": {
                      "type": "string",
                      "example": "video/mp4"
                    },
                    "isWatermarked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyStitchVideos": {
      "post": {
        "tags": [
          "post-production"
        ],
        "summary": "Concatenate 1–10 video clips",
        "description": "**No credits.** Per-clip render costs were already paid at\n`proxyStartVideoGeneration`. Optional cross-fade transitions between clips\n(default ~1.0s, clamped 0.3–2.0s); falls back to plain concatenation when a\nclip is too short or when ffmpeg's xfade filter rejects the input format.\n",
        "operationId": "stitchVideos",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "videoUrls"
                ],
                "properties": {
                  "videoUrls": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "items": {
                      "type": "string",
                      "format": "uri"
                    },
                    "description": "Signed URLs from `proxyFetchVideo` (or any HTTPS-reachable\n`video/mp4` URL). Order is preserved in the output.\n"
                  },
                  "engines": {
                    "type": "array",
                    "description": "Optional parallel array of engine names — one per `videoUrls`\nentry. When provided, Sora-tagged clips get the standard 0.5s\nstart trim that removes the prompt-image flash. Omit if you've\nalready trimmed your sources.\n",
                    "items": {
                      "$ref": "#/components/schemas/Engine"
                    }
                  },
                  "useCrossfade": {
                    "type": "boolean",
                    "default": true,
                    "description": "Set to `false` to skip transitions and use plain concat. Default\n`true` — the server applies a fadeblack transition where clip\ndurations allow.\n"
                  },
                  "crossfadeDuration": {
                    "type": "number",
                    "minimum": 0.3,
                    "maximum": 2,
                    "description": "Override the transition duration in seconds. Ignored when\n`useCrossfade` is `false`.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stitched video URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "videoUrl",
                    "mimeType"
                  ],
                  "properties": {
                    "videoUrl": {
                      "type": "string",
                      "format": "uri",
                      "description": "Permanent signed URL of the stitched MP4."
                    },
                    "mimeType": {
                      "type": "string",
                      "example": "video/mp4"
                    },
                    "finalUrl": {
                      "type": "string",
                      "format": "uri",
                      "description": "**Deprecated alias** for `videoUrl`. Returned for backward\ncompatibility with older web-UI clients. Prefer `videoUrl`.\n"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyApplyTextOverlay": {
      "post": {
        "tags": [
          "post-production"
        ],
        "summary": "Burn text overlay onto a video",
        "description": "Cost 1 credit. Multiple overlays in one call supported.",
        "operationId": "applyTextOverlay",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "videoUrl",
                  "overlays"
                ],
                "properties": {
                  "videoUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "overlays": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "$ref": "#/components/schemas/TextOverlay"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Video with overlays applied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "videoUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "mimeType": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyGenerateOverlaySuggestions": {
      "post": {
        "tags": [
          "post-production"
        ],
        "summary": "AI-suggest text overlays for a script's scenes",
        "description": "Returns hook + body + CTA overlay strings tuned for a target platform.",
        "operationId": "generateOverlaySuggestions",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "script"
                ],
                "properties": {
                  "script": {
                    "$ref": "#/components/schemas/ScriptResult"
                  },
                  "platform": {
                    "type": "string",
                    "enum": [
                      "tiktok",
                      "instagram-reels",
                      "youtube-shorts"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-scene overlay suggestions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "suggestions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "sceneIndex": {
                            "type": "integer"
                          },
                          "overlays": {
                            "type": "array",
                            "items": {
                              "$ref": "#/components/schemas/TextOverlay"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyListTwins": {
      "post": {
        "tags": [
          "twins"
        ],
        "summary": "List your AI Twins",
        "description": "No credits. Returns all twins owned by the calling key's account.",
        "operationId": "listTwins",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Twin list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "twins": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Twin"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyCreateTwin": {
      "post": {
        "tags": [
          "twins"
        ],
        "summary": "Create an AI Twin",
        "description": "Tier-gated AI Twin slot count: creator 1, pro 5, business 20. PAYG users default to creator (1).\nNo credit cost — the underlying image generation is billed separately if you call generateTwinImage.\n",
        "operationId": "createTwin",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TwinCreateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Twin"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyUpdateTwin": {
      "post": {
        "tags": [
          "twins"
        ],
        "summary": "Update an existing AI Twin",
        "operationId": "updateTwin",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "twinId"
                ],
                "properties": {
                  "twinId": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "nicheTopics": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "contentPillars": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "voiceDescription": {
                    "type": "string"
                  },
                  "imageUrl": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Twin"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyDeleteTwin": {
      "post": {
        "tags": [
          "twins"
        ],
        "summary": "Delete an AI Twin",
        "operationId": "deleteTwin",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "twinId"
                ],
                "properties": {
                  "twinId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deletion result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyAnalyzeVoice": {
      "post": {
        "tags": [
          "analysis"
        ],
        "summary": "Analyze a voice sample",
        "description": "Returns a structured voice profile (tone, pace, vocabulary cues) for use in script generation.\nCost: **3 credits for API callers** (free in the web UI as an onboarding courtesy).\n",
        "operationId": "analyzeVoice",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "audioUrl"
                ],
                "properties": {
                  "audioUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public URL or Firebase Storage path to the audio sample (mp3/wav/m4a, ≤2 min)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Voice profile.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceProfile"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyAnalyzeReferenceVideo": {
      "post": {
        "tags": [
          "analysis"
        ],
        "summary": "Analyze a reference video",
        "description": "Returns visual + script + creative analysis of a reference video. Two modes:\n- Standard (3 credits) — Gemini-only analysis.\n- Deep / clone-video (4 credits) — adds keyframe extraction for higher fidelity.\n",
        "operationId": "analyzeReferenceVideo",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "videoUrl"
                ],
                "properties": {
                  "videoUrl": {
                    "type": "string",
                    "description": "Public video URL OR a Firebase Storage path under projects/{uid}/reference-videos/..."
                  },
                  "storagePath": {
                    "type": "string",
                    "description": "Alternative to videoUrl — direct Firebase Storage path. Use for files >32MB."
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "standard",
                      "clone-video"
                    ],
                    "default": "standard"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Analysis result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferenceVideoAnalysis"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyGenerateSocialCaptions": {
      "post": {
        "tags": [
          "post-production"
        ],
        "summary": "Generate platform-tuned captions",
        "description": "Returns hook + body + CTA copy variants for TikTok / Reels / Shorts.",
        "operationId": "generateSocialCaptions",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "script"
                ],
                "properties": {
                  "script": {
                    "$ref": "#/components/schemas/ScriptResult"
                  },
                  "platforms": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "tiktok",
                        "instagram-reels",
                        "youtube-shorts"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Caption variants.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "captions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "platform": {
                            "type": "string"
                          },
                          "hook": {
                            "type": "string"
                          },
                          "body": {
                            "type": "string"
                          },
                          "cta": {
                            "type": "string"
                          },
                          "hashtags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/saveVideoToLibrary": {
      "post": {
        "tags": [
          "library"
        ],
        "summary": "Save a rendered video to the library",
        "description": "No credits. Persists a finished video under your account for later retrieval.\n\n**Subscription required.** Calls return `403 permission-denied` for non-subscribers\n(free, PAYG, or past_due). The web UI gates this feature the same way.\n",
        "operationId": "saveVideoToLibrary",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sourceUrl"
                ],
                "properties": {
                  "sourceUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "The temporary signed URL returned by `proxyFetchVideo` (or a `gs://` GCS\nURL). The server copies the bytes to permanent storage under your account.\n"
                  },
                  "name": {
                    "type": "string",
                    "description": "Display name for the saved video."
                  },
                  "operationName": {
                    "type": "string"
                  },
                  "engine": {
                    "$ref": "#/components/schemas/Engine"
                  },
                  "modelName": {
                    "type": "string"
                  },
                  "visualPrompt": {
                    "type": "string"
                  },
                  "productDescription": {
                    "type": "string"
                  },
                  "influencerDescription": {
                    "type": "string"
                  },
                  "hooks": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "sceneTone": {
                    "type": "string"
                  },
                  "projectMode": {
                    "type": "string"
                  },
                  "industry": {
                    "type": "string"
                  },
                  "duration": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Library entry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "savedUrl",
                    "savedAt"
                  ],
                  "properties": {
                    "savedUrl": {
                      "type": "string",
                      "format": "uri",
                      "description": "Permanent signed URL under `saved-videos/{uid}/`."
                    },
                    "savedAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "ISO 8601 timestamp of when the entry was created."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/proxyGetVideoLibrary": {
      "post": {
        "tags": [
          "library"
        ],
        "summary": "List saved videos",
        "description": "Returns paginated saved videos sorted by `savedAt` descending. To fetch the next\npage, pass the previous response's `nextPageCursor` as the `startAfterId` parameter.\n",
        "operationId": "getVideoLibrary",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "startAfterId": {
                    "type": "string",
                    "description": "Cursor for pagination — pass the `nextPageCursor` from the previous\nresponse. Opaque Firestore document ID; do not parse.\n"
                  },
                  "pageSize": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "default": 20,
                    "description": "Server caps at 50 even if a larger value is supplied."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paginated library.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "videos",
                    "nextPageCursor",
                    "hasMore"
                  ],
                  "properties": {
                    "videos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SavedVideo"
                      }
                    },
                    "nextPageCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass to the next call as `startAfterId`. `null` when no more pages."
                    },
                    "hasMore": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ugc_live_<32 hex chars>",
        "description": "API key issued from the web UI under **Profile → API Keys**. Format: `ugc_live_` followed\nby 32 hex characters (41 chars total). Sent as `Authorization: Bearer ugc_live_...`.\n"
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "UUID or any URL-safe string ≤256 chars. Dedupes within a 24-hour window — the same key\nsent twice returns the cached first response. **Strongly recommended for mutating /\ncredit-deducting calls** to prevent network-retry double-charges. Reusing a key with a\ndifferent request body returns `422 idempotency-conflict`.\n",
        "schema": {
          "type": "string",
          "maxLength": 256
        }
      }
    },
    "headers": {
      "X-Request-ID": {
        "description": "Unique ID for this request — quote in support tickets.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "X-Credits-Remaining": {
        "description": "Credits left in the current monthly window (excludes bonus credits below 0).",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "X-RateLimit-Limit": {
        "description": "Max requests in the current minute window for this actionType.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests remaining in the current minute window.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "Unix seconds when the current rate-limit window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "X-Idempotent-Replayed": {
        "description": "Set to `true` when the response was served from the idempotency cache.",
        "schema": {
          "type": "boolean"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (set on 429 responses).",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error response.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "InsufficientCredits": {
        "description": "Credit pool exhausted (monthly + bonus packs).",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Per-key rate limit or concurrency cap exceeded.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "IdempotencyConflict": {
        "description": "Idempotency-Key reused with a different request body.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "Engine": {
        "type": "string",
        "enum": [
          "sora",
          "veo",
          "kling",
          "seedance"
        ],
        "description": "Video generation engine."
      },
      "Quality": {
        "type": "string",
        "enum": [
          "standard",
          "hq"
        ]
      },
      "ProjectMode": {
        "type": "string",
        "enum": [
          "product-ad",
          "ugc-creator",
          "podcast-style",
          "influencer-noproduct",
          "vlog",
          "clone-video",
          "own-script"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "code",
              "message",
              "request_id"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "validation",
                  "authentication",
                  "permission",
                  "idempotency",
                  "rate_limit",
                  "resource",
                  "concurrency",
                  "internal"
                ],
                "description": "Coarse error category for client-side branching. The pair `(type, code)` is\nstable across versions; the `code` is the more specific identifier.\n"
              },
              "code": {
                "type": "string",
                "description": "Machine-readable code: invalid-argument, unauthenticated, permission-denied,\nnot-found, idempotency-in-progress, idempotency-conflict, resource-exhausted,\nconcurrency-exceeded, insufficient-credits, already-exists, failed-precondition,\ninternal.\n"
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string",
                "format": "uuid"
              },
              "retryAfter": {
                "type": "integer",
                "description": "Set on 429 responses — seconds to wait before retry."
              },
              "field": {
                "type": "string",
                "description": "Set on validation errors when a specific field is at fault."
              }
            }
          }
        }
      },
      "TrendingProduct": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "brand": {
            "type": "string"
          },
          "priceRange": {
            "type": "string"
          },
          "adAngle": {
            "type": "string"
          },
          "idealInfluencer": {
            "type": "object",
            "properties": {
              "ageRange": {
                "type": "string"
              },
              "gender": {
                "type": "string"
              },
              "ethnicity": {
                "type": "string"
              },
              "voicePersona": {
                "type": "string"
              }
            }
          },
          "sourceUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "MarketAnalysis": {
        "type": "object",
        "properties": {
          "industry": {
            "type": "string"
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TrendingProduct"
            }
          },
          "competitorAngles": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "audienceSegments": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          },
          "creativeRecommendations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ScriptGenerateRequest": {
        "type": "object",
        "required": [
          "productDescription"
        ],
        "properties": {
          "productDescription": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4000
          },
          "twinDescription": {
            "type": "string"
          },
          "twinId": {
            "type": "string",
            "description": "Reuse a saved AI Twin instead of describing a creator inline."
          },
          "voiceProfile": {
            "$ref": "#/components/schemas/VoiceProfile"
          },
          "tone": {
            "type": "string",
            "example": "humorous"
          },
          "platform": {
            "type": "string",
            "enum": [
              "tiktok",
              "instagram-reels",
              "youtube-shorts"
            ]
          },
          "projectMode": {
            "$ref": "#/components/schemas/ProjectMode"
          },
          "isFaceless": {
            "type": "boolean",
            "default": false
          },
          "targetDurationSec": {
            "type": "integer",
            "minimum": 8,
            "maximum": 90
          }
        }
      },
      "ScriptResult": {
        "type": "object",
        "properties": {
          "hooks": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "scenes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scene"
            }
          },
          "ctas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "platformVariations": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "hook": {
                  "type": "string"
                },
                "cta": {
                  "type": "string"
                },
                "caption": {
                  "type": "string"
                }
              }
            }
          },
          "rawScript": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "Scene": {
        "type": "object",
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 0
          },
          "visualPrompt": {
            "type": "string"
          },
          "scriptText": {
            "type": "string"
          },
          "soundCues": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "durationSec": {
            "type": "number",
            "minimum": 1
          }
        }
      },
      "StartVideoRequest": {
        "type": "object",
        "required": [
          "visualPrompt",
          "engine",
          "modelName"
        ],
        "properties": {
          "visualPrompt": {
            "type": "string",
            "minLength": 1
          },
          "engine": {
            "$ref": "#/components/schemas/Engine"
          },
          "modelName": {
            "type": "string",
            "description": "Engine-specific model identifier. Examples: 'sora-2', 'sora-2-pro',\n'veo-3.1-generate-preview', 'fal-ai/kling-video/o3/standard/image-to-video',\n'bytedance/seedance-2.0/image-to-video'.\n"
          },
          "sceneImage": {
            "type": "object",
            "properties": {
              "data": {
                "type": "string",
                "description": "Base64-encoded image (no data: prefix)."
              },
              "mimeType": {
                "type": "string",
                "example": "image/png"
              }
            }
          },
          "duration": {
            "type": "integer",
            "minimum": 4,
            "maximum": 20,
            "description": "Render duration in seconds (engine-clamped)."
          },
          "editVideoId": {
            "type": "string",
            "description": "For Sora extend flows — the source video to extend."
          },
          "isFaceless": {
            "type": "boolean",
            "default": false
          },
          "projectMode": {
            "$ref": "#/components/schemas/ProjectMode"
          },
          "productDescription": {
            "type": "string"
          },
          "influencerDescription": {
            "type": "string"
          }
        }
      },
      "Twin": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "nicheTopics": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "contentPillars": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "voiceDescription": {
            "type": "string"
          },
          "voiceProfile": {
            "$ref": "#/components/schemas/VoiceProfile"
          },
          "imageUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "ethnicity": {
            "type": "string"
          },
          "gender": {
            "type": "string"
          },
          "ageRange": {
            "type": "string"
          },
          "createdAt": {
            "type": "number",
            "description": "Timestamp in milliseconds."
          }
        }
      },
      "TwinCreateRequest": {
        "type": "object",
        "required": [
          "description"
        ],
        "properties": {
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          },
          "nicheTopics": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "contentPillars": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "voiceDescription": {
            "type": "string"
          },
          "ethnicity": {
            "type": "string"
          },
          "gender": {
            "type": "string"
          },
          "ageRange": {
            "type": "string"
          },
          "imageUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "VoiceProfile": {
        "type": "object",
        "properties": {
          "tone": {
            "type": "string"
          },
          "pace": {
            "type": "string"
          },
          "vocabulary": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "signaturePhrases": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "notes": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "TextOverlay": {
        "type": "object",
        "required": [
          "text",
          "startSec",
          "endSec"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "startSec": {
            "type": "number",
            "minimum": 0
          },
          "endSec": {
            "type": "number",
            "minimum": 0
          },
          "position": {
            "type": "string",
            "enum": [
              "top",
              "center",
              "bottom"
            ],
            "default": "bottom"
          },
          "style": {
            "type": "object",
            "properties": {
              "fontFamily": {
                "type": "string"
              },
              "fontSize": {
                "type": "integer",
                "minimum": 8,
                "maximum": 200
              },
              "color": {
                "type": "string",
                "example": "#ffffff"
              },
              "backgroundColor": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "SavedVideo": {
        "type": "object",
        "required": [
          "id",
          "savedUrl",
          "savedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass as `startAfterId` to paginate after this entry."
          },
          "savedUrl": {
            "type": "string",
            "format": "uri",
            "description": "Permanent signed URL."
          },
          "savedAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "visualPrompt": {
            "type": [
              "string",
              "null"
            ]
          },
          "productDescription": {
            "type": [
              "string",
              "null"
            ]
          },
          "sceneTone": {
            "type": [
              "string",
              "null"
            ]
          },
          "engine": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Engine"
              },
              {
                "type": "null"
              }
            ]
          },
          "modelName": {
            "type": [
              "string",
              "null"
            ]
          },
          "projectMode": {
            "type": [
              "string",
              "null"
            ]
          },
          "industry": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration": {
            "type": [
              "number",
              "null"
            ]
          },
          "hasEmbedding": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "ReferenceVideoAnalysis": {
        "type": "object",
        "properties": {
          "summary": {
            "type": "string"
          },
          "scenes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "startSec": {
                  "type": "number"
                },
                "endSec": {
                  "type": "number"
                },
                "visualDescription": {
                  "type": "string"
                },
                "script": {
                  "type": "string"
                }
              }
            }
          },
          "creativeStructure": {
            "type": "object",
            "properties": {
              "hook": {
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "cta": {
                "type": "string"
              }
            }
          },
          "recommendedHookVariants": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": true
      }
    }
  }
}