> ## Documentation Index
> Fetch the complete documentation index at: https://docs.viggle.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate Video (from Character Animation)

> Generate a video by applying a driving video's motion and/or up to 4 character images through the viggle-animate backend.

Send `multipart/form-data` — or a plain `application/json` body — with at least one of `character_image_url` or `character_image_urls`, optionally alongside `driving_video_url`. Supplying any of these three fields is what selects this mode of `POST /v1/videos` — character-animation generation — and dispatches to a separate backend (viggle-animate) from the other four modes on this endpoint.

<Note>
  Unlike every other `POST /v1/videos` mode, character-animation mode takes no file uploads at all — `driving_video_url` and every character image must already be reachable at a URL (`https://`, or an internal `gs://` object) rather than attached to the request. It's also the only mode that accepts a plain JSON body as an alternative to `multipart/form-data`; see [JSON request body](#json-request-body) below.
</Note>

This uses the same `POST /v1/videos` endpoint as [Generate Video (from Text)](/v1/api-reference/videos/create-from-text), [Generate Video (from First Frame and/or Text)](/v1/api-reference/videos/create-from-first-frame), [Generate Video (from First-Last Frames and/or Text)](/v1/api-reference/videos/create-from-first-last-frame), and [Generate Video (from Reference Video/Image)](/v1/api-reference/videos/create-from-reference-video) — which mode runs is selected by which fields you send, not by a separate parameter. Character-animation fields cannot be combined with `quality` or any frame/reference field from the other four modes; doing so answers `400 INVALID_REQUEST` with "driving\_video\_url/character\_image\_url cannot be combined with quality".

<Warning>
  Character-animation mode is billed differently from the other four modes: a **flat 11 credits (\$0.11)** per request, regardless of the actual output duration — not ⌈`duration_s`⌉ × a per-second rate. There's no `quality`, `duration_s`, `resolution`, `aspect_ratio`, or `seed` for this mode. See **Pricing** below.
</Warning>

## Request parameters

Send `multipart/form-data`, or JSON (see below).

| Parameter              | Type                     |  Required | Default | Description                                                                                                                                                                                                                                    |
| ---------------------- | ------------------------ | :-------: | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `driving_video_url`    | string (URI)             |     No    | —       | URL of a driving video whose motion is applied to the character image(s). `https://` or internal `gs://`. Single-valued. Must be 5–10 seconds long (inclusive), at least 64x64, and even width/height.                                         |
| `character_image_url`  | string (URI), repeatable | See below | —       | Character reference image URL. Can be repeated. Mutually exclusive with `character_image_urls` — supply one field or the other.                                                                                                                |
| `character_image_urls` | array of string (URI)    | See below | —       | Same as `character_image_url`, as one field instead of repeated ones: a JSON array of strings on a multipart request, or a native array on a JSON request body. Mutually exclusive with `character_image_url`.                                 |
| `prompt`               | string                   |     No    | —       | Optional style/content guidance. Unlike every other mode, not required and not enforced non-empty.                                                                                                                                             |
| `watermark`            | boolean                  |     No    | `false` | Whether to burn in a Viggle watermark. Same field and convention as the other four modes.                                                                                                                                                      |
| `priority`             | integer                  |     No    | `1000`  | Admission-queue priority tier, shared with H3 modes. Must be one of the server's configured tiers (currently `1000` or `0`); an unsupported value answers `400 INVALID_REQUEST` — "unsupported priority". Most callers never need to set this. |

Supplying `driving_video_url` and/or `character_image_url`/`character_image_urls` is what selects this mode — there's no separate `source` parameter. At least one character image is required across `character_image_url`/`character_image_urls`; supplying neither is **not** character-animation mode at all — see [Generate Video (from Text)](/v1/api-reference/videos/create-from-text) and the other modes for what runs instead.

### Driving video vs. character images

