---
title: "Resolve and validate template"
canonical_url: https://docs.zvid.io/docs/endpoints/preview-template/
source: docs/endpoints/preview-template.api.mdx
content_revision: 6338386a1dd87e4a
---

# Resolve and validate template

`POST /api/templates/{id}/preview`

Free dry-run: substitute variables and overrides, then validate with account limits. Returns the resolved project and resolution statistics. Does not enqueue a render, produce media, or spend credits.

## Authentication

- Option 1: `x-api-key: YOUR_API_KEY` (header). API key for authentication. Create one in your dashboard.
- Option 2: `Authorization: Bearer YOUR_ACCESS_TOKEN`. Dashboard JWT in Authorization: Bearer &lt;token>, only on operations that explicitly list this scheme. Prefer x-api-key for REST integrations. OAuth for hosted MCP is a separate connection at https://mcp.zvid.io/mcp.

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

## Parameters

| Parameter | Location | Type | Required | Description and constraints |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes | Pattern: `^tpl_[A-Za-z0-9]{20}$`. |

## 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.

The request body is optional.


### application/json

Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `body.variables` | object | No |  |
| `body.overrides` | [RenderOverrides](#schema-render-overrides) | No |  |


### Request example: preview


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

## Responses


### HTTP 200

Successful response

Content type: `application/json`.

Required properties: `project`, `stats`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `response.project` | [ResolvedRenderPayload](#schema-resolved-render-payload) | Yes |  |
| `response.stats` | object | Yes |  |


**Representative successful response response**

```json
{
  "project": {
    "duration": 5,
    "width": 1280,
    "height": 720,
    "visuals": [
      {
        "type": "TEXT",
        "text": "Hello"
      }
    ]
  },
  "stats": {
    "sceneCount": 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 template preview request

**TemplatePreviewRequest**


Unknown properties are rejected.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `TemplatePreviewRequest.variables` | object | No |  |
| `TemplatePreviewRequest.overrides` | [RenderOverrides](#schema-render-overrides) | No |  |


### 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 template preview response

**TemplatePreviewResponse**


Required properties: `project`, `stats`.

| Field | Type | Required | Description and constraints |
| --- | --- | --- | --- |
| `TemplatePreviewResponse.project` | [ResolvedRenderPayload](#schema-resolved-render-payload) | Yes |  |
| `TemplatePreviewResponse.stats` | object | Yes |  |


### 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/)
