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

# Create Narrated Slideshow

> Create an AI-powered real estate narrated slideshow video

Creates a new video generation task from property images. The video is generated asynchronously - you'll receive a task ID to poll for status or configure a webhook to be notified when complete.

## Request Body

<ParamField body="images" type="string[] | object[]" required>
  Images for the slideshow. Must contain **6 to 15 images**, and **all entries must be unique** (no duplicates allowed).

  Each array entry can take **one of two shapes**. Pick a single shape for the whole array — you cannot mix strings and objects.

  **1. Simple (URL strings):** an array of image URLs. Every image renders as a static still. This is the default behavior.

  **2. Rich (objects):** an array of objects, where each image can opt into a subtle camera move (the "reanimate" option).

  Images should be:

  * Publicly accessible HTTP(S) URLs (not a private/internal address)
  * JPEG, PNG, or WebP format
  * Minimum resolution: 720p recommended

  <Expandable title="Rich image object properties">
    <ParamField body="image_url" type="string" required>
      The image URL. Same rules as the string shape: a public HTTP(S) URL that cannot point to a private/internal address.
    </ParamField>

    <ParamField body="reanimate" type="boolean" default="false">
      When `true`, VibePeak's animation pipeline applies a subtle camera move to this image. When `false` or omitted, the image renders as a static still.
    </ParamField>

    <ParamField body="movement" type="string">
      Which camera move to apply. One of:

      * `traveling_left`
      * `traveling_right`
      * `traveling_up`
      * `traveling_down`
      * `dolly_in`
      * `dolly_out`

      Use `null` (together with `reanimate: true`) to let VibePeak pick a move automatically.
    </ParamField>

    <ParamField body="room_type" type="string">
      Optional scene label (max 64 characters) used to tune the camera move to the kind of room, e.g. `"living room"`, `"bedroom"`, `"kitchen"`, `"bathroom"`, `"garden"`. Omit to use a neutral default.
    </ParamField>
  </Expandable>

  <Warning>
    **`reanimate` and `movement` go together.** When `reanimate` is `true`, `movement` is required — supply one of the six values above **or** `null` (which lets VibePeak pick a move automatically). When `reanimate` is `false` or omitted, `movement` must **not** be set.
  </Warning>

  <Tip>
    For the best visual result, upload images that already match your target format. When `orientation` is set to `portrait`, the pipeline can expand landscape photos to fit a 9:16 output when needed.
  </Tip>
</ParamField>

<ParamField body="orientation" type="string">
  Optional output orientation for the final video.

  | Value       | Description                      |
  | ----------- | -------------------------------- |
  | `landscape` | Generate a horizontal 16:9 video |
  | `portrait`  | Generate a vertical 9:16 video   |

  If omitted, VibePeak auto-detects the orientation from the first image and defaults to `landscape` if detection is not possible.

  <Note>
    Choosing `portrait` is useful when you want social-first vertical output from a mixed or landscape-heavy photo set.
  </Note>
</ParamField>

<ParamField body="voiceover_enabled" type="boolean" default="true">
  Whether the video has a voiceover (narration). Defaults to `true`.

  Set to `false` to create a **music-only** slideshow: omit `voice` and `script`, and the video is produced with background music only (or silent if `background_music` is `false`). Without a voiceover, `subtitle` and `avatar_id` are not available and will be rejected.
</ParamField>