* **Driving video** (`driving_video_url`): optional — a character-animation request can run on character images alone, with no driving video. When supplied, it must be 5–10 seconds long (inclusive) — a narrower window than the 0.5–600 seconds enforced on every other video input in this API — and at least 64x64 with even width and height. A URL that fails to fetch answers `400 INVALID_REQUEST` — "failed to fetch driving\_video\_url".
* **Character images** (`character_image_url` / `character_image_urls`): at least one is required. Up to 4 total are accepted **when no driving video is supplied** — but only **1** when `driving_video_url` is present, since the driving video already establishes the motion. Supplying more than that cap answers `400 INVALID_REQUEST` — "at most 4 character images are allowed" (or "only one character\_image\_url is allowed when driving\_video\_url is provided" specifically for the video+image combination). Supplying both `character_image_url` and `character_image_urls` in the same request answers `400 INVALID_REQUEST` — "supply only one of character\_image\_url or character\_image\_urls".

Every URL — driving video or character image — must be `https://` or `gs://`; anything else (a bare `http://`, for example) answers `400 INVALID_REQUEST` with "each character\_image\_url must be a gs\:// or https\:// URI" (or the `driving_video_url` equivalent).

## JSON request body

Character-animation mode alone also accepts a plain JSON body (`Content-Type: application/json`) as an alternative to `multipart/form-data`, native arrays included — useful since this mode never takes file uploads in the first place:

```json theme={null}
{
  "driving_video_url": "https://example.test/driving.mp4",
  "character_image_urls": ["https://example.test/char1.png", "https://example.test/char2.png"],
  "prompt": "keep the same outfit and background",
  "watermark": false
}
```

The request body must be a single JSON object containing at least one of `driving_video_url`, `character_image_url`, or `character_image_urls`; an empty or malformed body answers `400 INVALID_REQUEST` — "request body must be a JSON object" (or "JSON animation requests require character images" when none of the three fields are present).

Since JSON object keys can't repeat, `character_image_url` can only ever hold one string in a JSON body — use `character_image_urls` with a native array whenever you need more than one image over JSON.

## Response parameters

Returns `200 OK` — an acceptance acknowledgment, not the final result.

| Field        | Type            | Always present | Description                                    |
| ------------ | --------------- | :------------: | ---------------------------------------------- |
| `id`         | string          |       Yes      | Public video ID, `anim_`-prefixed.             |
| `status`     | string          |       Yes      | Always `queued` on acceptance.                 |
| `progress`   | integer or null |       Yes      | Always `null` on acceptance.                   |
| `created_at` | string          |       Yes      | ISO 8601 creation timestamp, second precision. |

```json theme={null}
{
  "id": "anim_7c1e4b",
  "status": "queued",
  "progress": null,
  "created_at": "2026-09-09T09:12:03Z"
}
```

This response deliberately carries none of the fields `GET /v1/videos/{video_id}` returns (`video_url`, `completed_at`, `error`). See [Get Video](/v1/api-reference/videos/get) for the full shape once the video is processing or done — character-animation's response there carries no `seed` (H3-only) alongside the fields every video shares.

## Pricing

Character-animation mode is billed at a **flat rate**, unlike every other mode on this endpoint: **11 credits (\$0.11)** per accepted request, charged at creation time regardless of the actual output duration. This is because the real output duration isn't known until after the driving video is measured server-side — well after credits must already be reserved — so every request is charged as if it produced the maximum possible output (up to roughly 10 seconds). `watermark` does not affect price. See [Pricing and retention](/v1/pricing).

## Examples

<CodeGroup>
  ```bash cURL (multipart, driving video + one character image) theme={null}
  curl https://apis.viggle.ai/v1/videos \
    -H "Authorization: Bearer $VIGGLE_API_KEY" \
    -F driving_video_url=https://example.test/driving.mp4 \
    -F character_image_url=https://example.test/char1.png
  ```

  ```bash cURL (multipart, character images only, no driving video) theme={null}
  curl https://apis.viggle.ai/v1/videos \
    -H "Authorization: Bearer $VIGGLE_API_KEY" \
    -F prompt="keep the same outfit and background" \
    -F character_image_url=https://example.test/char1.png \
    -F character_image_url=https://example.test/char2.png
  ```

  ```bash cURL (JSON body) theme={null}
  curl https://apis.viggle.ai/v1/videos \
    -H "Authorization: Bearer $VIGGLE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
          "driving_video_url": "https://example.test/driving.mp4",
          "character_image_urls": ["https://example.test/char1.png"]
        }'
  ```

  ```python Python theme={null}
  import os, requests
  response = requests.post(
      "https://apis.viggle.ai/v1/videos",
      headers={"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}", "Content-Type": "application/json"},
      json={
          "driving_video_url": "https://example.test/driving.mp4",
          "character_image_urls": ["https://example.test/char1.png"],
      },
  )
  response.raise_for_status()
  video = response.json()
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://apis.viggle.ai/v1/videos", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.VIGGLE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      driving_video_url: "https://example.test/driving.mp4",
      character_image_urls: ["https://example.test/char1.png"],
    }),
  });
  const video = await response.json();
  ```
