---
title: "Validate render request"
canonical_url: https://docs.zvid.io/docs/endpoints/validate-render-job/
source: docs/endpoints/validate-render-job.api.mdx
content_revision: 6338386a1dd87e4a
---

# Validate render request

`POST /api/render/validate/api-key`

Free preflight through the same resolution and plan-aware validation pipeline as render submission. Returns the resolved payload, estimated credits and layout warnings. No job is queued and no credits are reserved. A passing result does not guarantee a remote asset will remain accessible.

## Authentication

`x-api-key: YOUR_API_KEY` (header). API key for authentication. Create one in your dashboard.

Create API keys at [app.zvid.io/api-keys](https://app.zvid.io/api-keys). Keep credentials on your server.

## Parameters

This operation has no path, query, or additional header parameters.

## Request

The shell examples read credentials from `ZVID_API_KEY` (or `ZVID_ACCESS_TOKEN` for Bearer authentication). Set that variable in your environment. Replace sample project, template, job, and asset identifiers with values from your own account.

A request body is required.


### application/json

Provide either `payload` (a full project) or `template` (a stored template id) — not both.

Unknown properties are rejected. Exactly one of the listed alternatives must match.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `body.payload` | [RenderPayload](#schema-render-payload) | No |  |
| `body.template` | string | No | Stored template id (`tpl_…`). Mutually exclusive with `payload`. Pattern: `^tpl_[A-Za-z0-9]{20}$`. |
| `body.variables` | object | No | Request-time values override project defaults for BOTH inline payloads and stored templates. |
| `body.overrides` | [RenderOverrides](#schema-render-overrides) | No |  |
| `body.webhookUrl` | string | No | One-off unsigned POST for this job on completion or failure. Use a registered webhook for HMAC signatures. The hosted API requires a public HTTPS address; private destinations are rejected. Maximum length: `2048`. Format: `uri`. |
| `body.jobId` | string | No | Optional caller-supplied UUID. This is not a documented idempotency guarantee: do not blindly resubmit after a timeout; first check this job ID. Format: `uuid`. |

**oneOf alternative 1**

Required properties: `payload`.

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "not": {
    "required": [
      "template"
    ]
  }
}
```
**oneOf alternative 2**

Required properties: `template`.

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "not": {
    "required": [
      "payload"
    ]
  }
}
```

### Request example: video


```bash
curl --request POST 'https://api.zvid.io/api/render/validate/api-key' \
  --header "x-api-key: $ZVID_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "payload": {
    "name": "Hello Zvid",
    "width": 1280,
    "height": 720,
    "duration": 5,
    "visuals": [
      {
        "type": "TEXT",
        "text": "Hello, Zvid!",
        "position": "center-center",
        "style": {
          "fontSize": 64,
          "color": "#ffffff"
        }
      }
    ],
    "backgroundColor": "#14213d"
  }
}'
```

### Request example: image


```bash
curl --request POST 'https://api.zvid.io/api/render/validate/api-key' \
  --header "x-api-key: $ZVID_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "payload": {
    "type": "image",
    "name": "Hello image",
    "width": 1200,
    "height": 630,
    "outputFormat": "png",
    "backgroundColor": "#14213d",
    "visuals": [
      {
        "type": "TEXT",
        "text": "Hello, Zvid!",
        "position": "center-center",
        "style": {
          "fontSize": 64,
          "color": "#ffffff"
        }
      }
    ]
  }
}'
```

### Request example: template


```bash
curl --request POST 'https://api.zvid.io/api/render/validate/api-key' \
  --header "x-api-key: $ZVID_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "template": "tpl_abcdefghijklmnopqrst",
  "variables": {
    "title": "Summer sale"
  }
}'
```

## Responses


### HTTP 200

Successful response

Content type: `application/json`.

Required properties: `valid`, `creditsRequired`, `payload`, `warnings`, `schemaVersion`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `response.valid` | `true` | Yes |  |
| `response.message` | string | No |  |
| `response.creditsRequired` | integer | Yes |  |
| `response.payload` | [ResolvedRenderPayload](#schema-resolved-render-payload) | Yes |  |
| `response.warnings` | array of object | Yes |  |
| `response.schemaVersion` | string | Yes |  |

**Nested field: `response.warnings`**

**Array item: `response.warnings[]`**

Type: object.


**Representative successful response response**

```json
{
  "valid": true,
  "message": "Payload is valid and would be accepted for rendering",
  "creditsRequired": 5,
  "payload": {
    "duration": 5,
    "width": 1280,
    "height": 720
  },
  "warnings": [],
  "schemaVersion": "1.0.0"
}
```

### HTTP 400

Invalid input or semantic validation error

Content type: `application/json`.

Required properties: `error`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `response.error` | string | Yes |  |
| `response.message` | string | No |  |
| `response.details` | array of object | No |  |
| `response.planLimits` | object | No | Present on render validation errors; contains the authenticated user's current render limits. |

**Nested field: `response.details`**

**Array item: `response.details[]`**

Unknown properties are rejected. Required properties: `field`, `message`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `response.details[].field` | string | Yes |  |
| `response.details[].message` | string | Yes |  |


### HTTP 401

Missing or invalid credentials

Content type: `application/json`.

Unknown properties are rejected. Required properties: `error`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `response.error` | string | Yes | Error type |
| `response.message` | string | No | Human-readable error message |


### HTTP 404

Not found for this account

Content type: `application/json`.

Required properties: `error`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `response.error` | string | Yes | Error type |
| `response.message` | string | No | Human-readable error message |


### HTTP 500

Server error

Content type: `application/json`.

Required properties: `error`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `response.error` | string | Yes | Error type |
| `response.message` | string | No | Human-readable error message |


## Schema definitions

The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract.


### schema render job request

**RenderJobRequest**


Provide either `payload` (a full project) or `template` (a stored template id) — not both.

Unknown properties are rejected. Exactly one of the listed alternatives must match.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `RenderJobRequest.payload` | [RenderPayload](#schema-render-payload) | No |  |
| `RenderJobRequest.template` | string | No | Stored template id (`tpl_…`). Mutually exclusive with `payload`. Pattern: `^tpl_[A-Za-z0-9]{20}$`. |
| `RenderJobRequest.variables` | object | No | Request-time values override project defaults for BOTH inline payloads and stored templates. |
| `RenderJobRequest.overrides` | [RenderOverrides](#schema-render-overrides) | No |  |
| `RenderJobRequest.webhookUrl` | string | No | One-off unsigned POST for this job on completion or failure. Use a registered webhook for HMAC signatures. The hosted API requires a public HTTPS address; private destinations are rejected. Maximum length: `2048`. Format: `uri`. |
| `RenderJobRequest.jobId` | string | No | Optional caller-supplied UUID. This is not a documented idempotency guarantee: do not blindly resubmit after a timeout; first check this job ID. Format: `uuid`. |

**oneOf alternative 1**

Required properties: `payload`.

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "not": {
    "required": [
      "template"
    ]
  }
}
```
**oneOf alternative 2**

Required properties: `template`.

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "not": {
    "required": [
      "payload"
    ]
  }
}
```


### schema render payload

**RenderPayload**


The payload accepted by render requests and stored templates, before variable substitution, conditions and scene iteration. Whole placeholders preserve the variable's type. Run authenticated validation to resolve values, enforce semantic rules and apply your account limits. Stored video templates require explicit positive scene durations.

Unknown properties are rejected. All listed constraints apply.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `RenderPayload.type` | `"video"` / `"image"` | No | Render type. "image" produces a still (png/jpg/webp) and forbids all time-domain fields. Default: `"video"`. |
| `RenderPayload.name` | string OR string | No | At least one listed alternative must match. |
| `RenderPayload.resolution` | `"sd"` / `"hd"` / `"full-hd"` / `"squared"` / `"youtube-short"` / `"youtube-video"` / `"tiktok"` / `"instagram-reel"` / `"instagram-post"` / `"instagram-story"` / `"instagram-feed"` / `"twitter-landscape"` / `"twitter-portrait"` / `"twitter-square"` / `"facebook-video"` / `"facebook-story"` / `"facebook-post"` / `"snapchat"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `RenderPayload.width` | integer OR string | No | At least one listed alternative must match. |
| `RenderPayload.height` | integer OR string | No | At least one listed alternative must match. |
| `RenderPayload.duration` | number OR string | No | At least one listed alternative must match. |
| `RenderPayload.durationMode` | `"auto"` / `"fixed"` OR string | No | At least one listed alternative must match. |
| `RenderPayload.frameRate` | integer OR string | No | At least one listed alternative must match. |
| `RenderPayload.outputFormat` | `"mp4"` / `"mov"` / `"avi"` / `"webm"` / `"png"` / `"jpg"` / `"jpeg"` / `"webp"` OR string | No | At least one listed alternative must match. |
| `RenderPayload.backgroundColor` | string OR string | No | At least one listed alternative must match. |
| `RenderPayload.snapshotTime` | number OR string | No | At least one listed alternative must match. |
| `RenderPayload.quality` | integer OR string | No | At least one listed alternative must match. |
| `RenderPayload.transparent` | boolean OR string | No | At least one listed alternative must match. |
| `RenderPayload.visuals` | array of [Visual](#schema-visual) OR string | No | At least one listed alternative must match. |
| `RenderPayload.audios` | array of [AudioItem](#schema-audio-item) OR string | No | At least one listed alternative must match. |
| `RenderPayload.scenes` | array of [Scene](#schema-scene) OR string | No | At least one listed alternative must match. |
| `RenderPayload.thumbnail` | string OR string | No | At least one listed alternative must match. |
| `RenderPayload.subtitle` | [Subtitle](#schema-subtitle) OR string | No | At least one listed alternative must match. |
| `RenderPayload.variables` | object | No | Default variable values; request variables override these. Scene defaults shadow project values inside that scene. |

**Nested field: `RenderPayload.name`**

At least one listed alternative must match.

**anyOf alternative 1**

Output name. Letters, digits, space, _ and - ONLY (no punctuation).

Default: `"unnamed"`. Maximum length: `1000`. Pattern: `^[a-zA-Z0-9_\- ]+$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.resolution`**

At least one listed alternative must match.

**anyOf alternative 1**

Canvas preset. Any value except "custom" OVERRIDES width/height.

Type: `"sd"` / `"hd"` / `"full-hd"` / `"squared"` / `"youtube-short"` / `"youtube-video"` / `"tiktok"` / `"instagram-reel"` / `"instagram-post"` / `"instagram-story"` / `"instagram-feed"` / `"twitter-landscape"` / `"twitter-portrait"` / `"twitter-square"` / `"facebook-video"` / `"facebook-story"` / `"facebook-post"` / `"snapchat"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.width`**

At least one listed alternative must match.

**anyOf alternative 1**

Canvas width in px (plan-limited).

Default: `1280`. Minimum: `1`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.height`**

At least one listed alternative must match.

**anyOf alternative 1**

Canvas height in px (plan-limited).

Default: `720`. Minimum: `1`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.duration`**

At least one listed alternative must match.

**anyOf alternative 1**

Video length in seconds (plan-limited). With durationMode auto this is an optional minimum. Scene projects always include the full scene sequence.

Default: `10`. Minimum: `0.1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.durationMode`**

At least one listed alternative must match.

**anyOf alternative 1**

Auto derives length from scenes, explicit element ends and captions. Supply videoEnd/exitEnd and audioEnd/exit for API submission. Omit to preserve legacy timing.

Type: `"auto"` / `"fixed"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.frameRate`**

At least one listed alternative must match.

**anyOf alternative 1**

Frames per second.

Default: `30`. Minimum: `1`. Maximum: `60`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.outputFormat`**

At least one listed alternative must match.

**anyOf alternative 1**

Videos: mp4 (default), mov, avi, webm. Images (type: "image"): png (default), jpg, jpeg, webp.

Type: `"mp4"` / `"mov"` / `"avi"` / `"webm"` / `"png"` / `"jpg"` / `"jpeg"` / `"webp"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.backgroundColor`**

At least one listed alternative must match.

**anyOf alternative 1**

Canvas background color.

Default: `"#ffffff"`. Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.snapshotTime`**

At least one listed alternative must match.

**anyOf alternative 1**

Image renders only: second of the (virtual) timeline to snapshot.

Minimum: `0`. Maximum: `3600`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.quality`**

At least one listed alternative must match.

**anyOf alternative 1**

Image renders only: jpg/webp quality (NOT valid for png).

Minimum: `1`. Maximum: `100`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.transparent`**

At least one listed alternative must match.

**anyOf alternative 1**

Image renders only: transparent background (png/webp only, not jpg).

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.visuals`**

At least one listed alternative must match.

**anyOf alternative 1**

Elements shown for the whole project (outside scenes).

**Array item: `RenderPayload.visuals[]`**

Schema: [Visual](#schema-visual).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.audios`**

At least one listed alternative must match.

**anyOf alternative 1**

Project-level audio tracks (video only, plan-limited count).

**Array item: `RenderPayload.audios[]`**

Schema: [AudioItem](#schema-audio-item).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.scenes`**

At least one listed alternative must match.

**anyOf alternative 1**

Sequential timeline segments (video only, plan-limited count).

**Array item: `RenderPayload.scenes[]`**

Schema: [Scene](#schema-scene).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.thumbnail`**

At least one listed alternative must match.

**anyOf alternative 1**

Custom thumbnail image URL (video only). Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs.

Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `RenderPayload.subtitle`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Subtitle](#schema-subtitle).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**allOf alternative 1**

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "if": {
    "properties": {
      "type": {
        "const": "image"
      }
    },
    "required": [
      "type"
    ]
  },
  "then": {
    "properties": {
      "duration": false,
      "durationMode": false,
      "frameRate": false,
      "audios": false,
      "scenes": false,
      "thumbnail": false,
      "subtitle": false,
      "outputFormat": {
        "anyOf": [
          {
            "enum": [
              "png",
              "jpg",
              "jpeg",
              "webp"
            ],
            "default": "png"
          },
          {
            "type": "string",
            "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
          }
        ]
      },
      "visuals": {
        "anyOf": [
          {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ImageVisual"
                },
                {
                  "$ref": "#/components/schemas/TextVisual"
                },
                {
                  "$ref": "#/components/schemas/SvgVisual"
                }
              ]
            }
          },
          {
            "type": "string",
            "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
          }
        ]
      }
    }
  },
  "else": {
    "properties": {
      "snapshotTime": false,
      "quality": false,
      "transparent": false,
      "outputFormat": {
        "anyOf": [
          {
            "enum": [
              "mp4",
              "mov",
              "avi",
              "webm"
            ],
            "default": "mp4"
          },
          {
            "type": "string",
            "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
          }
        ]
      }
    }
  }
}
```
**allOf alternative 2**

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "if": {
    "properties": {
      "type": {
        "const": "image"
      },
      "outputFormat": {
        "enum": [
          "png"
        ]
      }
    },
    "required": [
      "type",
      "outputFormat"
    ]
  },
  "then": {
    "properties": {
      "quality": false
    }
  }
}
```
**allOf alternative 3**

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "if": {
    "properties": {
      "type": {
        "const": "image"
      }
    },
    "required": [
      "type"
    ],
    "not": {
      "required": [
        "outputFormat"
      ]
    }
  },
  "then": {
    "properties": {
      "quality": false
    }
  }
}
```
**allOf alternative 4**

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "if": {
    "properties": {
      "type": {
        "const": "image"
      },
      "outputFormat": {
        "enum": [
          "jpg",
          "jpeg"
        ]
      }
    },
    "required": [
      "type",
      "outputFormat"
    ]
  },
  "then": {
    "properties": {
      "transparent": {
        "anyOf": [
          {
            "const": false
          },
          {
            "type": "string",
            "pattern": "\\{\\{\\s*[A-Za-z][A-Za-z0-9_]*(?:\\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\\s*\\}\\}"
          }
        ]
      }
    }
  }
}
```


### schema visual

**Visual**


One canvas element, discriminated by `type` (IMAGE | VIDEO | GIF | SVG | TEXT, case-insensitive).

Required properties: `type`. Exactly one of the listed alternatives must match.

**oneOf alternative 1**

Schema: [ImageVisual](#schema-image-visual).

**oneOf alternative 2**

Schema: [VideoVisual](#schema-video-visual).

**oneOf alternative 3**

Schema: [GifVisual](#schema-gif-visual).

**oneOf alternative 4**

Schema: [SvgVisual](#schema-svg-visual).

**oneOf alternative 5**

Schema: [TextVisual](#schema-text-visual).


### schema image visual

**ImageVisual**


A raster image placed on the canvas.

Unknown properties are rejected. Required properties: `type`, `src`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ImageVisual.type` | string | Yes | "IMAGE" (case-insensitive). Pattern: `^[Ii][Mm][Aa][Gg][Ee]$`. |
| `ImageVisual.x` | number OR string | No | At least one listed alternative must match. |
| `ImageVisual.y` | number OR string | No | At least one listed alternative must match. |
| `ImageVisual.width` | number OR string | No | At least one listed alternative must match. |
| `ImageVisual.height` | number OR string | No | At least one listed alternative must match. |
| `ImageVisual.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `ImageVisual.anchor` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `ImageVisual.resize` | `"contain"` / `"cover"` OR string | No | At least one listed alternative must match. |
| `ImageVisual.enterBegin` | number OR string | No | At least one listed alternative must match. |
| `ImageVisual.enterEnd` | number OR string | No | At least one listed alternative must match. |
| `ImageVisual.exitBegin` | number OR string | No | At least one listed alternative must match. |
| `ImageVisual.exitEnd` | number OR string | No | At least one listed alternative must match. |
| `ImageVisual.opacity` | number OR string | No | At least one listed alternative must match. |
| `ImageVisual.angle` | number OR string | No | At least one listed alternative must match. |
| `ImageVisual.flipV` | boolean OR string | No | At least one listed alternative must match. |
| `ImageVisual.flipH` | boolean OR string | No | At least one listed alternative must match. |
| `ImageVisual.track` | integer OR string | No | At least one listed alternative must match. |
| `ImageVisual.enterAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `ImageVisual.exitAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `ImageVisual.src` | string OR string | Yes | At least one listed alternative must match. |
| `ImageVisual.cropParams` | [CropParams](#schema-crop-params) OR string | No | At least one listed alternative must match. |
| `ImageVisual.filter` | [Filter](#schema-filter) OR string | No | At least one listed alternative must match. |
| `ImageVisual.chromaKey` | [ChromaKey](#schema-chroma-key) OR string | No | At least one listed alternative must match. |
| `ImageVisual.zoom` | [Zoom](#schema-zoom) OR string | No | At least one listed alternative must match. |
| `ImageVisual.radius` | [Radius](#schema-radius) OR string | No | At least one listed alternative must match. |
| `ImageVisual.condition` | any JSON value | No | A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported. |

**Nested field: `ImageVisual.x`**

At least one listed alternative must match.

**anyOf alternative 1**

Anchor-point X in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.y`**

At least one listed alternative must match.

**anyOf alternative 1**

Anchor-point Y in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.width`**

At least one listed alternative must match.

**anyOf alternative 1**

Element width in px (plan-limited).

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.height`**

At least one listed alternative must match.

**anyOf alternative 1**

Element height in px (plan-limited).

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.position`**

At least one listed alternative must match.

**anyOf alternative 1**

Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. "bottom-center" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless "custom". Two elements with the same preset render exactly stacked.

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.anchor`**

At least one listed alternative must match.

**anyOf alternative 1**

Which point of the ELEMENT sits at the position point. Defaults to the position preset itself ("custom" defaults to top-left).

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.resize`**

At least one listed alternative must match.

**anyOf alternative 1**

How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas.

Type: `"contain"` / `"cover"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.enterBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the element starts appearing (enter animation start).

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.enterEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the enter animation finishes. Must be >= enterBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.exitBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the exit animation starts.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.exitEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the element is fully gone. Must be >= exitBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.opacity`**

At least one listed alternative must match.

**anyOf alternative 1**

Element opacity, 0..1.

Minimum: `0`. Maximum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.angle`**

At least one listed alternative must match.

**anyOf alternative 1**

Rotation in degrees.

Minimum: `-360`. Maximum: `360`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.flipV`**

At least one listed alternative must match.

**anyOf alternative 1**

Flip vertically.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.flipH`**

At least one listed alternative must match.

**anyOf alternative 1**

Flip horizontally.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.track`**

At least one listed alternative must match.

**anyOf alternative 1**

Z-order track; higher tracks render on top.

Minimum: `0`. Maximum: `1000000`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.enterAnimation`**

At least one listed alternative must match.

**anyOf alternative 1**

Enter animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.exitAnimation`**

At least one listed alternative must match.

**anyOf alternative 1**

Exit animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.src`**

At least one listed alternative must match.

**anyOf alternative 1**

Source media URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs.

Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.cropParams`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [CropParams](#schema-crop-params).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.filter`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Filter](#schema-filter).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.chromaKey`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [ChromaKey](#schema-chroma-key).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.zoom`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Zoom](#schema-zoom).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ImageVisual.radius`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Radius](#schema-radius).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema crop params

**CropParams**


Source crop rectangle in source-media pixels.

Unknown properties are rejected. Required properties: `x`, `y`, `width`, `height`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `CropParams.x` | number OR string | Yes | At least one listed alternative must match. |
| `CropParams.y` | number OR string | Yes | At least one listed alternative must match. |
| `CropParams.width` | number OR string | Yes | At least one listed alternative must match. |
| `CropParams.height` | number OR string | Yes | At least one listed alternative must match. |

**Nested field: `CropParams.x`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `CropParams.y`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `CropParams.width`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `CropParams.height`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema filter

**Filter**


CSS-like color filters.

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Filter.brightness` | number OR string | No | At least one listed alternative must match. |
| `Filter.contrast` | number OR string | No | At least one listed alternative must match. |
| `Filter.saturate` | number OR string | No | At least one listed alternative must match. |
| `Filter.hue-rotate` | string OR string | No | At least one listed alternative must match. |
| `Filter.blur` | number OR string OR string | No | At least one listed alternative must match. |
| `Filter.invert` | boolean OR number OR string | No | At least one listed alternative must match. |
| `Filter.colorTint` | string OR string | No | At least one listed alternative must match. |

**Nested field: `Filter.brightness`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `-100`. Maximum: `100`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Filter.contrast`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `-100`. Maximum: `100`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Filter.saturate`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `-100`. Maximum: `100`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Filter.hue-rotate`**

At least one listed alternative must match.

**anyOf alternative 1**

Degrees, preferably with a "deg" suffix (e.g. "90deg"). Legacy numeric strings such as "90" remain accepted.

Pattern: `^-?(?:\d+(?:\.\d+)?|\.\d+)(?:deg)?$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Filter.blur`**

At least one listed alternative must match.

**anyOf alternative 1**

Unitless 0..100 amount. Numeric strings remain accepted for backward compatibility; CSS lengths such as 4px are not supported.

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `100`.

Type: number.

**anyOf alternative 2**

Pattern: `^(?:0*(?:100(?:\.0+)?|\d{1,2}(?:\.\d+)?|\.\d+))$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Filter.invert`**

At least one listed alternative must match.

**anyOf alternative 1**

Boolean or partial inversion amount from 0 to 1.

At least one listed alternative must match.

**anyOf alternative 1**

Type: boolean.

**anyOf alternative 2**

Minimum: `0`. Maximum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Filter.colorTint`**

At least one listed alternative must match.

**anyOf alternative 1**

Tint color (#rgb or #rrggbb).

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema chroma key

**ChromaKey**


Green-screen keying.

Unknown properties are rejected. Required properties: `color`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ChromaKey.color` | string OR string | Yes | At least one listed alternative must match. |
| `ChromaKey.similarity` | number OR string | No | At least one listed alternative must match. |
| `ChromaKey.blend` | number OR string | No | At least one listed alternative must match. |

**Nested field: `ChromaKey.color`**

At least one listed alternative must match.

**anyOf alternative 1**

Key color to remove (required).

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ChromaKey.similarity`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `100`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `ChromaKey.blend`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `100`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema zoom

**Zoom**


Ken Burns zoom: true (default 1.2x depth) or &#123; "depth": 1..10 &#125;.

At least one listed alternative must match.

**anyOf alternative 1**

Type: boolean.

**anyOf alternative 2**

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Zoom.depth` | number OR string | No | At least one listed alternative must match. |

**Nested field: `Zoom.depth`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `1`. Maximum: `10`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema radius

**Radius**


Rounded-corner radii in px (tl/tr/bl/br).

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Radius.tl` | number OR string | No | At least one listed alternative must match. |
| `Radius.tr` | number OR string | No | At least one listed alternative must match. |
| `Radius.bl` | number OR string | No | At least one listed alternative must match. |
| `Radius.br` | number OR string | No | At least one listed alternative must match. |

**Nested field: `Radius.tl`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Radius.tr`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Radius.bl`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Radius.br`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema video visual

**VideoVisual**


A video clip placed on the canvas (video projects only).

Unknown properties are rejected. Required properties: `type`, `src`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `VideoVisual.type` | string | Yes | "VIDEO" (case-insensitive). Pattern: `^[Vv][Ii][Dd][Ee][Oo]$`. |
| `VideoVisual.x` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.y` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.width` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.height` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `VideoVisual.anchor` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `VideoVisual.resize` | `"contain"` / `"cover"` OR string | No | At least one listed alternative must match. |
| `VideoVisual.enterBegin` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.enterEnd` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.exitBegin` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.exitEnd` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.opacity` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.angle` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.flipV` | boolean OR string | No | At least one listed alternative must match. |
| `VideoVisual.flipH` | boolean OR string | No | At least one listed alternative must match. |
| `VideoVisual.track` | integer OR string | No | At least one listed alternative must match. |
| `VideoVisual.enterAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `VideoVisual.exitAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `VideoVisual.src` | string OR string | Yes | At least one listed alternative must match. |
| `VideoVisual.cropParams` | [CropParams](#schema-crop-params) OR string | No | At least one listed alternative must match. |
| `VideoVisual.filter` | [Filter](#schema-filter) OR string | No | At least one listed alternative must match. |
| `VideoVisual.chromaKey` | [ChromaKey](#schema-chroma-key) OR string | No | At least one listed alternative must match. |
| `VideoVisual.zoom` | [Zoom](#schema-zoom) OR string | No | At least one listed alternative must match. |
| `VideoVisual.radius` | [Radius](#schema-radius) OR string | No | At least one listed alternative must match. |
| `VideoVisual.videoBegin` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.videoEnd` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.videoDuration` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.volume` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.speed` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.transition` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `VideoVisual.transitionDuration` | number OR string | No | At least one listed alternative must match. |
| `VideoVisual.transitionId` | string OR string | No | At least one listed alternative must match. |
| `VideoVisual.frameRate` | integer OR string | No | At least one listed alternative must match. |
| `VideoVisual.id` | string OR string | No | At least one listed alternative must match. |
| `VideoVisual.hasAudio` | boolean OR string | No | At least one listed alternative must match. |
| `VideoVisual.condition` | any JSON value | No | A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported. |

**Nested field: `VideoVisual.x`**

At least one listed alternative must match.

**anyOf alternative 1**

Anchor-point X in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.y`**

At least one listed alternative must match.

**anyOf alternative 1**

Anchor-point Y in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.width`**

At least one listed alternative must match.

**anyOf alternative 1**

Element width in px (plan-limited).

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.height`**

At least one listed alternative must match.

**anyOf alternative 1**

Element height in px (plan-limited).

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.position`**

At least one listed alternative must match.

**anyOf alternative 1**

Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. "bottom-center" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless "custom". Two elements with the same preset render exactly stacked.

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.anchor`**

At least one listed alternative must match.

**anyOf alternative 1**

Which point of the ELEMENT sits at the position point. Defaults to the position preset itself ("custom" defaults to top-left).

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.resize`**

At least one listed alternative must match.

**anyOf alternative 1**

How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas.

Type: `"contain"` / `"cover"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.enterBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the element starts appearing (enter animation start).

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.enterEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the enter animation finishes. Must be >= enterBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.exitBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the exit animation starts.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.exitEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the element is fully gone. Must be >= exitBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.opacity`**

At least one listed alternative must match.

**anyOf alternative 1**

Element opacity, 0..1.

Minimum: `0`. Maximum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.angle`**

At least one listed alternative must match.

**anyOf alternative 1**

Rotation in degrees.

Minimum: `-360`. Maximum: `360`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.flipV`**

At least one listed alternative must match.

**anyOf alternative 1**

Flip vertically.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.flipH`**

At least one listed alternative must match.

**anyOf alternative 1**

Flip horizontally.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.track`**

At least one listed alternative must match.

**anyOf alternative 1**

Z-order track; higher tracks render on top.

Minimum: `0`. Maximum: `1000000`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.enterAnimation`**

At least one listed alternative must match.

**anyOf alternative 1**

Enter animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.exitAnimation`**

At least one listed alternative must match.

**anyOf alternative 1**

Exit animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.src`**

At least one listed alternative must match.

**anyOf alternative 1**

Source media URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs.

Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.cropParams`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [CropParams](#schema-crop-params).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.filter`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Filter](#schema-filter).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.chromaKey`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [ChromaKey](#schema-chroma-key).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.zoom`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Zoom](#schema-zoom).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.radius`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Radius](#schema-radius).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.videoBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Trim: source time (s) where playback starts.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.videoEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Trim: source time (s) where playback ends.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.videoDuration`**

At least one listed alternative must match.

**anyOf alternative 1**

Play only this many seconds of the source.

Minimum: `0.1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.volume`**

At least one listed alternative must match.

**anyOf alternative 1**

Clip audio volume, 0..2 (1 = original level).

Minimum: `0`. Maximum: `2`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.speed`**

At least one listed alternative must match.

**anyOf alternative 1**

Playback speed multiplier, 0.1..10.

Minimum: `0.1`. Maximum: `10`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.transition`**

At least one listed alternative must match.

**anyOf alternative 1**

Transition into the linked clip (see transitionId).

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.transitionDuration`**

At least one listed alternative must match.

**anyOf alternative 1**

Transition length in seconds.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.transitionId`**

At least one listed alternative must match.

**anyOf alternative 1**

id of the clip this transition links to.

Maximum length: `100`. Pattern: `^[a-zA-Z0-9_-]+$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.frameRate`**

At least one listed alternative must match.

**anyOf alternative 1**

Override source frame rate (1..60).

Minimum: `1`. Maximum: `60`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.id`**

At least one listed alternative must match.

**anyOf alternative 1**

Clip id (referenced by transitionId).

Pattern: `^[a-zA-Z0-9_-]+$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `VideoVisual.hasAudio`**

At least one listed alternative must match.

**anyOf alternative 1**

Hint that the source has an audio stream.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema gif visual

**GifVisual**


An animated GIF placed on the canvas (video projects only).

Unknown properties are rejected. Required properties: `type`, `src`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `GifVisual.type` | string | Yes | "GIF" (case-insensitive). Pattern: `^[Gg][Ii][Ff]$`. |
| `GifVisual.x` | number OR string | No | At least one listed alternative must match. |
| `GifVisual.y` | number OR string | No | At least one listed alternative must match. |
| `GifVisual.width` | number OR string | No | At least one listed alternative must match. |
| `GifVisual.height` | number OR string | No | At least one listed alternative must match. |
| `GifVisual.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `GifVisual.anchor` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `GifVisual.resize` | `"contain"` / `"cover"` OR string | No | At least one listed alternative must match. |
| `GifVisual.enterBegin` | number OR string | No | At least one listed alternative must match. |
| `GifVisual.enterEnd` | number OR string | No | At least one listed alternative must match. |
| `GifVisual.exitBegin` | number OR string | No | At least one listed alternative must match. |
| `GifVisual.exitEnd` | number OR string | No | At least one listed alternative must match. |
| `GifVisual.opacity` | number OR string | No | At least one listed alternative must match. |
| `GifVisual.angle` | number OR string | No | At least one listed alternative must match. |
| `GifVisual.flipV` | boolean OR string | No | At least one listed alternative must match. |
| `GifVisual.flipH` | boolean OR string | No | At least one listed alternative must match. |
| `GifVisual.track` | integer OR string | No | At least one listed alternative must match. |
| `GifVisual.enterAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `GifVisual.exitAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `GifVisual.src` | string OR string | Yes | At least one listed alternative must match. |
| `GifVisual.cropParams` | [CropParams](#schema-crop-params) OR string | No | At least one listed alternative must match. |
| `GifVisual.filter` | [Filter](#schema-filter) OR string | No | At least one listed alternative must match. |
| `GifVisual.chromaKey` | [ChromaKey](#schema-chroma-key) OR string | No | At least one listed alternative must match. |
| `GifVisual.zoom` | [Zoom](#schema-zoom) OR string | No | At least one listed alternative must match. |
| `GifVisual.radius` | [Radius](#schema-radius) OR string | No | At least one listed alternative must match. |
| `GifVisual.condition` | any JSON value | No | A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported. |

**Nested field: `GifVisual.x`**

At least one listed alternative must match.

**anyOf alternative 1**

Anchor-point X in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.y`**

At least one listed alternative must match.

**anyOf alternative 1**

Anchor-point Y in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.width`**

At least one listed alternative must match.

**anyOf alternative 1**

Element width in px (plan-limited).

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.height`**

At least one listed alternative must match.

**anyOf alternative 1**

Element height in px (plan-limited).

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.position`**

At least one listed alternative must match.

**anyOf alternative 1**

Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. "bottom-center" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless "custom". Two elements with the same preset render exactly stacked.

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.anchor`**

At least one listed alternative must match.

**anyOf alternative 1**

Which point of the ELEMENT sits at the position point. Defaults to the position preset itself ("custom" defaults to top-left).

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.resize`**

At least one listed alternative must match.

**anyOf alternative 1**

How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas.

Type: `"contain"` / `"cover"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.enterBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the element starts appearing (enter animation start).

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.enterEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the enter animation finishes. Must be >= enterBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.exitBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the exit animation starts.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.exitEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the element is fully gone. Must be >= exitBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.opacity`**

At least one listed alternative must match.

**anyOf alternative 1**

Element opacity, 0..1.

Minimum: `0`. Maximum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.angle`**

At least one listed alternative must match.

**anyOf alternative 1**

Rotation in degrees.

Minimum: `-360`. Maximum: `360`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.flipV`**

At least one listed alternative must match.

**anyOf alternative 1**

Flip vertically.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.flipH`**

At least one listed alternative must match.

**anyOf alternative 1**

Flip horizontally.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.track`**

At least one listed alternative must match.

**anyOf alternative 1**

Z-order track; higher tracks render on top.

Minimum: `0`. Maximum: `1000000`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.enterAnimation`**

At least one listed alternative must match.

**anyOf alternative 1**

Enter animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.exitAnimation`**

At least one listed alternative must match.

**anyOf alternative 1**

Exit animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.src`**

At least one listed alternative must match.

**anyOf alternative 1**

Source media URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs.

Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.cropParams`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [CropParams](#schema-crop-params).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.filter`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Filter](#schema-filter).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.chromaKey`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [ChromaKey](#schema-chroma-key).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.zoom`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Zoom](#schema-zoom).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `GifVisual.radius`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Radius](#schema-radius).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema svg visual

**SvgVisual**


Inline SVG markup drawn on the canvas.

Unknown properties are rejected. Required properties: `type`, `svg`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `SvgVisual.type` | string | Yes | "SVG" (case-insensitive). Pattern: `^[Ss][Vv][Gg]$`. |
| `SvgVisual.x` | number OR string | No | At least one listed alternative must match. |
| `SvgVisual.y` | number OR string | No | At least one listed alternative must match. |
| `SvgVisual.width` | number OR string | No | At least one listed alternative must match. |
| `SvgVisual.height` | number OR string | No | At least one listed alternative must match. |
| `SvgVisual.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `SvgVisual.anchor` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `SvgVisual.resize` | `"contain"` / `"cover"` OR string | No | At least one listed alternative must match. |
| `SvgVisual.enterBegin` | number OR string | No | At least one listed alternative must match. |
| `SvgVisual.enterEnd` | number OR string | No | At least one listed alternative must match. |
| `SvgVisual.exitBegin` | number OR string | No | At least one listed alternative must match. |
| `SvgVisual.exitEnd` | number OR string | No | At least one listed alternative must match. |
| `SvgVisual.opacity` | number OR string | No | At least one listed alternative must match. |
| `SvgVisual.angle` | number OR string | No | At least one listed alternative must match. |
| `SvgVisual.flipV` | boolean OR string | No | At least one listed alternative must match. |
| `SvgVisual.flipH` | boolean OR string | No | At least one listed alternative must match. |
| `SvgVisual.track` | integer OR string | No | At least one listed alternative must match. |
| `SvgVisual.enterAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `SvgVisual.exitAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `SvgVisual.svg` | string OR string | Yes | At least one listed alternative must match. |
| `SvgVisual.filter` | [Filter](#schema-filter) OR string | No | At least one listed alternative must match. |
| `SvgVisual.chromaKey` | [ChromaKey](#schema-chroma-key) OR string | No | At least one listed alternative must match. |
| `SvgVisual.customCode` | [CustomCode](#schema-custom-code) OR string | No | At least one listed alternative must match. |
| `SvgVisual.designer` | [Designer](#schema-designer) OR string | No | At least one listed alternative must match. |
| `SvgVisual.condition` | any JSON value | No | A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported. |

**Nested field: `SvgVisual.x`**

At least one listed alternative must match.

**anyOf alternative 1**

Anchor-point X in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.y`**

At least one listed alternative must match.

**anyOf alternative 1**

Anchor-point Y in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.width`**

At least one listed alternative must match.

**anyOf alternative 1**

Element width in px (plan-limited).

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.height`**

At least one listed alternative must match.

**anyOf alternative 1**

Element height in px (plan-limited).

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.position`**

At least one listed alternative must match.

**anyOf alternative 1**

Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. "bottom-center" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless "custom". Two elements with the same preset render exactly stacked.

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.anchor`**

At least one listed alternative must match.

**anyOf alternative 1**

Which point of the ELEMENT sits at the position point. Defaults to the position preset itself ("custom" defaults to top-left).

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.resize`**

At least one listed alternative must match.

**anyOf alternative 1**

How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas.

Type: `"contain"` / `"cover"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.enterBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the element starts appearing (enter animation start).

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.enterEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the enter animation finishes. Must be >= enterBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.exitBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the exit animation starts.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.exitEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the element is fully gone. Must be >= exitBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.opacity`**

At least one listed alternative must match.

**anyOf alternative 1**

Element opacity, 0..1.

Minimum: `0`. Maximum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.angle`**

At least one listed alternative must match.

**anyOf alternative 1**

Rotation in degrees.

Minimum: `-360`. Maximum: `360`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.flipV`**

At least one listed alternative must match.

**anyOf alternative 1**

Flip vertically.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.flipH`**

At least one listed alternative must match.

**anyOf alternative 1**

Flip horizontally.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.track`**

At least one listed alternative must match.

**anyOf alternative 1**

Z-order track; higher tracks render on top.

Minimum: `0`. Maximum: `1000000`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.enterAnimation`**

At least one listed alternative must match.

**anyOf alternative 1**

Enter animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.exitAnimation`**

At least one listed alternative must match.

**anyOf alternative 1**

Exit animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.svg`**

At least one listed alternative must match.

**anyOf alternative 1**

Inline SVG markup starting with "&lt;svg". Sanitized: no &lt;script>/&lt;foreignObject>/&lt;iframe>/&lt;object>/&lt;embed>/&lt;audio>/&lt;video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions &lt;= 4096px, no integers with 8+ digits.

Minimum length: `1`. Maximum length: `200000`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.filter`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Filter](#schema-filter).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.chromaKey`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [ChromaKey](#schema-chroma-key).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.customCode`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [CustomCode](#schema-custom-code).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `SvgVisual.designer`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Designer](#schema-designer).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema custom code

**CustomCode**


Sandboxed styling/animation code run inside the render browser (Design Studio). No network/filesystem/storage/navigation APIs — see validation notes.

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `CustomCode.css` | string OR string | No | At least one listed alternative must match. |
| `CustomCode.js` | string OR string | No | At least one listed alternative must match. |
| `CustomCode.animationDuration` | number OR string | No | At least one listed alternative must match. |

**Nested field: `CustomCode.css`**

At least one listed alternative must match.

**anyOf alternative 1**

Maximum length: `200000`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `CustomCode.js`**

At least one listed alternative must match.

**anyOf alternative 1**

Maximum length: `200000`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `CustomCode.animationDuration`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds the customCode animation runs (max 15).

Maximum: `15`. Greater than: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema designer

**Designer**


Opaque Design Studio round-trip metadata (max 200k chars serialized). Never rendered; keeps 'edit design' working.

Type: object.


### schema text visual

**TextVisual**


Styled text. Provide plain `text` (no &lt; or > characters) and/or limited `html`. At least one of text/html must be non-empty.

Unknown properties are rejected. Required properties: `type`. At least one listed alternative must match.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `TextVisual.type` | string | Yes | "TEXT" (case-insensitive). Pattern: `^[Tt][Ee][Xx][Tt]$`. |
| `TextVisual.x` | number OR string | No | At least one listed alternative must match. |
| `TextVisual.y` | number OR string | No | At least one listed alternative must match. |
| `TextVisual.width` | number OR string | No | At least one listed alternative must match. |
| `TextVisual.height` | number OR string | No | At least one listed alternative must match. |
| `TextVisual.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `TextVisual.anchor` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` OR string | No | At least one listed alternative must match. |
| `TextVisual.resize` | `"contain"` / `"cover"` OR string | No | At least one listed alternative must match. |
| `TextVisual.enterBegin` | number OR string | No | At least one listed alternative must match. |
| `TextVisual.enterEnd` | number OR string | No | At least one listed alternative must match. |
| `TextVisual.exitBegin` | number OR string | No | At least one listed alternative must match. |
| `TextVisual.exitEnd` | number OR string | No | At least one listed alternative must match. |
| `TextVisual.opacity` | number OR string | No | At least one listed alternative must match. |
| `TextVisual.angle` | number OR string | No | At least one listed alternative must match. |
| `TextVisual.flipV` | boolean OR string | No | At least one listed alternative must match. |
| `TextVisual.flipH` | boolean OR string | No | At least one listed alternative must match. |
| `TextVisual.track` | integer OR string | No | At least one listed alternative must match. |
| `TextVisual.enterAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `TextVisual.exitAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `TextVisual.text` | string OR string | No | At least one listed alternative must match. |
| `TextVisual.html` | string OR string | No | At least one listed alternative must match. |
| `TextVisual.style` | object OR string | No | At least one listed alternative must match. |
| `TextVisual.fitToBox` | boolean OR string | No | At least one listed alternative must match. |
| `TextVisual.customCode` | [CustomCode](#schema-custom-code) OR string | No | At least one listed alternative must match. |
| `TextVisual.designer` | [Designer](#schema-designer) OR string | No | At least one listed alternative must match. |
| `TextVisual.condition` | any JSON value | No | A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported. |

**Nested field: `TextVisual.x`**

At least one listed alternative must match.

**anyOf alternative 1**

Anchor-point X in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.y`**

At least one listed alternative must match.

**anyOf alternative 1**

Anchor-point Y in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.width`**

At least one listed alternative must match.

**anyOf alternative 1**

Element width in px (plan-limited).

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.height`**

At least one listed alternative must match.

**anyOf alternative 1**

Element height in px (plan-limited).

Minimum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.position`**

At least one listed alternative must match.

**anyOf alternative 1**

Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. "bottom-center" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless "custom". Two elements with the same preset render exactly stacked.

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.anchor`**

At least one listed alternative must match.

**anyOf alternative 1**

Which point of the ELEMENT sits at the position point. Defaults to the position preset itself ("custom" defaults to top-left).

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.resize`**

At least one listed alternative must match.

**anyOf alternative 1**

How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas.

Type: `"contain"` / `"cover"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.enterBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the element starts appearing (enter animation start).

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.enterEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the enter animation finishes. Must be >= enterBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.exitBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the exit animation starts.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.exitEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds when the element is fully gone. Must be >= exitBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.opacity`**

At least one listed alternative must match.

**anyOf alternative 1**

Element opacity, 0..1.

Minimum: `0`. Maximum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.angle`**

At least one listed alternative must match.

**anyOf alternative 1**

Rotation in degrees.

Minimum: `-360`. Maximum: `360`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.flipV`**

At least one listed alternative must match.

**anyOf alternative 1**

Flip vertically.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.flipH`**

At least one listed alternative must match.

**anyOf alternative 1**

Flip horizontally.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.track`**

At least one listed alternative must match.

**anyOf alternative 1**

Z-order track; higher tracks render on top.

Minimum: `0`. Maximum: `1000000`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.enterAnimation`**

At least one listed alternative must match.

**anyOf alternative 1**

Enter animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.exitAnimation`**

At least one listed alternative must match.

**anyOf alternative 1**

Exit animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.text`**

At least one listed alternative must match.

**anyOf alternative 1**

Plain text content. HTML markup (&lt; or >) is rejected — use `html` for markup.

Maximum length: `20000`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.html`**

At least one listed alternative must match.

**anyOf alternative 1**

Limited HTML. Allowed tags: b, strong, i, em, u, s, br, span, div, p, ul, ol, li, img, canvas, svg (+ svg geometry). Allowed attributes: style, class, src, alt, width, height. No &lt;script>/&lt;style>, no event handlers, no external CSS url().

Maximum length: `200000`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.style`**

At least one listed alternative must match.

**anyOf alternative 1**

CSS style object applied to the text container (camelCase or kebab-case keys, and --custom-properties). LAYOUT MODEL: the container uses box-sizing: content-box, so padding/border render OUTSIDE the declared width/height and overflow gets cut — for cards/pills set the full size via width/height and center content with display:flex + alignItems:center + justifyContent:center instead of padding. Text sits at the TOP of the box unless flex-centered. Values must not contain url(), @import, expression(), comments or other dangerous tokens. Max value length 4000 chars.

Maximum properties: `120`.

Type: object.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.fitToBox`**

At least one listed alternative must match.

**anyOf alternative 1**

Scale the typography down just enough that the painted text stays inside the declared width/height instead of being clipped at the element's edge. No effect when the content already fits, so an unchanged template renders identically.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.customCode`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [CustomCode](#schema-custom-code).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `TextVisual.designer`**

At least one listed alternative must match.

**anyOf alternative 1**

Schema: [Designer](#schema-designer).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 1**

Required properties: `text`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `TextVisual.text` | string OR string | Yes | At least one listed alternative must match. |

**Nested field: `TextVisual.text`**

At least one listed alternative must match.

**anyOf alternative 1**

Pattern: `\S`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 2**

Required properties: `html`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `TextVisual.html` | string OR string | Yes | At least one listed alternative must match. |

**Nested field: `TextVisual.html`**

At least one listed alternative must match.

**anyOf alternative 1**

Pattern: `\S`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 3**

Required properties: `condition`.

Type: any JSON value.


### schema audio item

**AudioItem**


A soundtrack / voice-over entry for `audios` (video projects only).

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `AudioItem.src` | string OR string | No | At least one listed alternative must match. |
| `AudioItem.matchDuration` | boolean OR string | No | At least one listed alternative must match. |
| `AudioItem.enter` | number OR string | No | At least one listed alternative must match. |
| `AudioItem.exit` | number OR string | No | At least one listed alternative must match. |
| `AudioItem.volume` | number OR string | No | At least one listed alternative must match. |
| `AudioItem.speed` | number OR string | No | At least one listed alternative must match. |
| `AudioItem.audioBegin` | number OR string | No | At least one listed alternative must match. |
| `AudioItem.audioEnd` | number OR string | No | At least one listed alternative must match. |
| `AudioItem.audioDuration` | number OR string | No | At least one listed alternative must match. |
| `AudioItem.track` | integer OR string | No | At least one listed alternative must match. |
| `AudioItem.condition` | any JSON value | No | A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported. |

**Nested field: `AudioItem.src`**

At least one listed alternative must match.

**anyOf alternative 1**

Audio file URL (mp3/wav/...). Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs.

Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `AudioItem.matchDuration`**

At least one listed alternative must match.

**anyOf alternative 1**

Follow the containing scene/project length without extending automatic duration. Overrides exit.

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `AudioItem.enter`**

At least one listed alternative must match.

**anyOf alternative 1**

Timeline second the audio starts playing.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `AudioItem.exit`**

At least one listed alternative must match.

**anyOf alternative 1**

Timeline second the audio stops. Must be >= enter.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `AudioItem.volume`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `2`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `AudioItem.speed`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0.1`. Maximum: `10`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `AudioItem.audioBegin`**

At least one listed alternative must match.

**anyOf alternative 1**

Trim: source second where playback starts.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `AudioItem.audioEnd`**

At least one listed alternative must match.

**anyOf alternative 1**

Trim: source second where playback ends. Must be >= audioBegin.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `AudioItem.audioDuration`**

At least one listed alternative must match.

**anyOf alternative 1**

Play only this many seconds of the source.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `AudioItem.track`**

At least one listed alternative must match.

**anyOf alternative 1**

Mix track index; audio tracks mix together.

Minimum: `0`. Maximum: `1000000`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema scene

**Scene**


A timeline segment. Scenes play sequentially; a scene's `transition` blends into the NEXT scene (xfade overlap is subtracted from the total duration).

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Scene.id` | string OR string | No | At least one listed alternative must match. |
| `Scene.duration` | `-1` OR number OR string | No | At least one listed alternative must match. |
| `Scene.transition` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null OR string | No | At least one listed alternative must match. |
| `Scene.transitionId` | string OR null OR string | No | At least one listed alternative must match. |
| `Scene.transitionDuration` | number OR string | No | At least one listed alternative must match. |
| `Scene.backgroundColor` | string OR string | No | At least one listed alternative must match. |
| `Scene.visuals` | array of [Visual](#schema-visual) OR string | No | At least one listed alternative must match. |
| `Scene.audios` | array of [AudioItem](#schema-audio-item) OR string | No | At least one listed alternative must match. |
| `Scene.variables` | object | No | Default variable values; request variables override these. Scene defaults shadow project values inside that scene. |
| `Scene.iterate` | string | No | Name or dotted path of an array variable; one scene is generated per item. |
| `Scene.iterateAs` | string | No | Default: `"item"`. Pattern: `^[A-Za-z][A-Za-z0-9_]*$`. |
| `Scene.condition` | any JSON value | No | A literal flag or placeholder. false, 0, an empty string, null or an absent resolved value prune this item. Expressions are not supported. |

**Nested field: `Scene.id`**

At least one listed alternative must match.

**anyOf alternative 1**

Optional scene ID. Explicit IDs must be unique within the project.

Maximum length: `100`. Pattern: `^[a-zA-Z0-9_-]+$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Scene.duration`**

At least one listed alternative must match.

**anyOf alternative 1**

Seconds; -1 (or omitted) auto-computes from the scene's content.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `-1`.

**anyOf alternative 2**

Minimum: `0.1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Scene.transition`**

At least one listed alternative must match.

**anyOf alternative 1**

Transition into the next scene.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Scene.transitionId`**

At least one listed alternative must match.

**anyOf alternative 1**

At least one listed alternative must match.

**anyOf alternative 1**

Maximum length: `100`. Pattern: `^[a-zA-Z0-9_-]+$`.

Type: string.

**anyOf alternative 2**

Type: null.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Scene.transitionDuration`**

At least one listed alternative must match.

**anyOf alternative 1**

Transition overlap in seconds (default 0.5).

Minimum: `0`. Maximum: `60`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Scene.backgroundColor`**

At least one listed alternative must match.

**anyOf alternative 1**

Scene background (#rgb or #rrggbb).

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Scene.visuals`**

At least one listed alternative must match.

**anyOf alternative 1**

**Array item: `Scene.visuals[]`**

Schema: [Visual](#schema-visual).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Scene.audios`**

At least one listed alternative must match.

**anyOf alternative 1**

**Array item: `Scene.audios[]`**

Schema: [AudioItem](#schema-audio-item).

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.


### schema subtitle

**Subtitle**


Burned-in subtitles. Content: EXACTLY ONE of `src` (SRT/VTT URL) or `captions`. Style: v2 flat keys (animation/font/stroke/background/...) OR the legacy `styles` block — not both.

Unknown properties are rejected. Exactly one of the listed alternatives must match.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.src` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.captions` | array of any JSON value OR any JSON value OR string | No | At least one listed alternative must match. |
| `Subtitle.maxWordsPerLine` | integer OR string | No | At least one listed alternative must match. |
| `Subtitle.animation` | `"normal"` / `"none"` / `"one-word"` / `"karaoke"` / `"progressive"` / `"highlight"` / `"fill"` / `"pop"` / `"bounce"` / `"fade"` / `"slide"` / `"typewriter"` OR string | No | At least one listed alternative must match. |
| `Subtitle.direction` | `"up"` / `"down"` / `"left"` / `"right"` OR string | No | At least one listed alternative must match. |
| `Subtitle.font` | object OR string | No | At least one listed alternative must match. |
| `Subtitle.stroke` | object OR string | No | At least one listed alternative must match. |
| `Subtitle.background` | object OR string | No | At least one listed alternative must match. |
| `Subtitle.activeWord` | object OR string | No | At least one listed alternative must match. |
| `Subtitle.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"bottom-left"` / `"bottom-center"` / `"bottom-right"` / `"center-center"` / `"center-left"` / `"center-right"` / `"top"` / `"center"` / `"bottom"` OR string | No | At least one listed alternative must match. |
| `Subtitle.margin` | object OR string | No | At least one listed alternative must match. |
| `Subtitle.styles` | object OR string | No | At least one listed alternative must match. |

**Nested field: `Subtitle.src`**

At least one listed alternative must match.

**anyOf alternative 1**

SRT or VTT file URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs.

Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.captions`**

At least one listed alternative must match.

**anyOf alternative 1**

Inline captions (plan-limited count).

Minimum items: `1`.

**Array item: `Subtitle.captions[]`**

Unknown properties are rejected. Required properties: `start`, `end`. At least one listed alternative must match.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.captions[].start` | number OR string | Yes | At least one listed alternative must match. |
| `Subtitle.captions[].end` | number OR string | Yes | At least one listed alternative must match. |
| `Subtitle.captions[].text` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.captions[].words` | array of object OR string | No | At least one listed alternative must match. |

**Nested field: `Subtitle.captions[].start`**

At least one listed alternative must match.

**anyOf alternative 1**

Caption start time in seconds.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.captions[].end`**

At least one listed alternative must match.

**anyOf alternative 1**

Caption end time in seconds.

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.captions[].text`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum length: `1`. Maximum length: `1000`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.captions[].words`**

At least one listed alternative must match.

**anyOf alternative 1**

Per-word timings; auto-distributed from `text` when omitted.

Minimum items: `1`.

**Array item: `Subtitle.captions[].words[]`**

Unknown properties are rejected. Required properties: `start`, `end`, `text`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.captions[].words[].start` | number OR string | Yes | At least one listed alternative must match. |
| `Subtitle.captions[].words[].end` | number OR string | Yes | At least one listed alternative must match. |
| `Subtitle.captions[].words[].text` | string OR string | Yes | At least one listed alternative must match. |

**Nested field: `Subtitle.captions[].words[].start`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.captions[].words[].end`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.captions[].words[].text`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum length: `1`. Maximum length: `100`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 1**

Required properties: `text`.

Type: any JSON value.

**anyOf alternative 2**

Required properties: `words`.

Type: any JSON value.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.maxWordsPerLine`**

At least one listed alternative must match.

**anyOf alternative 1**

Re-chunk captions to at most N words per line.

Minimum: `1`. Maximum: `20`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.animation`**

At least one listed alternative must match.

**anyOf alternative 1**

Caption animation mode.

Type: `"normal"` / `"none"` / `"one-word"` / `"karaoke"` / `"progressive"` / `"highlight"` / `"fill"` / `"pop"` / `"bounce"` / `"fade"` / `"slide"` / `"typewriter"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.direction`**

At least one listed alternative must match.

**anyOf alternative 1**

Slide direction when animation is "slide".

Type: `"up"` / `"down"` / `"left"` / `"right"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.font`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum properties: `1`. Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.font.family` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.font.size` | number OR string | No | At least one listed alternative must match. |
| `Subtitle.font.color` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.font.bold` | boolean OR string | No | At least one listed alternative must match. |
| `Subtitle.font.italic` | boolean OR string | No | At least one listed alternative must match. |
| `Subtitle.font.transform` | `"uppercase"` / `"lowercase"` / `"capitalize"` OR string | No | At least one listed alternative must match. |

**Nested field: `Subtitle.font.family`**

At least one listed alternative must match.

**anyOf alternative 1**

Maximum length: `1000`. Pattern: `^[a-zA-Z0-9_\- ]+$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.font.size`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `1`. Maximum: `1000`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.font.color`**

At least one listed alternative must match.

**anyOf alternative 1**

#rrggbb or #rrggbbaa

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.font.bold`**

At least one listed alternative must match.

**anyOf alternative 1**

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.font.italic`**

At least one listed alternative must match.

**anyOf alternative 1**

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.font.transform`**

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"uppercase"` / `"lowercase"` / `"capitalize"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.stroke`**

At least one listed alternative must match.

**anyOf alternative 1**

Unknown properties are rejected. Required properties: `color`, `width`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.stroke.color` | string OR string | Yes | At least one listed alternative must match. |
| `Subtitle.stroke.width` | number OR string | Yes | At least one listed alternative must match. |

**Nested field: `Subtitle.stroke.color`**

At least one listed alternative must match.

**anyOf alternative 1**

#rrggbb or #rrggbbaa

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.stroke.width`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `100`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.background`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum properties: `1`. Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.background.color` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.background.opacity` | number OR string | No | At least one listed alternative must match. |
| `Subtitle.background.padding` | number OR string | No | At least one listed alternative must match. |
| `Subtitle.background.radius` | number OR string | No | At least one listed alternative must match. |

**Nested field: `Subtitle.background.color`**

At least one listed alternative must match.

**anyOf alternative 1**

#rrggbb or #rrggbbaa

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.background.opacity`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `1`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.background.padding`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `200`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.background.radius`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `200`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.activeWord`**

At least one listed alternative must match.

**anyOf alternative 1**

Highlight styling for the currently spoken word.

Minimum properties: `1`. Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.activeWord.color` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.activeWord.background` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.activeWord.radius` | number OR string | No | At least one listed alternative must match. |

**Nested field: `Subtitle.activeWord.color`**

At least one listed alternative must match.

**anyOf alternative 1**

#rrggbb or #rrggbbaa

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.activeWord.background`**

At least one listed alternative must match.

**anyOf alternative 1**

#rrggbb or #rrggbbaa

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.activeWord.radius`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `200`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.position`**

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"bottom-left"` / `"bottom-center"` / `"bottom-right"` / `"center-center"` / `"center-left"` / `"center-right"` / `"top"` / `"center"` / `"bottom"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.margin`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum properties: `1`. Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.margin.x` | integer OR string | No | At least one listed alternative must match. |
| `Subtitle.margin.y` | integer OR string | No | At least one listed alternative must match. |

**Nested field: `Subtitle.margin.x`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.margin.y`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles`**

At least one listed alternative must match.

**anyOf alternative 1**

LEGACY style block — prefer the flat v2 keys. Cannot be combined with them.

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.styles.color` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.background` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.backgroundPadding` | number OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.backgroundRadius` | number OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.isBold` | boolean OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.isItalic` | boolean OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.fontSize` | number OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.fontFamily` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.textTransform` | `"uppercase"` / `"lowercase"` / `"capitalize"` OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.outline` | object OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"bottom-left"` / `"bottom-center"` / `"bottom-right"` / `"center-center"` / `"center-left"` / `"center-right"` OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.marginV` | integer OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.marginH` | integer OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.mode` | `"normal"` / `"none"` / `"one-word"` / `"karaoke"` / `"progressive"` / `"highlight"` / `"fill"` / `"pop"` / `"bounce"` / `"fade"` / `"slide"` / `"typewriter"` OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.slideDirection` | `"up"` / `"down"` / `"left"` / `"right"` OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.activeWord` | object OR string | No | At least one listed alternative must match. |

**Nested field: `Subtitle.styles.color`**

At least one listed alternative must match.

**anyOf alternative 1**

#rrggbb or #rrggbbaa

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.background`**

At least one listed alternative must match.

**anyOf alternative 1**

#rrggbb or #rrggbbaa

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.backgroundPadding`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `200`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.backgroundRadius`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `200`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.isBold`**

At least one listed alternative must match.

**anyOf alternative 1**

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.isItalic`**

At least one listed alternative must match.

**anyOf alternative 1**

Type: boolean.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.fontSize`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `1`. Maximum: `1000`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.fontFamily`**

At least one listed alternative must match.

**anyOf alternative 1**

Maximum length: `1000`. Pattern: `^[a-zA-Z0-9_\- ]+$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.textTransform`**

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"uppercase"` / `"lowercase"` / `"capitalize"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.outline`**

At least one listed alternative must match.

**anyOf alternative 1**

Unknown properties are rejected. Required properties: `width`, `color`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.styles.outline.width` | number OR string | Yes | At least one listed alternative must match. |
| `Subtitle.styles.outline.color` | string OR string | Yes | At least one listed alternative must match. |

**Nested field: `Subtitle.styles.outline.width`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `100`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.outline.color`**

At least one listed alternative must match.

**anyOf alternative 1**

#rrggbb or #rrggbbaa

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.position`**

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"top-left"` / `"top-center"` / `"top-right"` / `"bottom-left"` / `"bottom-center"` / `"bottom-right"` / `"center-center"` / `"center-left"` / `"center-right"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.marginV`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.marginH`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`.

Type: integer.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.mode`**

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"normal"` / `"none"` / `"one-word"` / `"karaoke"` / `"progressive"` / `"highlight"` / `"fill"` / `"pop"` / `"bounce"` / `"fade"` / `"slide"` / `"typewriter"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.slideDirection`**

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"up"` / `"down"` / `"left"` / `"right"`.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.activeWord`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum properties: `1`. Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Subtitle.styles.activeWord.color` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.activeWord.background` | string OR string | No | At least one listed alternative must match. |
| `Subtitle.styles.activeWord.radius` | number OR string | No | At least one listed alternative must match. |

**Nested field: `Subtitle.styles.activeWord.color`**

At least one listed alternative must match.

**anyOf alternative 1**

#rrggbb or #rrggbbaa

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.activeWord.background`**

At least one listed alternative must match.

**anyOf alternative 1**

#rrggbb or #rrggbbaa

Pattern: `^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**Nested field: `Subtitle.styles.activeWord.radius`**

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `200`.

Type: number.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**anyOf alternative 2**

Pattern: `\{\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.(?:[A-Za-z][A-Za-z0-9_]*|[0-9]+))*\s*\}\}`.

Type: string.

**oneOf alternative 1**

Required properties: `src`.

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "not": {
    "required": [
      "captions"
    ]
  }
}
```
**oneOf alternative 2**

Required properties: `captions`.

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "not": {
    "required": [
      "src"
    ]
  }
}
```
Additional JSON Schema constraints:

```json
{
  "dependentSchemas": {
    "styles": {
      "properties": {
        "animation": false,
        "direction": false,
        "font": false,
        "stroke": false,
        "background": false,
        "activeWord": false,
        "position": false,
        "margin": false
      }
    }
  }
}
```


### schema render overrides

**RenderOverrides**


Output knobs applied after template/variable resolution.

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `RenderOverrides.name` | string | No | Maximum length: `1000`. |
| `RenderOverrides.width` | integer | No | Minimum: `1`. |
| `RenderOverrides.height` | integer | No | Minimum: `1`. |
| `RenderOverrides.outputFormat` | string | No | mp4/mov/avi/webm for video; png/jpg/jpeg/webp for image renders. |
| `RenderOverrides.frameRate` | integer | No | Minimum: `1`. Maximum: `60`. |
| `RenderOverrides.backgroundColor` | string | No |  |
| `RenderOverrides.snapshotTime` | number | No | Image renders only. Minimum: `0`. |
| `RenderOverrides.quality` | integer | No | Image renders only (jpg/webp). Minimum: `1`. Maximum: `100`. |
| `RenderOverrides.transparent` | boolean | No | Image renders only (png/webp). |
| `RenderOverrides.resolution` | string | No | Resolution preset. Explicit width/height overrides force custom unless resolution is also provided. |


### schema validation result

**ValidationResult**


Required properties: `valid`, `creditsRequired`, `payload`, `warnings`, `schemaVersion`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ValidationResult.valid` | `true` | Yes |  |
| `ValidationResult.message` | string | No |  |
| `ValidationResult.creditsRequired` | integer | Yes |  |
| `ValidationResult.payload` | [ResolvedRenderPayload](#schema-resolved-render-payload) | Yes |  |
| `ValidationResult.warnings` | array of object | Yes |  |
| `ValidationResult.schemaVersion` | string | Yes |  |

**Nested field: `ValidationResult.warnings`**

**Array item: `ValidationResult.warnings[]`**

Type: object.


### schema resolved render payload

**ResolvedRenderPayload**


A project after variable substitution, iteration and conditions. Account-specific ceilings and semantic rules are checked by POST /api/render/validate/api-key. This portable schema does not imply entitlement to any resolution, duration or element count.

Unknown properties are rejected. All listed constraints apply.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedRenderPayload.type` | `"video"` / `"image"` | No | Render type. "image" produces a still (png/jpg/webp) and forbids all time-domain fields. Default: `"video"`. |
| `ResolvedRenderPayload.name` | string | No | Output name. Letters, digits, space, _ and - ONLY (no punctuation). Default: `"unnamed"`. Maximum length: `1000`. Pattern: `^[a-zA-Z0-9_\- ]+$`. |
| `ResolvedRenderPayload.resolution` | `"sd"` / `"hd"` / `"full-hd"` / `"squared"` / `"youtube-short"` / `"youtube-video"` / `"tiktok"` / `"instagram-reel"` / `"instagram-post"` / `"instagram-story"` / `"instagram-feed"` / `"twitter-landscape"` / `"twitter-portrait"` / `"twitter-square"` / `"facebook-video"` / `"facebook-story"` / `"facebook-post"` / `"snapchat"` / `"custom"` | No | Canvas preset. Any value except "custom" OVERRIDES width/height. |
| `ResolvedRenderPayload.width` | integer | No | Canvas width in px (plan-limited). Default: `1280`. Minimum: `1`. |
| `ResolvedRenderPayload.height` | integer | No | Canvas height in px (plan-limited). Default: `720`. Minimum: `1`. |
| `ResolvedRenderPayload.duration` | number | No | Video length in seconds (plan-limited). With durationMode auto this is an optional minimum. Scene projects always include the full scene sequence. Default: `10`. Minimum: `0.1`. |
| `ResolvedRenderPayload.durationMode` | `"auto"` / `"fixed"` | No | Auto derives length from scenes, explicit element ends and captions. Supply videoEnd/exitEnd and audioEnd/exit for API submission. Omit to preserve legacy timing. |
| `ResolvedRenderPayload.frameRate` | integer | No | Frames per second. Default: `30`. Minimum: `1`. Maximum: `60`. |
| `ResolvedRenderPayload.outputFormat` | `"mp4"` / `"mov"` / `"avi"` / `"webm"` / `"png"` / `"jpg"` / `"jpeg"` / `"webp"` | No | Videos: mp4 (default), mov, avi, webm. Images (type: "image"): png (default), jpg, jpeg, webp. |
| `ResolvedRenderPayload.backgroundColor` | string | No | Canvas background color. Default: `"#ffffff"`. Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{3})$`. |
| `ResolvedRenderPayload.snapshotTime` | number | No | Image renders only: second of the (virtual) timeline to snapshot. Minimum: `0`. Maximum: `3600`. |
| `ResolvedRenderPayload.quality` | integer | No | Image renders only: jpg/webp quality (NOT valid for png). Minimum: `1`. Maximum: `100`. |
| `ResolvedRenderPayload.transparent` | boolean | No | Image renders only: transparent background (png/webp only, not jpg). |
| `ResolvedRenderPayload.visuals` | array of [ResolvedVisual](#schema-resolved-visual) | No | Elements shown for the whole project (outside scenes). |
| `ResolvedRenderPayload.audios` | array of [ResolvedAudioItem](#schema-resolved-audio-item) | No | Project-level audio tracks (video only, plan-limited count). |
| `ResolvedRenderPayload.scenes` | array of [ResolvedScene](#schema-resolved-scene) | No | Sequential timeline segments (video only, plan-limited count). |
| `ResolvedRenderPayload.thumbnail` | string | No | Custom thumbnail image URL (video only). Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs. Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`. |
| `ResolvedRenderPayload.subtitle` | [ResolvedSubtitle](#schema-resolved-subtitle) | No |  |

**Nested field: `ResolvedRenderPayload.visuals`**

Elements shown for the whole project (outside scenes).

**Array item: `ResolvedRenderPayload.visuals[]`**

Schema: [ResolvedVisual](#schema-resolved-visual).

**Nested field: `ResolvedRenderPayload.audios`**

Project-level audio tracks (video only, plan-limited count).

**Array item: `ResolvedRenderPayload.audios[]`**

Schema: [ResolvedAudioItem](#schema-resolved-audio-item).

**Nested field: `ResolvedRenderPayload.scenes`**

Sequential timeline segments (video only, plan-limited count).

**Array item: `ResolvedRenderPayload.scenes[]`**

Schema: [ResolvedScene](#schema-resolved-scene).

**allOf alternative 1**

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "if": {
    "properties": {
      "type": {
        "const": "image"
      }
    },
    "required": [
      "type"
    ]
  },
  "then": {
    "properties": {
      "duration": false,
      "durationMode": false,
      "frameRate": false,
      "audios": false,
      "scenes": false,
      "thumbnail": false,
      "subtitle": false,
      "outputFormat": {
        "enum": [
          "png",
          "jpg",
          "jpeg",
          "webp"
        ],
        "default": "png"
      },
      "visuals": {
        "type": "array",
        "items": {
          "oneOf": [
            {
              "$ref": "#/components/schemas/ResolvedImageVisual"
            },
            {
              "$ref": "#/components/schemas/ResolvedTextVisual"
            },
            {
              "$ref": "#/components/schemas/ResolvedSvgVisual"
            }
          ]
        }
      }
    }
  },
  "else": {
    "properties": {
      "snapshotTime": false,
      "quality": false,
      "transparent": false,
      "outputFormat": {
        "enum": [
          "mp4",
          "mov",
          "avi",
          "webm"
        ],
        "default": "mp4"
      }
    }
  }
}
```
**allOf alternative 2**

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "if": {
    "properties": {
      "type": {
        "const": "image"
      },
      "outputFormat": {
        "enum": [
          "png"
        ]
      }
    },
    "required": [
      "type",
      "outputFormat"
    ]
  },
  "then": {
    "properties": {
      "quality": false
    }
  }
}
```
**allOf alternative 3**

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "if": {
    "properties": {
      "type": {
        "const": "image"
      }
    },
    "required": [
      "type"
    ],
    "not": {
      "required": [
        "outputFormat"
      ]
    }
  },
  "then": {
    "properties": {
      "quality": false
    }
  }
}
```
**allOf alternative 4**

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "if": {
    "properties": {
      "type": {
        "const": "image"
      },
      "outputFormat": {
        "enum": [
          "jpg",
          "jpeg"
        ]
      }
    },
    "required": [
      "type",
      "outputFormat"
    ]
  },
  "then": {
    "properties": {
      "transparent": {
        "const": false
      }
    }
  }
}
```


### schema resolved visual

**ResolvedVisual**


One canvas element, discriminated by `type` (IMAGE | VIDEO | GIF | SVG | TEXT, case-insensitive).

Required properties: `type`. Exactly one of the listed alternatives must match.

**oneOf alternative 1**

Schema: [ResolvedImageVisual](#schema-resolved-image-visual).

**oneOf alternative 2**

Schema: [ResolvedVideoVisual](#schema-resolved-video-visual).

**oneOf alternative 3**

Schema: [ResolvedGifVisual](#schema-resolved-gif-visual).

**oneOf alternative 4**

Schema: [ResolvedSvgVisual](#schema-resolved-svg-visual).

**oneOf alternative 5**

Schema: [ResolvedTextVisual](#schema-resolved-text-visual).


### schema resolved image visual

**ResolvedImageVisual**


A raster image placed on the canvas.

Unknown properties are rejected. Required properties: `type`, `src`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedImageVisual.type` | string | Yes | "IMAGE" (case-insensitive). Pattern: `^[Ii][Mm][Aa][Gg][Ee]$`. |
| `ResolvedImageVisual.x` | number | No | Anchor-point X in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y. |
| `ResolvedImageVisual.y` | number | No | Anchor-point Y in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y. |
| `ResolvedImageVisual.width` | number | No | Element width in px (plan-limited). Minimum: `1`. |
| `ResolvedImageVisual.height` | number | No | Element height in px (plan-limited). Minimum: `1`. |
| `ResolvedImageVisual.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` | No | Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. "bottom-center" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless "custom". Two elements with the same preset render exactly stacked. |
| `ResolvedImageVisual.anchor` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` | No | Which point of the ELEMENT sits at the position point. Defaults to the position preset itself ("custom" defaults to top-left). |
| `ResolvedImageVisual.resize` | `"contain"` / `"cover"` | No | How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas. |
| `ResolvedImageVisual.enterBegin` | number | No | Seconds when the element starts appearing (enter animation start). Minimum: `0`. |
| `ResolvedImageVisual.enterEnd` | number | No | Seconds when the enter animation finishes. Must be >= enterBegin. Minimum: `0`. |
| `ResolvedImageVisual.exitBegin` | number | No | Seconds when the exit animation starts. Minimum: `0`. |
| `ResolvedImageVisual.exitEnd` | number | No | Seconds when the element is fully gone. Must be >= exitBegin. Minimum: `0`. |
| `ResolvedImageVisual.opacity` | number | No | Element opacity, 0..1. Minimum: `0`. Maximum: `1`. |
| `ResolvedImageVisual.angle` | number | No | Rotation in degrees. Minimum: `-360`. Maximum: `360`. |
| `ResolvedImageVisual.flipV` | boolean | No | Flip vertically. |
| `ResolvedImageVisual.flipH` | boolean | No | Flip horizontally. |
| `ResolvedImageVisual.track` | integer | No | Z-order track; higher tracks render on top. Minimum: `0`. Maximum: `1000000`. |
| `ResolvedImageVisual.enterAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Enter animation effect (xfade family) or null. At least one listed alternative must match. |
| `ResolvedImageVisual.exitAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Exit animation effect (xfade family) or null. At least one listed alternative must match. |
| `ResolvedImageVisual.src` | string | Yes | Source media URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs. Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`. |
| `ResolvedImageVisual.cropParams` | [ResolvedCropParams](#schema-resolved-crop-params) | No |  |
| `ResolvedImageVisual.filter` | [ResolvedFilter](#schema-resolved-filter) | No |  |
| `ResolvedImageVisual.chromaKey` | [ResolvedChromaKey](#schema-resolved-chroma-key) | No |  |
| `ResolvedImageVisual.zoom` | [ResolvedZoom](#schema-resolved-zoom) | No |  |
| `ResolvedImageVisual.radius` | [ResolvedRadius](#schema-resolved-radius) | No |  |

**Nested field: `ResolvedImageVisual.enterAnimation`**

Enter animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**Nested field: `ResolvedImageVisual.exitAnimation`**

Exit animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.


### schema resolved crop params

**ResolvedCropParams**


Source crop rectangle in source-media pixels.

Unknown properties are rejected. Required properties: `x`, `y`, `width`, `height`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedCropParams.x` | number | Yes | Minimum: `0`. |
| `ResolvedCropParams.y` | number | Yes | Minimum: `0`. |
| `ResolvedCropParams.width` | number | Yes | Minimum: `1`. |
| `ResolvedCropParams.height` | number | Yes | Minimum: `1`. |


### schema resolved filter

**ResolvedFilter**


CSS-like color filters.

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedFilter.brightness` | number | No | Minimum: `-100`. Maximum: `100`. |
| `ResolvedFilter.contrast` | number | No | Minimum: `-100`. Maximum: `100`. |
| `ResolvedFilter.saturate` | number | No | Minimum: `-100`. Maximum: `100`. |
| `ResolvedFilter.hue-rotate` | string | No | Degrees, preferably with a "deg" suffix (e.g. "90deg"). Legacy numeric strings such as "90" remain accepted. Pattern: `^-?(?:\d+(?:\.\d+)?\|\.\d+)(?:deg)?$`. |
| `ResolvedFilter.blur` | number OR string | No | Unitless 0..100 amount. Numeric strings remain accepted for backward compatibility; CSS lengths such as 4px are not supported. At least one listed alternative must match. |
| `ResolvedFilter.invert` | boolean OR number | No | Boolean or partial inversion amount from 0 to 1. At least one listed alternative must match. |
| `ResolvedFilter.colorTint` | string | No | Tint color (#rgb or #rrggbb). Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{3})$`. |

**Nested field: `ResolvedFilter.blur`**

Unitless 0..100 amount. Numeric strings remain accepted for backward compatibility; CSS lengths such as 4px are not supported.

At least one listed alternative must match.

**anyOf alternative 1**

Minimum: `0`. Maximum: `100`.

Type: number.

**anyOf alternative 2**

Pattern: `^(?:0*(?:100(?:\.0+)?|\d{1,2}(?:\.\d+)?|\.\d+))$`.

Type: string.

**Nested field: `ResolvedFilter.invert`**

Boolean or partial inversion amount from 0 to 1.

At least one listed alternative must match.

**anyOf alternative 1**

Type: boolean.

**anyOf alternative 2**

Minimum: `0`. Maximum: `1`.

Type: number.


### schema resolved chroma key

**ResolvedChromaKey**


Green-screen keying.

Unknown properties are rejected. Required properties: `color`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedChromaKey.color` | string | Yes | Key color to remove (required). Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{3})$`. |
| `ResolvedChromaKey.similarity` | number | No | Minimum: `0`. Maximum: `100`. |
| `ResolvedChromaKey.blend` | number | No | Minimum: `0`. Maximum: `100`. |


### schema resolved zoom

**ResolvedZoom**


Ken Burns zoom: true (default 1.2x depth) or &#123; "depth": 1..10 &#125;.

At least one listed alternative must match.

**anyOf alternative 1**

Type: boolean.

**anyOf alternative 2**

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedZoom.depth` | number | No | Minimum: `1`. Maximum: `10`. |


### schema resolved radius

**ResolvedRadius**


Rounded-corner radii in px (tl/tr/bl/br).

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedRadius.tl` | number | No | Minimum: `0`. |
| `ResolvedRadius.tr` | number | No | Minimum: `0`. |
| `ResolvedRadius.bl` | number | No | Minimum: `0`. |
| `ResolvedRadius.br` | number | No | Minimum: `0`. |


### schema resolved video visual

**ResolvedVideoVisual**


A video clip placed on the canvas (video projects only).

Unknown properties are rejected. Required properties: `type`, `src`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedVideoVisual.type` | string | Yes | "VIDEO" (case-insensitive). Pattern: `^[Vv][Ii][Dd][Ee][Oo]$`. |
| `ResolvedVideoVisual.x` | number | No | Anchor-point X in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y. |
| `ResolvedVideoVisual.y` | number | No | Anchor-point Y in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y. |
| `ResolvedVideoVisual.width` | number | No | Element width in px (plan-limited). Minimum: `1`. |
| `ResolvedVideoVisual.height` | number | No | Element height in px (plan-limited). Minimum: `1`. |
| `ResolvedVideoVisual.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` | No | Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. "bottom-center" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless "custom". Two elements with the same preset render exactly stacked. |
| `ResolvedVideoVisual.anchor` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` | No | Which point of the ELEMENT sits at the position point. Defaults to the position preset itself ("custom" defaults to top-left). |
| `ResolvedVideoVisual.resize` | `"contain"` / `"cover"` | No | How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas. |
| `ResolvedVideoVisual.enterBegin` | number | No | Seconds when the element starts appearing (enter animation start). Minimum: `0`. |
| `ResolvedVideoVisual.enterEnd` | number | No | Seconds when the enter animation finishes. Must be >= enterBegin. Minimum: `0`. |
| `ResolvedVideoVisual.exitBegin` | number | No | Seconds when the exit animation starts. Minimum: `0`. |
| `ResolvedVideoVisual.exitEnd` | number | No | Seconds when the element is fully gone. Must be >= exitBegin. Minimum: `0`. |
| `ResolvedVideoVisual.opacity` | number | No | Element opacity, 0..1. Minimum: `0`. Maximum: `1`. |
| `ResolvedVideoVisual.angle` | number | No | Rotation in degrees. Minimum: `-360`. Maximum: `360`. |
| `ResolvedVideoVisual.flipV` | boolean | No | Flip vertically. |
| `ResolvedVideoVisual.flipH` | boolean | No | Flip horizontally. |
| `ResolvedVideoVisual.track` | integer | No | Z-order track; higher tracks render on top. Minimum: `0`. Maximum: `1000000`. |
| `ResolvedVideoVisual.enterAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Enter animation effect (xfade family) or null. At least one listed alternative must match. |
| `ResolvedVideoVisual.exitAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Exit animation effect (xfade family) or null. At least one listed alternative must match. |
| `ResolvedVideoVisual.src` | string | Yes | Source media URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs. Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`. |
| `ResolvedVideoVisual.cropParams` | [ResolvedCropParams](#schema-resolved-crop-params) | No |  |
| `ResolvedVideoVisual.filter` | [ResolvedFilter](#schema-resolved-filter) | No |  |
| `ResolvedVideoVisual.chromaKey` | [ResolvedChromaKey](#schema-resolved-chroma-key) | No |  |
| `ResolvedVideoVisual.zoom` | [ResolvedZoom](#schema-resolved-zoom) | No |  |
| `ResolvedVideoVisual.radius` | [ResolvedRadius](#schema-resolved-radius) | No |  |
| `ResolvedVideoVisual.videoBegin` | number | No | Trim: source time (s) where playback starts. Minimum: `0`. |
| `ResolvedVideoVisual.videoEnd` | number | No | Trim: source time (s) where playback ends. Minimum: `0`. |
| `ResolvedVideoVisual.videoDuration` | number | No | Play only this many seconds of the source. Minimum: `0.1`. |
| `ResolvedVideoVisual.volume` | number | No | Clip audio volume, 0..2 (1 = original level). Minimum: `0`. Maximum: `2`. |
| `ResolvedVideoVisual.speed` | number | No | Playback speed multiplier, 0.1..10. Minimum: `0.1`. Maximum: `10`. |
| `ResolvedVideoVisual.transition` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Transition into the linked clip (see transitionId). At least one listed alternative must match. |
| `ResolvedVideoVisual.transitionDuration` | number | No | Transition length in seconds. Minimum: `0`. |
| `ResolvedVideoVisual.transitionId` | string | No | id of the clip this transition links to. Maximum length: `100`. Pattern: `^[a-zA-Z0-9_-]+$`. |
| `ResolvedVideoVisual.frameRate` | integer | No | Override source frame rate (1..60). Minimum: `1`. Maximum: `60`. |
| `ResolvedVideoVisual.id` | string | No | Clip id (referenced by transitionId). Pattern: `^[a-zA-Z0-9_-]+$`. |
| `ResolvedVideoVisual.hasAudio` | boolean | No | Hint that the source has an audio stream. |

**Nested field: `ResolvedVideoVisual.enterAnimation`**

Enter animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**Nested field: `ResolvedVideoVisual.exitAnimation`**

Exit animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**Nested field: `ResolvedVideoVisual.transition`**

Transition into the linked clip (see transitionId).

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.


### schema resolved gif visual

**ResolvedGifVisual**


An animated GIF placed on the canvas (video projects only).

Unknown properties are rejected. Required properties: `type`, `src`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedGifVisual.type` | string | Yes | "GIF" (case-insensitive). Pattern: `^[Gg][Ii][Ff]$`. |
| `ResolvedGifVisual.x` | number | No | Anchor-point X in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y. |
| `ResolvedGifVisual.y` | number | No | Anchor-point Y in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y. |
| `ResolvedGifVisual.width` | number | No | Element width in px (plan-limited). Minimum: `1`. |
| `ResolvedGifVisual.height` | number | No | Element height in px (plan-limited). Minimum: `1`. |
| `ResolvedGifVisual.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` | No | Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. "bottom-center" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless "custom". Two elements with the same preset render exactly stacked. |
| `ResolvedGifVisual.anchor` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` | No | Which point of the ELEMENT sits at the position point. Defaults to the position preset itself ("custom" defaults to top-left). |
| `ResolvedGifVisual.resize` | `"contain"` / `"cover"` | No | How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas. |
| `ResolvedGifVisual.enterBegin` | number | No | Seconds when the element starts appearing (enter animation start). Minimum: `0`. |
| `ResolvedGifVisual.enterEnd` | number | No | Seconds when the enter animation finishes. Must be >= enterBegin. Minimum: `0`. |
| `ResolvedGifVisual.exitBegin` | number | No | Seconds when the exit animation starts. Minimum: `0`. |
| `ResolvedGifVisual.exitEnd` | number | No | Seconds when the element is fully gone. Must be >= exitBegin. Minimum: `0`. |
| `ResolvedGifVisual.opacity` | number | No | Element opacity, 0..1. Minimum: `0`. Maximum: `1`. |
| `ResolvedGifVisual.angle` | number | No | Rotation in degrees. Minimum: `-360`. Maximum: `360`. |
| `ResolvedGifVisual.flipV` | boolean | No | Flip vertically. |
| `ResolvedGifVisual.flipH` | boolean | No | Flip horizontally. |
| `ResolvedGifVisual.track` | integer | No | Z-order track; higher tracks render on top. Minimum: `0`. Maximum: `1000000`. |
| `ResolvedGifVisual.enterAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Enter animation effect (xfade family) or null. At least one listed alternative must match. |
| `ResolvedGifVisual.exitAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Exit animation effect (xfade family) or null. At least one listed alternative must match. |
| `ResolvedGifVisual.src` | string | Yes | Source media URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs. Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`. |
| `ResolvedGifVisual.cropParams` | [ResolvedCropParams](#schema-resolved-crop-params) | No |  |
| `ResolvedGifVisual.filter` | [ResolvedFilter](#schema-resolved-filter) | No |  |
| `ResolvedGifVisual.chromaKey` | [ResolvedChromaKey](#schema-resolved-chroma-key) | No |  |
| `ResolvedGifVisual.zoom` | [ResolvedZoom](#schema-resolved-zoom) | No |  |
| `ResolvedGifVisual.radius` | [ResolvedRadius](#schema-resolved-radius) | No |  |

**Nested field: `ResolvedGifVisual.enterAnimation`**

Enter animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**Nested field: `ResolvedGifVisual.exitAnimation`**

Exit animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.


### schema resolved svg visual

**ResolvedSvgVisual**


Inline SVG markup drawn on the canvas.

Unknown properties are rejected. Required properties: `type`, `svg`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSvgVisual.type` | string | Yes | "SVG" (case-insensitive). Pattern: `^[Ss][Vv][Gg]$`. |
| `ResolvedSvgVisual.x` | number | No | Anchor-point X in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y. |
| `ResolvedSvgVisual.y` | number | No | Anchor-point Y in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y. |
| `ResolvedSvgVisual.width` | number | No | Element width in px (plan-limited). Minimum: `1`. |
| `ResolvedSvgVisual.height` | number | No | Element height in px (plan-limited). Minimum: `1`. |
| `ResolvedSvgVisual.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` | No | Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. "bottom-center" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless "custom". Two elements with the same preset render exactly stacked. |
| `ResolvedSvgVisual.anchor` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` | No | Which point of the ELEMENT sits at the position point. Defaults to the position preset itself ("custom" defaults to top-left). |
| `ResolvedSvgVisual.resize` | `"contain"` / `"cover"` | No | How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas. |
| `ResolvedSvgVisual.enterBegin` | number | No | Seconds when the element starts appearing (enter animation start). Minimum: `0`. |
| `ResolvedSvgVisual.enterEnd` | number | No | Seconds when the enter animation finishes. Must be >= enterBegin. Minimum: `0`. |
| `ResolvedSvgVisual.exitBegin` | number | No | Seconds when the exit animation starts. Minimum: `0`. |
| `ResolvedSvgVisual.exitEnd` | number | No | Seconds when the element is fully gone. Must be >= exitBegin. Minimum: `0`. |
| `ResolvedSvgVisual.opacity` | number | No | Element opacity, 0..1. Minimum: `0`. Maximum: `1`. |
| `ResolvedSvgVisual.angle` | number | No | Rotation in degrees. Minimum: `-360`. Maximum: `360`. |
| `ResolvedSvgVisual.flipV` | boolean | No | Flip vertically. |
| `ResolvedSvgVisual.flipH` | boolean | No | Flip horizontally. |
| `ResolvedSvgVisual.track` | integer | No | Z-order track; higher tracks render on top. Minimum: `0`. Maximum: `1000000`. |
| `ResolvedSvgVisual.enterAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Enter animation effect (xfade family) or null. At least one listed alternative must match. |
| `ResolvedSvgVisual.exitAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Exit animation effect (xfade family) or null. At least one listed alternative must match. |
| `ResolvedSvgVisual.svg` | string | Yes | Inline SVG markup starting with "&lt;svg". Sanitized: no &lt;script>/&lt;foreignObject>/&lt;iframe>/&lt;object>/&lt;embed>/&lt;audio>/&lt;video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions &lt;= 4096px, no integers with 8+ digits. Minimum length: `1`. Maximum length: `200000`. |
| `ResolvedSvgVisual.filter` | [ResolvedFilter](#schema-resolved-filter) | No |  |
| `ResolvedSvgVisual.chromaKey` | [ResolvedChromaKey](#schema-resolved-chroma-key) | No |  |
| `ResolvedSvgVisual.customCode` | [ResolvedCustomCode](#schema-resolved-custom-code) | No |  |
| `ResolvedSvgVisual.designer` | [ResolvedDesigner](#schema-resolved-designer) | No |  |

**Nested field: `ResolvedSvgVisual.enterAnimation`**

Enter animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**Nested field: `ResolvedSvgVisual.exitAnimation`**

Exit animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.


### schema resolved custom code

**ResolvedCustomCode**


Sandboxed styling/animation code run inside the render browser (Design Studio). No network/filesystem/storage/navigation APIs — see validation notes.

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedCustomCode.css` | string | No | Maximum length: `200000`. |
| `ResolvedCustomCode.js` | string | No | Maximum length: `200000`. |
| `ResolvedCustomCode.animationDuration` | number | No | Seconds the customCode animation runs (max 15). Maximum: `15`. Greater than: `0`. |


### schema resolved designer

**ResolvedDesigner**


Opaque Design Studio round-trip metadata (max 200k chars serialized). Never rendered; keeps 'edit design' working.

Type: object.


### schema resolved text visual

**ResolvedTextVisual**


Styled text. Provide plain `text` (no &lt; or > characters) and/or limited `html`. At least one of text/html must be non-empty.

Unknown properties are rejected. Required properties: `type`. At least one listed alternative must match.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedTextVisual.type` | string | Yes | "TEXT" (case-insensitive). Pattern: `^[Tt][Ee][Xx][Tt]$`. |
| `ResolvedTextVisual.x` | number | No | Anchor-point X in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y. |
| `ResolvedTextVisual.y` | number | No | Anchor-point Y in canvas px (may be negative). ONLY used when position is "custom" — every other preset OVERWRITES x/y. |
| `ResolvedTextVisual.width` | number | No | Element width in px (plan-limited). Minimum: `1`. |
| `ResolvedTextVisual.height` | number | No | Element height in px (plan-limited). Minimum: `1`. |
| `ResolvedTextVisual.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` | No | Placement preset. Sets BOTH the canvas point AND the default anchor (e.g. "bottom-center" puts the element's bottom edge flush with the frame bottom — no margin). x/y are IGNORED unless "custom". Two elements with the same preset render exactly stacked. |
| `ResolvedTextVisual.anchor` | `"top-left"` / `"top-center"` / `"top-right"` / `"center-left"` / `"center-center"` / `"center-right"` / `"bottom-right"` / `"bottom-center"` / `"bottom-left"` / `"custom"` | No | Which point of the ELEMENT sits at the position point. Defaults to the position preset itself ("custom" defaults to top-left). |
| `ResolvedTextVisual.resize` | `"contain"` / `"cover"` | No | How media fits its frame: contain (letterbox) or cover (center-crop). With explicit width/height the media fits INTO that box (CSS object-fit); without a box it is sized against the full canvas. |
| `ResolvedTextVisual.enterBegin` | number | No | Seconds when the element starts appearing (enter animation start). Minimum: `0`. |
| `ResolvedTextVisual.enterEnd` | number | No | Seconds when the enter animation finishes. Must be >= enterBegin. Minimum: `0`. |
| `ResolvedTextVisual.exitBegin` | number | No | Seconds when the exit animation starts. Minimum: `0`. |
| `ResolvedTextVisual.exitEnd` | number | No | Seconds when the element is fully gone. Must be >= exitBegin. Minimum: `0`. |
| `ResolvedTextVisual.opacity` | number | No | Element opacity, 0..1. Minimum: `0`. Maximum: `1`. |
| `ResolvedTextVisual.angle` | number | No | Rotation in degrees. Minimum: `-360`. Maximum: `360`. |
| `ResolvedTextVisual.flipV` | boolean | No | Flip vertically. |
| `ResolvedTextVisual.flipH` | boolean | No | Flip horizontally. |
| `ResolvedTextVisual.track` | integer | No | Z-order track; higher tracks render on top. Minimum: `0`. Maximum: `1000000`. |
| `ResolvedTextVisual.enterAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Enter animation effect (xfade family) or null. At least one listed alternative must match. |
| `ResolvedTextVisual.exitAnimation` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Exit animation effect (xfade family) or null. At least one listed alternative must match. |
| `ResolvedTextVisual.text` | string | No | Plain text content. HTML markup (&lt; or >) is rejected — use `html` for markup. Maximum length: `20000`. |
| `ResolvedTextVisual.html` | string | No | Limited HTML. Allowed tags: b, strong, i, em, u, s, br, span, div, p, ul, ol, li, img, canvas, svg (+ svg geometry). Allowed attributes: style, class, src, alt, width, height. No &lt;script>/&lt;style>, no event handlers, no external CSS url(). Maximum length: `200000`. |
| `ResolvedTextVisual.style` | object | No | CSS style object applied to the text container (camelCase or kebab-case keys, and --custom-properties). LAYOUT MODEL: the container uses box-sizing: content-box, so padding/border render OUTSIDE the declared width/height and overflow gets cut — for cards/pills set the full size via width/height and center content with display:flex + alignItems:center + justifyContent:center instead of padding. Text sits at the TOP of the box unless flex-centered. Values must not contain url(), @import, expression(), comments or other dangerous tokens. Max value length 4000 chars. Maximum properties: `120`. |
| `ResolvedTextVisual.fitToBox` | boolean | No | Scale the typography down just enough that the painted text stays inside the declared width/height instead of being clipped at the element's edge. No effect when the content already fits, so an unchanged template renders identically. |
| `ResolvedTextVisual.customCode` | [ResolvedCustomCode](#schema-resolved-custom-code) | No |  |
| `ResolvedTextVisual.designer` | [ResolvedDesigner](#schema-resolved-designer) | No |  |

**Nested field: `ResolvedTextVisual.enterAnimation`**

Enter animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**Nested field: `ResolvedTextVisual.exitAnimation`**

Exit animation effect (xfade family) or null.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**anyOf alternative 1**

Required properties: `text`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedTextVisual.text` | string | Yes | Pattern: `\S`. |

**anyOf alternative 2**

Required properties: `html`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedTextVisual.html` | string | Yes | Pattern: `\S`. |


### schema resolved audio item

**ResolvedAudioItem**


A soundtrack / voice-over entry for `audios` (video projects only).

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedAudioItem.src` | string | No | Audio file URL (mp3/wav/...). Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs. Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`. |
| `ResolvedAudioItem.matchDuration` | boolean | No | Follow the containing scene/project length without extending automatic duration. Overrides exit. |
| `ResolvedAudioItem.enter` | number | No | Timeline second the audio starts playing. Minimum: `0`. |
| `ResolvedAudioItem.exit` | number | No | Timeline second the audio stops. Must be >= enter. Minimum: `0`. |
| `ResolvedAudioItem.volume` | number | No | Minimum: `0`. Maximum: `2`. |
| `ResolvedAudioItem.speed` | number | No | Minimum: `0.1`. Maximum: `10`. |
| `ResolvedAudioItem.audioBegin` | number | No | Trim: source second where playback starts. Minimum: `0`. |
| `ResolvedAudioItem.audioEnd` | number | No | Trim: source second where playback ends. Must be >= audioBegin. Minimum: `0`. |
| `ResolvedAudioItem.audioDuration` | number | No | Play only this many seconds of the source. Minimum: `0`. |
| `ResolvedAudioItem.track` | integer | No | Mix track index; audio tracks mix together. Minimum: `0`. Maximum: `1000000`. |


### schema resolved scene

**ResolvedScene**


A timeline segment. Scenes play sequentially; a scene's `transition` blends into the NEXT scene (xfade overlap is subtracted from the total duration).

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedScene.id` | string | No | Optional scene ID. Explicit IDs must be unique within the project. Maximum length: `100`. Pattern: `^[a-zA-Z0-9_-]+$`. |
| `ResolvedScene.duration` | `-1` OR number | No | Seconds; -1 (or omitted) auto-computes from the scene's content. At least one listed alternative must match. |
| `ResolvedScene.transition` | `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"` OR null | No | Transition into the next scene. At least one listed alternative must match. |
| `ResolvedScene.transitionId` | string OR null | No | At least one listed alternative must match. |
| `ResolvedScene.transitionDuration` | number | No | Transition overlap in seconds (default 0.5). Minimum: `0`. Maximum: `60`. |
| `ResolvedScene.backgroundColor` | string | No | Scene background (#rgb or #rrggbb). Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{3})$`. |
| `ResolvedScene.visuals` | array of [ResolvedVisual](#schema-resolved-visual) | No |  |
| `ResolvedScene.audios` | array of [ResolvedAudioItem](#schema-resolved-audio-item) | No |  |

**Nested field: `ResolvedScene.duration`**

Seconds; -1 (or omitted) auto-computes from the scene's content.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `-1`.

**anyOf alternative 2**

Minimum: `0.1`.

Type: number.

**Nested field: `ResolvedScene.transition`**

Transition into the next scene.

At least one listed alternative must match.

**anyOf alternative 1**

Type: `"fade"` / `"fadeblack"` / `"fadewhite"` / `"distance"` / `"wipeleft"` / `"wiperight"` / `"wipeup"` / `"wipedown"` / `"slideleft"` / `"slideright"` / `"slideup"` / `"slidedown"` / `"smoothleft"` / `"smoothright"` / `"smoothup"` / `"smoothdown"` / `"circlecrop"` / `"rectcrop"` / `"circleclose"` / `"circleopen"` / `"horzclose"` / `"horzopen"` / `"vertclose"` / `"vertopen"` / `"diagbl"` / `"diagbr"` / `"diagtl"` / `"diagtr"` / `"hlslice"` / `"hrslice"` / `"vuslice"` / `"vdslice"` / `"dissolve"` / `"pixelize"` / `"radial"` / `"hblur"` / `"wipetl"` / `"wipetr"` / `"wipebl"` / `"wipebr"` / `"fadegrays"` / `"zoomin"` / `"hlwind"` / `"hrwind"` / `"squeezeh"` / `"squeezev"` / `"fadefast"` / `"fadeslow"` / `"vuwind"` / `"vdwind"` / `"coverleft"` / `"coverright"` / `"coverup"` / `"coverdown"` / `"revealleft"` / `"revealright"` / `"revealup"` / `"revealdown"`.

**anyOf alternative 2**

Type: null.

**Nested field: `ResolvedScene.transitionId`**

At least one listed alternative must match.

**anyOf alternative 1**

Maximum length: `100`. Pattern: `^[a-zA-Z0-9_-]+$`.

Type: string.

**anyOf alternative 2**

Type: null.

**Nested field: `ResolvedScene.visuals`**

**Array item: `ResolvedScene.visuals[]`**

Schema: [ResolvedVisual](#schema-resolved-visual).

**Nested field: `ResolvedScene.audios`**

**Array item: `ResolvedScene.audios[]`**

Schema: [ResolvedAudioItem](#schema-resolved-audio-item).


### schema resolved subtitle

**ResolvedSubtitle**


Burned-in subtitles. Content: EXACTLY ONE of `src` (SRT/VTT URL) or `captions`. Style: v2 flat keys (animation/font/stroke/background/...) OR the legacy `styles` block — not both.

Unknown properties are rejected. Exactly one of the listed alternatives must match.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.src` | string | No | SRT or VTT file URL. Must be a PUBLIC http(s) URL: no credentials, no spaces/backslashes, port 80/443 only, no localhost/.local, no private IPs. Minimum length: `1`. Maximum length: `2048`. Pattern: `^https?://`. |
| `ResolvedSubtitle.captions` | array of any JSON value OR any JSON value | No | Inline captions (plan-limited count). Minimum items: `1`. |
| `ResolvedSubtitle.maxWordsPerLine` | integer | No | Re-chunk captions to at most N words per line. Minimum: `1`. Maximum: `20`. |
| `ResolvedSubtitle.animation` | `"normal"` / `"none"` / `"one-word"` / `"karaoke"` / `"progressive"` / `"highlight"` / `"fill"` / `"pop"` / `"bounce"` / `"fade"` / `"slide"` / `"typewriter"` | No | Caption animation mode. |
| `ResolvedSubtitle.direction` | `"up"` / `"down"` / `"left"` / `"right"` | No | Slide direction when animation is "slide". |
| `ResolvedSubtitle.font` | object | No | Minimum properties: `1`. Unknown properties are rejected. |
| `ResolvedSubtitle.stroke` | object | No | Unknown properties are rejected. Required properties: `color`, `width`. |
| `ResolvedSubtitle.background` | object | No | Minimum properties: `1`. Unknown properties are rejected. |
| `ResolvedSubtitle.activeWord` | object | No | Highlight styling for the currently spoken word. Minimum properties: `1`. Unknown properties are rejected. |
| `ResolvedSubtitle.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"bottom-left"` / `"bottom-center"` / `"bottom-right"` / `"center-center"` / `"center-left"` / `"center-right"` / `"top"` / `"center"` / `"bottom"` | No |  |
| `ResolvedSubtitle.margin` | object | No | Minimum properties: `1`. Unknown properties are rejected. |
| `ResolvedSubtitle.styles` | object | No | LEGACY style block — prefer the flat v2 keys. Cannot be combined with them. Unknown properties are rejected. |

**Nested field: `ResolvedSubtitle.captions`**

Inline captions (plan-limited count).

Minimum items: `1`.

**Array item: `ResolvedSubtitle.captions[]`**

Unknown properties are rejected. Required properties: `start`, `end`. At least one listed alternative must match.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.captions[].start` | number | Yes | Caption start time in seconds. Minimum: `0`. |
| `ResolvedSubtitle.captions[].end` | number | Yes | Caption end time in seconds. Minimum: `0`. |
| `ResolvedSubtitle.captions[].text` | string | No | Minimum length: `1`. Maximum length: `1000`. |
| `ResolvedSubtitle.captions[].words` | array of object | No | Per-word timings; auto-distributed from `text` when omitted. Minimum items: `1`. |

**Nested field: `ResolvedSubtitle.captions[].words`**

Per-word timings; auto-distributed from `text` when omitted.

Minimum items: `1`.

**Array item: `ResolvedSubtitle.captions[].words[]`**

Unknown properties are rejected. Required properties: `start`, `end`, `text`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.captions[].words[].start` | number | Yes | Minimum: `0`. |
| `ResolvedSubtitle.captions[].words[].end` | number | Yes | Minimum: `0`. |
| `ResolvedSubtitle.captions[].words[].text` | string | Yes | Minimum length: `1`. Maximum length: `100`. |

**anyOf alternative 1**

Required properties: `text`.

Type: any JSON value.

**anyOf alternative 2**

Required properties: `words`.

Type: any JSON value.

**Nested field: `ResolvedSubtitle.font`**

Minimum properties: `1`. Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.font.family` | string | No | Maximum length: `1000`. Pattern: `^[a-zA-Z0-9_\- ]+$`. |
| `ResolvedSubtitle.font.size` | number | No | Minimum: `1`. Maximum: `1000`. |
| `ResolvedSubtitle.font.color` | string | No | #rrggbb or #rrggbbaa Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{8})$`. |
| `ResolvedSubtitle.font.bold` | boolean | No |  |
| `ResolvedSubtitle.font.italic` | boolean | No |  |
| `ResolvedSubtitle.font.transform` | `"uppercase"` / `"lowercase"` / `"capitalize"` | No |  |

**Nested field: `ResolvedSubtitle.stroke`**

Unknown properties are rejected. Required properties: `color`, `width`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.stroke.color` | string | Yes | #rrggbb or #rrggbbaa Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{8})$`. |
| `ResolvedSubtitle.stroke.width` | number | Yes | Minimum: `0`. Maximum: `100`. |

**Nested field: `ResolvedSubtitle.background`**

Minimum properties: `1`. Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.background.color` | string | No | #rrggbb or #rrggbbaa Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{8})$`. |
| `ResolvedSubtitle.background.opacity` | number | No | Minimum: `0`. Maximum: `1`. |
| `ResolvedSubtitle.background.padding` | number | No | Minimum: `0`. Maximum: `200`. |
| `ResolvedSubtitle.background.radius` | number | No | Minimum: `0`. Maximum: `200`. |

**Nested field: `ResolvedSubtitle.activeWord`**

Highlight styling for the currently spoken word.

Minimum properties: `1`. Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.activeWord.color` | string | No | #rrggbb or #rrggbbaa Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{8})$`. |
| `ResolvedSubtitle.activeWord.background` | string | No | #rrggbb or #rrggbbaa Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{8})$`. |
| `ResolvedSubtitle.activeWord.radius` | number | No | Minimum: `0`. Maximum: `200`. |

**Nested field: `ResolvedSubtitle.margin`**

Minimum properties: `1`. Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.margin.x` | integer | No | Minimum: `0`. |
| `ResolvedSubtitle.margin.y` | integer | No | Minimum: `0`. |

**Nested field: `ResolvedSubtitle.styles`**

LEGACY style block — prefer the flat v2 keys. Cannot be combined with them.

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.styles.color` | string | No | #rrggbb or #rrggbbaa Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{8})$`. |
| `ResolvedSubtitle.styles.background` | string | No | #rrggbb or #rrggbbaa Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{8})$`. |
| `ResolvedSubtitle.styles.backgroundPadding` | number | No | Minimum: `0`. Maximum: `200`. |
| `ResolvedSubtitle.styles.backgroundRadius` | number | No | Minimum: `0`. Maximum: `200`. |
| `ResolvedSubtitle.styles.isBold` | boolean | No |  |
| `ResolvedSubtitle.styles.isItalic` | boolean | No |  |
| `ResolvedSubtitle.styles.fontSize` | number | No | Minimum: `1`. Maximum: `1000`. |
| `ResolvedSubtitle.styles.fontFamily` | string | No | Maximum length: `1000`. Pattern: `^[a-zA-Z0-9_\- ]+$`. |
| `ResolvedSubtitle.styles.textTransform` | `"uppercase"` / `"lowercase"` / `"capitalize"` | No |  |
| `ResolvedSubtitle.styles.outline` | object | No | Unknown properties are rejected. Required properties: `width`, `color`. |
| `ResolvedSubtitle.styles.position` | `"top-left"` / `"top-center"` / `"top-right"` / `"bottom-left"` / `"bottom-center"` / `"bottom-right"` / `"center-center"` / `"center-left"` / `"center-right"` | No |  |
| `ResolvedSubtitle.styles.marginV` | integer | No | Minimum: `0`. |
| `ResolvedSubtitle.styles.marginH` | integer | No | Minimum: `0`. |
| `ResolvedSubtitle.styles.mode` | `"normal"` / `"none"` / `"one-word"` / `"karaoke"` / `"progressive"` / `"highlight"` / `"fill"` / `"pop"` / `"bounce"` / `"fade"` / `"slide"` / `"typewriter"` | No |  |
| `ResolvedSubtitle.styles.slideDirection` | `"up"` / `"down"` / `"left"` / `"right"` | No |  |
| `ResolvedSubtitle.styles.activeWord` | object | No | Minimum properties: `1`. Unknown properties are rejected. |

**Nested field: `ResolvedSubtitle.styles.outline`**

Unknown properties are rejected. Required properties: `width`, `color`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.styles.outline.width` | number | Yes | Minimum: `0`. Maximum: `100`. |
| `ResolvedSubtitle.styles.outline.color` | string | Yes | #rrggbb or #rrggbbaa Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{8})$`. |

**Nested field: `ResolvedSubtitle.styles.activeWord`**

Minimum properties: `1`. Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ResolvedSubtitle.styles.activeWord.color` | string | No | #rrggbb or #rrggbbaa Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{8})$`. |
| `ResolvedSubtitle.styles.activeWord.background` | string | No | #rrggbb or #rrggbbaa Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{8})$`. |
| `ResolvedSubtitle.styles.activeWord.radius` | number | No | Minimum: `0`. Maximum: `200`. |

**oneOf alternative 1**

Required properties: `src`.

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "not": {
    "required": [
      "captions"
    ]
  }
}
```
**oneOf alternative 2**

Required properties: `captions`.

Type: any JSON value.

Additional JSON Schema constraints:

```json
{
  "not": {
    "required": [
      "src"
    ]
  }
}
```
Additional JSON Schema constraints:

```json
{
  "dependentSchemas": {
    "styles": {
      "properties": {
        "animation": false,
        "direction": false,
        "font": false,
        "stroke": false,
        "background": false,
        "activeWord": false,
        "position": false,
        "margin": false
      }
    }
  }
}
```


### schema validation error

**ValidationError**


Required properties: `error`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ValidationError.error` | string | Yes |  |
| `ValidationError.message` | string | No |  |
| `ValidationError.details` | array of object | No |  |
| `ValidationError.planLimits` | object | No | Present on render validation errors; contains the authenticated user's current render limits. |

**Nested field: `ValidationError.details`**

**Array item: `ValidationError.details[]`**

Unknown properties are rejected. Required properties: `field`, `message`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `ValidationError.details[].field` | string | Yes |  |
| `ValidationError.details[].message` | string | Yes |  |


### schema authentication error

**AuthenticationError**


Unknown properties are rejected. Required properties: `error`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `AuthenticationError.error` | string | Yes | Error type |
| `AuthenticationError.message` | string | No | Human-readable error message |


### schema error

**Error**


Required properties: `error`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `Error.error` | string | Yes | Error type |
| `Error.message` | string | No | Human-readable error message |


## Related resources

- [OpenAPI specification](https://docs.zvid.io/openapi.yaml)
- [Project payload schema](https://docs.zvid.io/schemas/render-payload.schema.json)
- [Quick Start](https://docs.zvid.io/docs/quick-start/)
- [Authentication guide](https://docs.zvid.io/docs/authentication/)