<ParamField body="voice" type="object">
  Voice configuration for the AI-generated narration. **Required when `voiceover_enabled` is `true`** (the default); omit it together with `script` when `voiceover_enabled` is `false`.

  <Expandable title="Voice properties">
    <ParamField body="voice_id" type="string" required>
      Voice ID to use for narration. You can find available voices using the [List Voices](/api-reference/voices/list-voices) endpoint.
    </ParamField>

    <ParamField body="language" type="string" required>
      ISO 639-1 language code for TTS pronunciation (e.g., `en`, `es`, `de`, `fr`, `pt`).

      This helps produce more accurate pronunciation for the target language.

      <Tip>
        Use the same language code as the script content for best results.
      </Tip>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="script" type="string">
  Narration script for text-to-speech. **Required when `voiceover_enabled` is `true`** (the default); omit it together with `voice` when `voiceover_enabled` is `false`.

  The script length must be proportional to the number of images to ensure proper narration pacing (\~3 seconds per image):

  | Images | Min Characters | Max Characters |
  | ------ | -------------- | -------------- |
  | 6      | 240            | 300            |
  | 10     | 400            | 500            |
  | 15     | 600            | 750            |

  **Formula:** `min = images × 40`, `max = images × 50`

  <Warning>
    **Numbers and symbols are allowed.** Write the script naturally — numbers (e.g. `3 bedrooms`, `580.000 €`) and symbols are read correctly by text-to-speech. Only emojis and other non-speech characters (pictographs, control characters) are rejected.
  </Warning>

  <Warning>
    Scripts that are too short or too long for the number of images will be rejected with `SCRIPT_TOO_SHORT` or `SCRIPT_TOO_LONG` error codes.
  </Warning>
</ParamField>

<ParamField body="orchestration_mode" type="string" default="artistic">
  Video orchestration mode that controls scene pacing and transitions.

  | Value        | Description                                                                      |
  | ------------ | -------------------------------------------------------------------------------- |
  | `standard`   | Standard mode with scenes of 3-7 seconds                                         |
  | `artistic`   | Cinematic mode with 2-3 second scenes and visual weight hierarchy (default)      |
  | `sequential` | Images appear in order without AI reordering. Scene duration is auto-calculated. |
</ParamField>

<ParamField body="webhook_url" type="string">
  URL to receive webhook notification when the task completes.

  Must be a valid HTTPS URL. See [Webhooks](/concepts/webhooks) for details.

  <Warning>
    Webhook URLs must use HTTPS and cannot point to private/internal networks (SSRF protection).
  </Warning>
</ParamField>

<ParamField body="avatar_id" type="string">
  Optional UUID of an avatar to include in the video.

  Must be a valid UUID v4 format (e.g., `123e4567-e89b-12d3-a456-426614174000`).

  <Note>
    Requires voiceover. A speaking avatar needs narration, so omit `avatar_id` when `voiceover_enabled` is `false` (music-only or silent video) — the request is rejected otherwise.
  </Note>
</ParamField>

<ParamField body="background_music" type="boolean" default="false">
  Enable AI-generated background music for the video.

  When `true`, generates calm ambient music that matches the video duration. The music is automatically generated with a real estate style (soft piano, elegant, minimal) and mixed at 20% volume to complement the narration.

  <Note>
    Background music generation adds a few seconds to the processing time. If music generation fails, the video will still be created without background music.
  </Note>
</ParamField>