</CodeGroup>

## Common errors

| Error                                                                                                                                                         | Cause                                                                                |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `400 INVALID_REQUEST` — "character images are required"                                                                                                       | Neither `character_image_url` nor `character_image_urls` supplied.                   |
| `400 INVALID_REQUEST` — "supply only one of character\_image\_url or character\_image\_urls"                                                                  | Both fields supplied together.                                                       |
| `400 INVALID_REQUEST` — "at most 4 character images are allowed"                                                                                              | More than 4 character images supplied across both fields.                            |
| `400 INVALID_REQUEST` — "only one character\_image\_url is allowed when driving\_video\_url is provided"                                                      | More than 1 character image supplied alongside a driving video.                      |
| `400 INVALID_REQUEST` — "each character\_image\_url must be a gs\:// or https\:// URI" (or "...character\_image\_urls...", matching whichever field you used) | A character image used an unsupported URI scheme (not `https://` or `gs://`).        |
| `400 INVALID_REQUEST` — "driving\_video\_url must be a gs\:// or https\:// URI"                                                                               | `driving_video_url` used an unsupported URI scheme.                                  |
| `400 INVALID_REQUEST` — "driving\_video\_url must be a single URL"                                                                                            | `driving_video_url` supplied more than once.                                         |
| `400 INVALID_REQUEST` — "failed to fetch driving\_video\_url"                                                                                                 | The driving video URL couldn't be fetched.                                           |
| `400 INVALID_REQUEST` — "driving\_video\_url duration must be between 5 and 10 seconds (inclusive)"                                                           | The fetched driving video was outside the 5–10 second window.                        |
| `400 INVALID_REQUEST` — "driving\_video\_url/character\_image\_url cannot be combined with quality"                                                           | `quality` (or another H3-mode field) supplied alongside a character-animation field. |
| `400 INVALID_REQUEST` — "unsupported priority"                                                                                                                | `priority` set to a value outside the server's configured tiers.                     |

## Next step

Use [Get Video](/v1/api-reference/videos/get) with the returned `id` to poll for the result.

<CardGroup cols={2}>
  <Card title="Generate Video (from Text)" icon="text" href="/v1/api-reference/videos/create-from-text">
    Generate from a prompt alone, with no frame image or reference.
  </Card>

  <Card title="Generate Video (from Reference Video/Image)" icon="clapperboard" href="/v1/api-reference/videos/create-from-reference-video">
    Carry a subject or style from a reference video and/or up to 4 images, on the H3 backend instead.
  </Card>
</CardGroup>


## OpenAPI

````yaml openapi.yaml POST /v1/videos
openapi: 3.0.3
info:
  title: Viggle API
  description: Generate AI-powered character animation videos
  version: 2.0.0
  contact:
    name: Viggle Support
    url: https://viggle.ai
servers:
  - url: https://apis.viggle.ai
    description: Production server
security:
  - bearerAuth: []
tags:
  - name: Renders
    description: Prepare inputs, create renders, observe progress, and retrieve results.
  - name: Credits
    description: Read the credit balance available to the calling principal.
  - name: Characters
    description: |
      Create, list, inspect, and delete reusable character assets. `type`
      selects whether creation also extracts a 3D vsplat; export its
      download URL separately once ready.
  - name: Motions
    description: |
      Create, list, inspect, and delete reusable motion assets, and import one
      from an official motion template. `type` selects whether creation also
      extracts or generates a 3D animation; export its download URL
      separately once ready.
  - name: Videos
    description: |
      Generate a MiniMax H3 text-to-video (`vid_` prefix) or a
      character-animation video from a character image and/or driving video
      (`anim_` prefix), and read the unified view over every video the
      caller owns — H3 generations, character-animation generations, and
      character+motion Renders (`render_` prefix) — merged into one
      resource.