<ParamField body="subtitle" type="object">
  Subtitle configuration for word-level animated overlays. Use `subtitle_style_preset` for quick setup or `styles` for granular control. Providing both will result in a validation error.

  <Note>
    Requires voiceover. Subtitles are derived from the narration's word timing, so omit `subtitle` (or set it to `false`) when `voiceover_enabled` is `false` — the request is rejected otherwise.
  </Note>

  <Expandable title="Subtitle Config properties">
    <ParamField body="subtitle_style_preset" type="string">
      Name of a predefined style to apply.
      Options: `classic`, `cinematic`, `gradient`, `handwritten`, `hustle`, `karaoke_highlight_no_box`, `karaoke_red_box_phrase`, `karaoke_word_box_clip`, `minimal`, `neon`, `one_word_red_box`, `phrase_yellow`, `retro`, `tiktok_default`, `youtube_caption`.
    </ParamField>

    <ParamField body="styles" type="object">
      Granular styling properties for subtitle rendering.

      <Expandable title="Styles properties">
        <ParamField body="alignment" type="integer" default="2">
          SSA alignment: 1-9 (e.g., 2 for bottom-center).
        </ParamField>

        <ParamField body="backColor" type="string" default="#000000">
          Background color in hex format (e.g., `#000000`).
        </ParamField>

        <ParamField body="backOpacity" type="integer" default="100">
          Transparency of background box: 0-255.
        </ParamField>

        <ParamField body="behavior" type="string" default="phrase_blocks">
          Subtitle reveal behavior. Options: `phrase_blocks`, `one_word`, `karaoke_highlight`, `karaoke_box_word`.
        </ParamField>

        <ParamField body="borderStyle" type="integer" default="1">
          Border style: 1 (Outline) or 3 (Box).
        </ParamField>

        <ParamField body="fontFamily" type="string" default="Arial">
          System font name.
        </ParamField>

        <ParamField body="fontSize" type="integer" default="28">
          Font size in pixels (14-96).
        </ParamField>

        <ParamField body="marginL" type="integer" default="40">
          Left margin in pixels (0-500).
        </ParamField>

        <ParamField body="marginR" type="integer" default="40">
          Right margin in pixels (0-500).
        </ParamField>

        <ParamField body="marginV" type="integer" default="200">
          Vertical/Bottom margin in pixels (0-500).
        </ParamField>

        <ParamField body="maxGapSeconds" type="number" default="0.3">
          Max silence (sec) between words before phrase break (0.01-2).
        </ParamField>

        <ParamField body="maxWordsPerLine" type="integer" default="6">
          Automatic line wrapping constraint (1-12).
        </ParamField>

        <ParamField body="outline" type="integer" default="2">
          Thickness of text stroke (0-10).
        </ParamField>

        <ParamField body="outlineColor" type="string" default="#000000">
          Color of text stroke in hex format.
        </ParamField>

        <ParamField body="primaryColor" type="string" default="#FFFFFF">
          Default text color in hex format.
        </ParamField>

        <ParamField body="secondaryColor" type="string" default="#FFFFFF">
          Alt color for highlights/karaoke in hex format.
        </ParamField>

        <ParamField body="shadow" type="integer" default="0">
          Thickness of text drop shadow (0-10).
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="elements_config" type="object">
  Configuration for visual elements and branding overlays. If not provided, elements are disabled by default.

  <Expandable title="Elements Config properties">
    <ParamField body="enabled" type="boolean" default="false">
      Global toggle for the elements overlay feature.
    </ParamField>

    <ParamField body="watermark" type="object">
      Watermark (logo) branding configuration.

      <Expandable title="Watermark properties">
        <ParamField body="url" type="string" required>
          URL of the uploaded watermark image. Must be a valid public URL.
        </ParamField>

        <ParamField body="position" type="string" default="bottom-right">
          Anchor point for the watermark placement.
          Options: `top-left`, `top-center`, `top-right`, `center-left`, `center`, `center-right`, `bottom-left`, `bottom-center`, `bottom-right`.
        </ParamField>

        <ParamField body="size" type="number" default="20">
          Relative scale percentage of the watermark (0-100).
        </ParamField>

        <ParamField body="opacity" type="number" default="100">
          Transparency level (0 for transparent, 100 for opaque).
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="text" type="object">
      Text overlay configuration for static labels or call-to-actions.

      <Expandable title="Text properties">
        <ParamField body="content" type="string" required>
          Text content to be rendered on the video (max 20 characters).
        </ParamField>

        <ParamField body="color" type="string" default="#FFFFFF">
          Hex color code for the text (e.g., `#FFFFFF`).
        </ParamField>

        <ParamField body="font" type="string" default="Inter">
          Font family for the text overlay.
          Options: `Alegreya`, `DM Mono`, `Inter`, `Libre Bodoni`, `Quicksand`, `Roboto`, `Raleway`, `Noto Sans`, `Zilla Slab`.
        </ParamField>

        <ParamField body="opacity" type="number" default="100">
          Transparency level of the text (0-100).
        </ParamField>

        <ParamField body="size" type="number" default="20">
          Font size scale relative to video height (1-100).
        </ParamField>

        <ParamField body="position" type="string" default="center">
          Anchor point for the text placement. Same options as watermark position.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