paths:
  /v1/videos:
    post:
      tags:
        - Videos
      summary: >-
        Generate a video from text, from a first and optional last frame, from a
        reference video/image, or from a character image and/or driving video
      description: >
        Generates a Viggle-optimized MiniMax H3 video with native audio, or

        (for character-animation mode) a character-animation video on a

        separate backend. `multipart/form-data` for every mode;

        character-animation mode also accepts a plain `application/json`

        body. The fields supplied select one of five generation modes:


        - Neither frame field nor any reference or character-animation
          field supplied: text-to-video from `prompt` alone.
        - `first_frame_image`/`first_frame_image_url` supplied, no last
          frame: first-frame-to-video.
        - Both a first frame and a last frame supplied:
        first+last-frame-to-video.

        - Any of `reference_video`/`reference_video_url`/`reference_image`/
          `reference_image_url` supplied: reference-video (omni-reference)
          generation, carrying the subject/style of a reference video and/or
          up to 4 reference images into the new video. Mutually exclusive
          with the frame fields.
        - `driving_video_url` and/or `character_image_url`/
          `character_image_urls` supplied: character-animation generation —
          applies a driving video's motion and/or up to 4 character images
          to produce a new video. Mutually exclusive with every field
          above, including `quality`. See `CreateVideoForm` and `Generate
          Video (from Character Animation)`.

        `quality=low` generates faster for quicker iteration; `quality=high`

        produces higher-fidelity output. Both tiers are billed at the same

        $0.01/sec rate — see `CreateVideoForm` below. Character-animation

        mode has no `quality` tier and is billed at a flat rate instead.


        All five modes answer `200` with the same minimal acceptance shape;

        poll `GET /v1/videos/{video_id}` for the final result.
      operationId: createVideo
      parameters:
        - $ref: '#/components/parameters/RequestId'
        - $ref: '#/components/parameters/SourceChannel'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/CreateVideoForm'
      responses:
        '200':
          description: Video generation accepted and queued.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoCreateResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/V1Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
components:
  parameters:
    RequestId:
      name: X-Request-Id
      in: header
      required: false
      description: >-
        Optional caller-supplied correlation ID, up to 128 characters. The
        service returns the effective value in the response header for tracing
        and support.
      schema:
        type: string
        minLength: 1
        maxLength: 128
    SourceChannel:
      name: X-Viggle-Source
      in: header
      required: false
      description: >-
        Optional source-channel label, up to 128 characters, used to attribute
        traffic to an SDK, integration, product surface, or internal workflow.
      schema:
        type: string
        minLength: 1
        maxLength: 128
  schemas:
    CreateVideoForm:
      description: |
        Body of `POST /v1/videos`, `multipart/form-data` only (character-
        animation mode below also accepts a plain `application/json` body —
        see `Generate Video (from Character Animation)`). Which fields are
        supplied selects the generation mode:

        - Neither `first_frame_image`/`first_frame_image_url` nor
          `last_frame_image`/`last_frame_image_url` nor any reference or
          character-animation field supplied: text-to-video.
        - A first frame supplied, no last frame: first-frame-to-video.
        - Both a first frame and a last frame supplied:
          first+last-frame-to-video.
        - Any of `reference_video`/`reference_video_url`/
          `reference_image`/`reference_image_url` supplied: reference-video
          (omni-reference) generation.
        - `driving_video_url` and/or `character_image_url`/
          `character_image_urls` supplied: character-animation generation,
          dispatched to a separate backend from the four modes above. It
          has no `quality` tier, no `duration_s`/`resolution`/
          `aspect_ratio`/`seed`, and `prompt` is optional rather than
          required; see below and `Generate Video (from Character
          Animation)` for its own fields, pricing, and constraints.

        Within a frame slot, supply at most one of the file and the URL
        form. Supplying both `first_frame_image` and `first_frame_image_url`
        (or both `last_frame_image` and `last_frame_image_url`) answers
        `400 INVALID_REQUEST` with "supply only one of first_frame_image or
        first_frame_image_url" (or the `last_frame_*` equivalent). A last
        frame requires a first frame; supplying a last frame with no first
        frame answers `400 INVALID_REQUEST` with "a last frame requires a
        first frame".

        Reference-video mode is selected purely by whether any of the four
        reference fields is present — there is no separate `source`/`mode`
        parameter, and no error if you meant to but forgot: supplying none
        of `reference_video`/`reference_video_url`/`reference_image`/
        `reference_image_url` simply generates text-to-video instead (the
        first bullet above), the same way omitting `first_frame_image`/
        `last_frame_image` falls back to text-to-video rather than
        erroring. `reference_video` and `reference_video_url` are declared
        as arrays for forward compatibility, but at most 1 reference video
        is accepted today; supplying any combination of the two fields that
        adds up to more than 1 (two files, two URLs, or one of each) answers
        `400 INVALID_REQUEST` with "at most 1 reference video(s) allowed".
        `reference_image` and `reference_image_url` may be mixed freely, but
        their combined count cannot exceed 4; going over answers
        `400 INVALID_REQUEST` with "at most 4 reference images are allowed".
        Reference fields cannot be combined with
        `first_frame_image`/`last_frame_image` (file or URL form); doing so
        answers `400 INVALID_REQUEST` with
        "reference_video/reference_image cannot be combined with
        first_frame_image/last_frame_image". The reference video itself
        (independent of `duration_s`, the target duration of the *generated*
        video) must be 0.5–600 seconds long and at least 64x64 with even
        width and height, the same constraints enforced on uploaded video
        elsewhere in this API.

        Character-animation mode cannot be combined with `quality` (or any
        frame/reference field above) — doing so answers
        `400 INVALID_REQUEST` with "driving_video_url/character_image_url
        cannot be combined with quality". At least one character image is
        required (`character_image_url` and `character_image_urls` are
        mutually exclusive ways to supply them — "supply only one of
        character_image_url or character_image_urls" otherwise); up to 4
        are accepted when `driving_video_url` is absent, but only 1 when
        it's present ("only one character_image_url is allowed when
        driving_video_url is provided"). `driving_video_url` itself is
        optional, single-valued, and — unlike every other video input in
        this API — checked against a narrower 5–10 second (inclusive)
        duration window rather than 0.5–600 seconds, on top of the same
        at-least-64x64/even-dimensions checks.

        Billed at ⌈`duration_s`⌉ × the per-second credit rate for `quality`
        (currently 1 credit/second — $0.01/sec — for both `low` and `high`).
        `resolution` and `aspect_ratio` do not affect price, and
        image/video-conditioned generation (first-frame, first-last-frame,
        or reference-video) costs the same as text-only for the same
        quality and duration. Character-animation mode is billed
        differently — a flat rate regardless of actual output duration; see
        `Generate Video (from Character Animation)`.

        Every generated video includes native audio; there is no separate
        audio flag or field.
      type: object
      properties:
        prompt:
          type: string
          minLength: 1
          description: >-
            Text description of the desired video. Required and non-empty for
            text, first-frame, first+last-frame, and reference-video modes.
            Optional (and merely a style/content hint, not enforced non-empty)
            for character-animation mode.
          example: A paper airplane gliding through a sunlit office
        quality:
          type: string
          enum:
            - low
            - high
          description: >-
            Generation quality tier — also selects the per-second credit rate
            charged for this request. `low` generates faster, for quicker
            iteration; `high` generates slower but produces higher-fidelity
            output. Both tiers are billed at the same $0.01/sec rate. Required
            for every mode except character-animation, which has no quality
            tiers and rejects this field outright if supplied.
          example: low
        first_frame_image:
          type: string
          format: binary
          description: >-
            First-frame image uploaded directly. Mutually exclusive with
            `first_frame_image_url`. Supplying either switches on
            first-frame-to-video mode. Cannot be combined with any
            `reference_video`/`reference_image` field.
        first_frame_image_url:
          type: string
          format: uri
          description: >-
            Publicly reachable URL of the first-frame image; the service fetches
            and re-hosts it. Mutually exclusive with `first_frame_image`. Cannot
            be combined with any `reference_video`/`reference_image` field.
        last_frame_image:
          type: string
          format: binary
          description: >-
            Last-frame image uploaded directly. Mutually exclusive with
            `last_frame_image_url`. Valid only alongside a first frame. Cannot
            be combined with any `reference_video`/`reference_image` field.
        last_frame_image_url:
          type: string
          format: uri
          description: >-
            Publicly reachable URL of the last-frame image; the service fetches
            and re-hosts it. Mutually exclusive with `last_frame_image`. Valid
            only alongside a first frame. Cannot be combined with any
            `reference_video`/`reference_image` field.
        reference_video:
          type: array
          maxItems: 1
          items:
            type: string
            format: binary
          description: >-
            Reference video uploaded directly, for reference-video
            (omni-reference) generation. Declared as an array for forward
            compatibility, but at most 1 is accepted today — combined with
            `reference_video_url`. Not required if at least one reference image
            is supplied instead. Must be 0.5–600 seconds long and at least 64x64
            with even width/height. Cannot be combined with
            `first_frame_image`/`last_frame_image`.
        reference_video_url:
          type: array
          maxItems: 1
          items:
            type: string
            format: uri
          description: >-
            Publicly reachable URL of the reference video; the service fetches
            and re-hosts it. Declared as an array for forward compatibility, but
            at most 1 is accepted today — combined with `reference_video`. Same
            duration/resolution constraints as `reference_video`.
        reference_image:
          type: array
          maxItems: 4
          items:
            type: string
            format: binary
          description: >-
            Reference images uploaded directly, for reference-video
            (omni-reference) generation. Up to 4 total, combined with
            `reference_image_url`. Can be freely mixed with
            `reference_image_url`.
        reference_image_url:
          type: array
          maxItems: 4
          items:
            type: string
            format: uri
          description: >-
            Publicly reachable URLs of reference images; the service fetches and
            re-hosts each one. Up to 4 total, combined with `reference_image`.
            Can be freely mixed with `reference_image`.
        driving_video_url:
          type: string
          format: uri
          description: >-
            Publicly reachable URL (`https://`) or internal `gs://` object of a
            driving video, for character-animation generation — its motion is
            applied to the character image(s). Optional and single-valued; a
            character-animation request can run on character images alone. Must
            be 5–10 seconds long (inclusive), at least 64x64, and even
            width/height. Selects character-animation mode together with any
            `character_image_url`/`character_image_urls`; cannot be combined
            with `quality` or any frame/reference field above.
        character_image_url:
          type: array
          items:
            type: string
            format: uri
          description: >-
            Character reference image URL(s) (`https://` or `gs://`),
            repeatable, for character-animation generation. Mutually exclusive
            with `character_image_urls` — supply one field or the other, not
            both. At least one character image is required across the two
            fields; up to 4 total when `driving_video_url` is absent, but only 1
            when it's present.
        character_image_urls:
          type: array
          items:
            type: string
            format: uri
          description: >-
            Same as `character_image_url`, supplied as one field instead of
            repeated ones — as a JSON array of strings on a multipart request,
            or natively on a JSON request body. Mutually exclusive with
            `character_image_url`.
        priority:
          type: integer
          description: >-
            Admission-queue priority tier, shared by H3 and character-animation
            modes. Defaults to 1000 (top tier) — most callers never need to set
            this. Must be one of the server's configured tiers (currently 1000
            or 0); an unsupported value answers `400 INVALID_REQUEST` with
            "unsupported priority".
          default: 1000
        duration_s:
          type: number
          minimum: 3
          maximum: 15
          default: 5
          description: Target video duration in seconds.
        resolution:
          type: string
          enum:
            - 480p
            - 768p
            - 1080p
          default: 768p
          description: Output resolution tier.
        aspect_ratio:
          type: string
          enum:
            - '16:9'
            - '9:16'
            - '1:1'
            - '4:3'
            - '3:4'
            - '21:9'
          default: '16:9'
          description: Output aspect ratio.
        seed:
          type: integer
          minimum: 0
          description: >-
            Deterministic generation seed. Omit for a random seed. H3 modes only
            — not applicable to character-animation mode.
        watermark:
          type: boolean
          default: false
          description: Whether to burn in a Viggle watermark.
    VideoCreateResponse:
      description: |
        Acceptance acknowledgment for `POST /v1/videos`, shared by all five
        generation modes. This is a queued confirmation, not the final
        result — poll `GET /v1/videos/{video_id}` (or watch it via
        `GET /v1/videos`) until `status` reaches `ready`, then read
        `video_url` from that response.
      type: object
      additionalProperties: false
      required:
        - id
        - status
        - progress
        - created_at
      properties:
        id:
          type: string
          description: >-
            Public video ID — `vid_`-prefixed for the four H3 modes,
            `anim_`-prefixed for character-animation mode.
          minLength: 1
        status:
          description: Lifecycle state at acceptance time; always `queued`.
          allOf:
            - $ref: '#/components/schemas/ResourceStatus'
        progress:
          type: integer
          description: Always null on acceptance.
          nullable: true
          minimum: 0
          maximum: 100
        created_at:
          type: string
          description: ISO 8601 creation timestamp, second precision.
          nullable: true
          format: date-time
    ResourceStatus:
      description: |
        The public lifecycle shared by every asynchronous resource. It carries
        the same values as `RenderStatus`.
      type: string
      enum:
        - queued
        - processing
        - ready
        - failed
        - cancelled
    ErrorResponse:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          description: >-
            Top-level draft-flow error payload containing the stable code,
            message, retry guidance, correlation ID, and error-specific details.
          allOf:
            - $ref: '#/components/schemas/ErrorBody'
    ErrorBody:
      type: object
      additionalProperties: false
      required:
        - code
        - message
        - retryable
        - request_id
        - details
        - remediation
      properties:
        code:
          description: Stable error category from the draft-based render error vocabulary.
          allOf:
            - $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
          description: >-
            Human-readable explanation suitable for logs or presentation to a
            developer.
        retryable:
          type: boolean
          description: >-
            Whether retrying the operation without changing its semantic input
            may succeed.
        request_id:
          type: string
          description: >-
            Correlation identifier for tracing and support. Include it when
            reporting the failure.
        details:
          type: object
          description: Error-specific structured context; available keys depend on `code`.
          additionalProperties: true
        remediation:
          description: Suggested recovery action and optional retry delay.
          allOf:
            - $ref: '#/components/schemas/ErrorRemediation'
    ErrorCode:
      description: |
        The failure vocabulary of the draft-based render flow. The resource
        operations project the same vocabulary onto the lower_snake_case codes
        described by `ResourceError`.
      type: string
      enum:
        - UNAUTHENTICATED
        - INVALID_CREDENTIAL
        - FORBIDDEN
        - INVALID_REQUEST
        - INVALID_CHARACTER
        - INVALID_MOTION
        - UNSUPPORTED_MEDIA
        - CONTENT_POLICY_VIOLATION
        - UPLOAD_REQUIRED
        - UPLOAD_MISMATCH
        - UPLOAD_EXPIRED
        - DRAFT_NOT_FOUND
        - DRAFT_ALREADY_CONSUMED
        - RENDER_NOT_FOUND
        - RENDER_NOT_CANCELLABLE
        - CHARACTER_NOT_FOUND
        - MOTION_NOT_FOUND
        - MOTION_NOT_READY
        - MOTION_TEMPLATE_NOT_FOUND
        - AVATAR_NOT_FOUND
        - ANIMATION_NOT_FOUND
        - EXTRACTION_UNAVAILABLE
        - EXTRACTION_FAILED
        - IDEMPOTENCY_KEY_REUSED
        - INSUFFICIENT_CREDITS
        - RATE_LIMITED
        - SERVICE_BUSY
        - INTERNAL_ERROR
    ErrorRemediation:
      type: object
      additionalProperties: false
      required:
        - action
        - retry_after_ms
      properties:
        action:
          type: string
          description: >-
            Suggested machine-readable or human-readable next action, such as
            retrying later, uploading media again, or correcting input.
        retry_after_ms:
          type: integer
          description: >-
            Recommended delay in milliseconds before retrying; null when no
            timed retry is advised.
          nullable: true
          format: int64
          minimum: 0
  headers:
    RequestId:
      description: Stable request identifier for support and tracing.
      schema:
        type: string
  responses:
    BadRequest:
      description: Invalid request.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    V1Unauthorized:
      description: Missing, invalid, or expired credential.
      headers:
        WWW-Authenticate:
          schema:
            type: string
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    PaymentRequired:
      description: Insufficient credits.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: Credential does not grant access to this resource.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Rate limit exceeded.
      headers:
        Retry-After:
          schema:
            type: integer
            minimum: 0
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalServerError:
      description: Unexpected server error.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ServiceUnavailable:
      description: Service temporarily unavailable.
      headers:
        Retry-After:
          schema:
            type: integer
            minimum: 0
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key or OAuth access token
      description: |
        Server-side SDK clients use a project API key. Remote MCP clients use
        an OAuth access token. Never expose a project API key in browser code.

````