### Scene Duration Overrides

For fine-tuned control over video pacing, you can override the default scene durations.

<ParamField body="min_scene_duration_seconds" type="number">
  Minimum duration for each scene in seconds. Range: 1-30.
</ParamField>

<ParamField body="max_scene_duration_seconds" type="number">
  Maximum duration for each scene in seconds. Range: 1-30.
</ParamField>

### Artistic Mode Duration Overrides

When using `orchestration_mode: "artistic"`, you can separately control hero and secondary scene durations.

<ParamField body="hero_min_duration_seconds" type="number">
  Minimum duration for hero (primary) scenes in seconds. Range: 1-30.
</ParamField>

<ParamField body="hero_max_duration_seconds" type="number">
  Maximum duration for hero (primary) scenes in seconds. Range: 1-30.
</ParamField>

<ParamField body="secondary_min_duration_seconds" type="number">
  Minimum duration for secondary scenes in seconds. Range: 1-30.
</ParamField>

<ParamField body="secondary_max_duration_seconds" type="number">
  Maximum duration for secondary scenes in seconds. Range: 1-30.
</ParamField>

### Test Mode

<ParamField body="test_scenario" type="string" default="success">
  Sandbox-only scenario selector. **Honored only for test-mode (`vpk_test_`) keys and silently ignored for live keys**, so it is safe to leave in shared request-building code.

  | Value     | Outcome                                                       |
  | --------- | ------------------------------------------------------------- |
  | `success` | Synthetic task completes with a watermarked sample video      |
  | `fail`    | Synthetic task fails with a sanitized, public-safe error      |
  | `slow`    | Like `success`, but completes after a noticeably longer delay |

  <Note>
    Test mode never spends credits or runs the real pipeline. See [Test Mode](/concepts/test-mode) for the full sandbox model.
  </Note>
</ParamField>

## Response

<ResponseField name="task_id" type="string" required>
  Unique identifier for the task. Use this to check status.
</ResponseField>

<ResponseField name="status" type="string" required>
  Initial task status. Always `queued` for new tasks.
</ResponseField>

<ResponseField name="queue_position" type="integer">
  Position in the processing queue.
</ResponseField>

<ResponseField name="estimated_completion" type="string">
  Estimated completion time (ISO 8601 format).
</ResponseField>

<ResponseField name="created_at" type="string" required>
  Task creation timestamp (ISO 8601 format).
</ResponseField>

<ResponseField name="livemode" type="boolean" required>
  `true` for live (`vpk_live_`) requests; `false` for test-mode (`vpk_test_`) requests. Use this to tell a real task from a sandbox one. See [Test Mode](/concepts/test-mode).
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.vibepeak.ai/v1/real-estate/narrated-slideshow \
    -H "Authorization: Bearer vpk_live_xxxxx" \
    -H "Content-Type: application/json" \
    -d '{
      "images": [
        "https://example.com/property/living-room.jpg",
        "https://example.com/property/kitchen.jpg",
        "https://example.com/property/bedroom.jpg",
        "https://example.com/property/bathroom.jpg",
        "https://example.com/property/backyard.jpg",
        "https://example.com/property/exterior.jpg"
      ],
      "voice": {
        "voice_id": "EXAVITQu4vr4xnSDxMaL",
        "language": "en"
      },
      "orientation": "portrait",
      "script": "Welcome to this stunning 3-bedroom home in the heart of the city. The open-concept living area floods with natural light...",
      "orchestration_mode": "standard",
      "webhook_url": "https://yourserver.com/webhooks/vibepeak",
      "background_music": true,
      "subtitle": {
        "subtitle_style_preset": "classic"
      },
      "elements_config": {
        "enabled": true,
        "watermark": {
          "url": "https://example.com/logo.png",
          "position": "bottom-right",
          "size": 20,
          "opacity": 100
        },
        "text": {
          "content": "Powered by Vibepeak",
          "color": "#FFFFFF",
          "font": "Inter",
          "size": 20,
          "position": "center"
        }
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.vibepeak.ai/v1/real-estate/narrated-slideshow', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer vpk_live_xxxxx',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      images: [
        'https://example.com/property/living-room.jpg',
        'https://example.com/property/kitchen.jpg',
        'https://example.com/property/bedroom.jpg',
        'https://example.com/property/bathroom.jpg',
        'https://example.com/property/backyard.jpg',
        'https://example.com/property/exterior.jpg'
      ],
      voice: {
        voice_id: 'EXAVITQu4vr4xnSDxMaL',
        language: 'en'
      },
      orientation: 'portrait',
      script: 'Welcome to this stunning 3-bedroom home...',
      orchestration_mode: 'standard',
      webhook_url: 'https://yourserver.com/webhooks/vibepeak',
      background_music: true,
      subtitle: {
        subtitle_style_preset: 'classic'
      },
      elements_config: {
        enabled: true,
        watermark: {
          url: 'https://example.com/logo.png',
          position: 'bottom-right',
          size: 20,
          opacity: 100
        },
        text: {
          content: 'Powered by Vibepeak',
          color: '#FFFFFF',
          font: 'Inter',
          size: 20,
          position: 'center'
        }
      }
    })
  });

  const task = await response.json();
  console.log(`Task created: ${task.task_id}`);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.vibepeak.ai/v1/real-estate/narrated-slideshow',
      headers={
          'Authorization': 'Bearer vpk_live_xxxxx',
          'Content-Type': 'application/json'
      },
      json={
          'images': [
              'https://example.com/property/living-room.jpg',
              'https://example.com/property/kitchen.jpg',
              'https://example.com/property/bedroom.jpg',
              'https://example.com/property/bathroom.jpg',
              'https://example.com/property/backyard.jpg',
              'https://example.com/property/exterior.jpg'
          ],
          'voice': {
              'voice_id': 'EXAVITQu4vr4xnSDxMaL',
              'language': 'en'
          },
          'orientation': 'portrait',
          'script': 'Welcome to this stunning 3-bedroom home...',
          'orchestration_mode': 'standard',
          'webhook_url': 'https://yourserver.com/webhooks/vibepeak',
          'background_music': True,
          'subtitle': {
              'subtitle_style_preset': 'classic'
          },
          'elements_config': {
              'enabled': True,
              'watermark': {
                  'url': 'https://example.com/logo.png',
                  'position': 'bottom_right',
                  'size': 20,
                  'opacity': 100
              },
              'text': {
                  'content': 'Powered by Vibepeak',
                  'color': '#FFFFFF',
                  'font': 'Inter',
                  'size': 20,
                  'position': 'center'
              }
          }
      }
  )

  task = response.json()
  print(f"Task created: {task['task_id']}")
  ```
</RequestExample>

### Example: images with camera motion

To apply subtle per-image camera moves, pass `images` as an array of objects with `reanimate: true` and a `movement` value (or `null` to let VibePeak pick one). Remember you cannot mix strings and objects in the same array.

<CodeGroup>
  ```bash cURL (with camera motion) theme={null}
  curl -X POST https://api.vibepeak.ai/v1/real-estate/narrated-slideshow \
    -H "Authorization: Bearer vpk_live_xxxxx" \
    -H "Content-Type: application/json" \
    -d '{
      "images": [
        { "image_url": "https://example.com/property/living-room.jpg", "reanimate": true, "movement": "dolly_in", "room_type": "living room" },
        { "image_url": "https://example.com/property/kitchen.jpg", "reanimate": true, "movement": "traveling_left" },
        { "image_url": "https://example.com/property/bedroom.jpg", "reanimate": true, "movement": null },
        { "image_url": "https://example.com/property/bathroom.jpg", "reanimate": false },
        { "image_url": "https://example.com/property/backyard.jpg", "reanimate": true, "movement": "traveling_up" },
        { "image_url": "https://example.com/property/exterior.jpg", "reanimate": true, "movement": "dolly_out" }
      ],
      "voice": {
        "voice_id": "EXAVITQu4vr4xnSDxMaL",
        "language": "en"
      },
      "script": "Welcome to this stunning 3-bedroom home in the heart of the city. The open-concept living area floods with natural light..."
    }'
  ```

  ```javascript Node.js (with camera motion) theme={null}
  const response = await fetch('https://api.vibepeak.ai/v1/real-estate/narrated-slideshow', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer vpk_live_xxxxx',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      images: [
        { image_url: 'https://example.com/property/living-room.jpg', reanimate: true, movement: 'dolly_in', room_type: 'living room' },
        { image_url: 'https://example.com/property/kitchen.jpg', reanimate: true, movement: 'traveling_left' },
        { image_url: 'https://example.com/property/bedroom.jpg', reanimate: true, movement: null },
        { image_url: 'https://example.com/property/bathroom.jpg', reanimate: false },
        { image_url: 'https://example.com/property/backyard.jpg', reanimate: true, movement: 'traveling_up' },
        { image_url: 'https://example.com/property/exterior.jpg', reanimate: true, movement: 'dolly_out' }
      ],
      voice: {
        voice_id: 'EXAVITQu4vr4xnSDxMaL',
        language: 'en'
      },
      script: 'Welcome to this stunning 3-bedroom home...'
    })
  });

  const task = await response.json();
  console.log(`Task created: ${task.task_id}`);
  ```

  ```python Python (with camera motion) theme={null}
  import requests

  response = requests.post(
      'https://api.vibepeak.ai/v1/real-estate/narrated-slideshow',
      headers={
          'Authorization': 'Bearer vpk_live_xxxxx',
          'Content-Type': 'application/json'
      },
      json={
          'images': [
              {'image_url': 'https://example.com/property/living-room.jpg', 'reanimate': True, 'movement': 'dolly_in', 'room_type': 'living room'},
              {'image_url': 'https://example.com/property/kitchen.jpg', 'reanimate': True, 'movement': 'traveling_left'},
              {'image_url': 'https://example.com/property/bedroom.jpg', 'reanimate': True, 'movement': None},
              {'image_url': 'https://example.com/property/bathroom.jpg', 'reanimate': False},
              {'image_url': 'https://example.com/property/backyard.jpg', 'reanimate': True, 'movement': 'traveling_up'},
              {'image_url': 'https://example.com/property/exterior.jpg', 'reanimate': True, 'movement': 'dolly_out'}
          ],
          'voice': {
              'voice_id': 'EXAVITQu4vr4xnSDxMaL',
              'language': 'en'
          },
          'script': 'Welcome to this stunning 3-bedroom home...'
      }
  )

  task = response.json()
  print(f"Task created: {task['task_id']}")
  ```
</CodeGroup>

<ResponseExample>
  ```json 202 Accepted theme={null}
  {
    "task_id": "task_abc123xyz",
    "status": "queued",
    "queue_position": 3,
    "estimated_completion": "2026-01-04T12:30:00Z",
    "created_at": "2026-01-04T12:00:00Z",
    "livemode": true
  }
  ```

  ```json 202 Accepted (Test Mode) theme={null}
  {
    "task_id": "task_abc123xyz",
    "status": "queued",
    "message": "Video generation request accepted",
    "created_at": "2026-01-04T12:00:00Z",
    "livemode": false,
    "request_id": "req_xyz123",
    "_links": {
      "self": "/v1/tasks/task_abc123xyz",
      "poll_interval_seconds": 30
    }
  }
  ```

  ```json 400 Bad Request (Validation Error) theme={null}
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "images must contain between 6 and 15 URLs",
      "request_id": "req_xyz123"
    }
  }
  ```

  ```json 400 Bad Request (Subtitle Conflict) theme={null}
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Provide either 'subtitle_style_preset' or 'styles', but not both.",
      "request_id": "req_xyz123"
    }
  }
  ```

  ```json 400 Bad Request (Script Too Short) theme={null}
  {
    "error": {
      "code": "SCRIPT_TOO_SHORT",
      "message": "Script is too short for 6 images. Minimum 240 characters required (40 per image), but got 100.",
      "request_id": "req_xyz123",
      "details": {
        "script_length": 100,
        "min_length": 240,
        "max_length": 300,
        "image_count": 6
      }
    }
  }
  ```

  ```json 400 Bad Request (Script Too Long) theme={null}
  {
    "error": {
      "code": "SCRIPT_TOO_LONG",
      "message": "Script is too long for 6 images. Maximum 300 characters allowed (50 per image), but got 500.",
      "request_id": "req_xyz123",
      "details": {
        "script_length": 500,
        "min_length": 240,
        "max_length": 300,
        "image_count": 6
      }
    }
  }
  ```

  ```json 400 Bad Request (Elements Config Missing) theme={null}
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "If elements_config is enabled, you must provide at least a 'watermark' or 'text' configuration.",
      "request_id": "req_xyz123",
      "details": {
        "path": ["elements_config", "enabled"]
      }
    }
  }
  ```

  ```json 400 Bad Request (Invalid Hex Color) theme={null}
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Invalid hex color",
      "request_id": "req_xyz123",
      "details": {
        "path": ["elements_config", "text", "color"]
      }
    }
  }
  ```

  ```json 400 Bad Request (Invalid Characters) theme={null}
  {
    "error": {
      "code": "SCRIPT_INVALID_CHARACTERS",
      "message": "Script contains characters not supported by text-to-speech: \"🏠\". Emojis and other non-speech symbols are not allowed.",
      "request_id": "req_xyz123",
      "details": {
        "invalid_characters": ["3", "2"]
      }
    }
  }
  ```

  ```json 429 Too Many Requests theme={null}
  {
    "error": {
      "code": "CONCURRENCY_LIMIT_EXCEEDED",
      "message": "You have reached your concurrent task limit of 1. Please wait for existing tasks to complete.",
      "request_id": "req_xyz123",
      "details": {
        "limit": 1,
        "in_flight": 1,
        "plan": "Plus"
      }
    }
  }
  ```
</ResponseExample>

## Error Codes

| Code                         | Status | Description                                                                     |
| ---------------------------- | ------ | ------------------------------------------------------------------------------- |
| `VALIDATION_ERROR`           | 400    | Invalid request parameters                                                      |
| `INVALID_IMAGE_URL`          | 400    | One or more image URLs are inaccessible or invalid                              |
| `INVALID_WATERMARK_URL`      | 400    | The `elements_config` watermark URL is inaccessible or does not return an image |
| `SCRIPT_TOO_SHORT`           | 400    | Script is too short for the number of images (min 40 chars/image)               |
| `SCRIPT_TOO_LONG`            | 400    | Script is too long for the number of images (max 50 chars/image)                |
| `SCRIPT_INVALID_CHARACTERS`  | 400    | Script contains emojis or other non-speech characters not supported by TTS      |
| `INVALID_API_KEY`            | 401    | Invalid or missing API key                                                      |
| `PLAN_REQUIRED`              | 403    | Plan doesn't include API access                                                 |
| `CONCURRENCY_LIMIT_EXCEEDED` | 429    | Concurrent task limit reached                                                   |
| `SERVICE_UNAVAILABLE`        | 503    | Video generation service temporarily unavailable                                |
| `SERVICE_TIMEOUT`            | 504    | Service request timed out                                                       |

See [Error Handling](/concepts/error-handling) for more details.

## Next Steps

After creating a task:

1. **Poll for status**: Use [Get Task](/api-reference/videos/get-task) to check progress
2. **Wait for webhook**: If configured, receive notification when complete
3. **Download video**: Access the video URL from the completed task result
