# Zvid complete documentation Content revision: 6338386a1dd87e4a --- Canonical URL: https://docs.zvid.io/docs/ai-assistants/ Markdown URL: https://docs.zvid.io/markdown/docs/ai-assistants.md # Use Zvid with ChatGPT and AI assistants An assistant can help you write Zvid JSON using these public docs. To inspect your account, save a draft or render media, connect the assistant to Zvid's authenticated **MCP server**: ```text https://mcp.zvid.io/mcp ``` Reading the website does not connect an account or start a render. If you only want JSON, give the assistant the [documentation index](https://docs.zvid.io/docs-index.md), [project reference](https://docs.zvid.io/docs/structure/) and [validation guide](https://docs.zvid.io/docs/validate-and-estimate/). Run the resulting request from your own server. ## Connect ChatGPT You need a Zvid account and a ChatGPT account/workspace that allows custom MCP connections. Availability and administration settings can differ. 1. In ChatGPT, open **Settings → Security and login** and enable **Developer mode**, if available. 2. Open **Plugins**, use the add connection action, and enter `https://mcp.zvid.io/mcp` as the server URL. 3. Choose OAuth and complete the Zvid sign-in and consent screen. Do not paste an API key into a chat message. 4. Start with: **“Use Zvid to show my account and available tools. Do not render anything.”** These ChatGPT steps were checked on 22 September 2026. Follow [OpenAI's connection instructions](https://developers.openai.com/api/docs/mcp#connect-in-chatgpt) if the labels differ in your client. A workspace administrator may control access to custom connections. ## Connect Codex Codex is a separate client. Register the hosted server and authenticate: ```bash codex mcp add zvid --url https://mcp.zvid.io/mcp codex mcp login zvid ``` In a client that exposes MCP settings instead of a command line, enter the same server URL and complete OAuth there. See [Codex MCP configuration](https://learn.chatgpt.com/docs/extend/mcp) for client-specific options. ## Other MCP clients and n8n Use a client that supports Streamable HTTP and OAuth. Zvid uses authorization code with PKCE and the `zvid:mcp` scope. For clients without OAuth, the hosted endpoint accepts an API key in the `x-api-key` header. Keep credentials in the client's credential settings. In n8n, use the built-in **MCP Client Tool** against the hosted endpoint with an **MCP OAuth2 API** credential. The separate Zvid action and trigger nodes use API-key credentials; see [Integrations](https://docs.zvid.io/docs/integrations/). ## Choose the capabilities the assistant needs | Profile | Intended use | Capabilities | | --- | --- | --- | | `readonly` | Account and result inspection | `get_account`, `list_media`, `get_media` | | `creator` | Interactive creative work; default | Authoring/schema tools, library discovery, validation, projects/templates, reviewable drafts and approval-aware rendering | | `automation` | Trusted automated workflows | Creator tools plus direct video/image rendering, bulk rendering and webhook tools | | `developer` | API development | All registered tools; globally disabled operations remain unavailable | The client discovers the actual available tools when it connects. A tool mentioned in another profile is not necessarily available in yours. Update/delete tools are disabled on the hosted MCP surface even where REST supports the underlying operation. Your dashboard stores the default profile and credit ceiling. A connection can request a concrete profile and limit: ```text https://mcp.zvid.io/mcp?profile=creator&maxRenderCredits=60 ``` The dashboard ceiling still applies; requesting a higher value in the URL does not bypass it. Connection settings cannot be changed by the model inside a conversation. The fallback per-render ceiling is 120 credits; actual account settings may lower it. MCP bulk operations have their own item ceiling, normally 25, in addition to account limits. ## From a brief to a finished video 1. **Describe the outcome.** Give the aspect ratio, target duration, audience, text, brand colors and any assets you own. 2. **Inspect an example.** The assistant can use `plan_creative_video`, `find_matching_examples` and `start_from_example` to find a suitable starting point. It can also discover reusable creative assets and search Zvid's stock library. 3. **Check the current contract.** Use `get_project_schema` and the relevant `get_element_docs`. If the schema reports `live: false`, it is a bundled fallback; use remote validation before rendering. 4. **Validate the exact project.** Use `validate_project_json` with `remote: true`. Resolve errors and inspect layout warnings. Mechanical repairs from `repair_project_json` still need review and validation. 5. **Save a draft.** In Creator, `create_media` or the matching template/example draft tool saves a reviewable draft. Creating a draft does not render media or spend render credits. `create_media` and `revise_media` require the complete project payload. 6. **Review and approve.** Check the draft and quote. `render_media` requires the approved quote token; changing the project or allowing the quote to expire requires a fresh quote. The default quote lifetime is 15 minutes; use the returned expiry time for the actual deadline. 7. **Track the result.** Inspect `get_media` or the returned render job with `get_render`. Deliver the media link only when completion is confirmed. Report a failed job instead of presenting a draft as finished media. Automation and Developer can expose direct render tools that spend credits without the Creator draft/quote flow. Direct REST rendering also submits immediately. Select the interface and approval behavior appropriate to your workflow. ## A useful first request > Create a draft for a 15-second vertical product video. Use my supplied product image and these three benefits. Find a suitable example, validate the exact project against my account limits, and show the draft and estimated credits. Wait for my approval before rendering. For a reusable template, ask the assistant to declare variable defaults, save the template and use the free template preview to inspect the resolved project. Preview validates data; it does not produce a video preview. See [Template basics](https://docs.zvid.io/docs/templates/template-basics/). ## When something goes wrong | Symptom | Action | | --- | --- | | No Zvid tools appear | Check the exact endpoint, connection status and selected profile; reconnect after changing connection settings | | OAuth fails | Complete sign-in again and confirm your workspace permits the connection; use [authentication troubleshooting](https://docs.zvid.io/docs/authentication/) | | Validation fails | Correct the returned field paths and account limits; avoid repeated paid submissions | | Quote expired or project changed | Review a fresh draft/quote before approving | | Credit ceiling reached | Reduce the requested work or deliberately change connection/account settings | | Render failed | Inspect the returned reason and [render troubleshooting](https://docs.zvid.io/docs/operations/errors-and-retries/) | For precise machine-readable context, use [Documentation resources](https://docs.zvid.io/docs/documentation-resources/). For account help, contact us at [https://zvid.io/contact](https://zvid.io/contact). --- Canonical URL: https://docs.zvid.io/docs/authentication/ Markdown URL: https://docs.zvid.io/markdown/docs/authentication.md # Authentication Zvid has two authentication methods. Which one you use depends on how you connect: | Method | Use it for | | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | | **API key** (`zvid_…` in the `x-api-key` header) | Direct REST API calls, SDKs, server-to-server automations, and the Zvid n8n action and trigger nodes | | **OAuth — sign in with your Zvid account** | AI agents connecting through the hosted MCP endpoint `https://mcp.zvid.io/mcp` — Claude Code, Codex, and the one-click n8n AI-agent workflow | ## Method 1 — API keys Programmatic Zvid API access uses API keys. Include your key in the `x-api-key` header on API-key compatible endpoints such as render submission, job lookup, profile lookup, credits, and API-key management. ### Creating an API Key Create API keys on the [API Keys page](https://app.zvid.io/api-keys) of the Zvid dashboard: 1. Log in to your Zvid account at [app.zvid.io](https://app.zvid.io). 2. Open **API Keys**. 3. Click **Create API Key**. 4. Enter a descriptive name, such as `Production Server`. 5. Copy the generated key immediately. > **Important** The full API key is shown only once. If you lose it, revoke the old key and create a new one. ### Using API Keys Send the API key in the `x-api-key` header: ```bash curl -X GET https://api.zvid.io/api/user/profile \ -H "x-api-key: zvid_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" ``` ```javascript const response = await fetch("https://api.zvid.io/api/user/profile", { headers: { "x-api-key": "zvid_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", }, }); const data = await response.json(); console.log(data); ``` ### API Key Format Zvid API keys use this format: ```text zvid_<64-character-hexadecimal-string> ``` - `zvid_`: Zvid production key prefix. - `64-character-hexadecimal-string`: Secret key material using `a-f` and `0-9`. ### Security Recommendations - Store API keys in server-side environment variables or a secrets manager. - Do not expose API keys in browser code, mobile apps, public repos, logs, or support screenshots. - Use separate keys for production and development. - Revoke keys that are unused or suspected to be exposed. ## Method 2 — OAuth sign-in for AI agents (MCP) AI agents connect to Zvid through the hosted MCP endpoint `https://mcp.zvid.io/mcp`. Instead of pasting an API key, you sign in with your Zvid account: the endpoint publishes OAuth discovery metadata, uses authorization code + PKCE with the single scope `zvid:mcp`, and issues short-lived access tokens with rotating refresh tokens. The client can store a refresh credential to renew access without another sign-in. Revoke access or disconnect the client when it is no longer needed. This is the method used by: - **Claude Code** — `claude mcp add --transport http zvid https://mcp.zvid.io/mcp`, then `claude mcp login zvid`. - **OpenAI Codex** — register `https://mcp.zvid.io/mcp` with `auth = "oauth"`, then authenticate from MCP settings. - **n8n AI-agent workflow** — the **Zvid MCP Tools** node uses n8n's built-in **MCP OAuth2 API** credential; you sign in from the node, with dynamic client registration enabled. Two API-key fallbacks exist for MCP: the hosted endpoint also accepts an API key in the `X-Api-Key` header for clients that cannot run an OAuth flow, and the local/self-hosted stdio server (`@zvid/mcp` on npm) is API-key only. ### Which method does my n8n workflow use? - **Zvid** action node and **Zvid Trigger** → the **Zvid API** credential with an API key (method 1). - **Zvid MCP Tools** node in the AI-agent workflow → n8n's built-in **MCP OAuth2 API** credential with OAuth sign-in (method 2). A workflow that combines both needs both credentials. ## Need Help? For setup and the validate → estimate → render workflow in an AI client, see [AI assistants](https://docs.zvid.io/docs/ai-assistants/). For supported integration choices, see [Integrations](https://docs.zvid.io/docs/integrations/). If authentication fails: 1. **API key:** verify the key value is correct, confirm the header name is exactly `x-api-key`, and check that the key has not been revoked. 2. **OAuth:** sign out and reconnect from your MCP client to obtain a fresh token, and confirm the endpoint is exactly `https://mcp.zvid.io/mcp`. 3. Contact us at [https://zvid.io/contact](https://zvid.io/contact). --- Canonical URL: https://docs.zvid.io/docs/automation/bulk-rendering/ Markdown URL: https://docs.zvid.io/markdown/docs/automation/bulk-rendering.md # Bulk Rendering Bulk rendering submits **one template + many variable sets** in a single request — Zvid fans it out into individual render jobs, each with its own credits, status, and output. It's the scale half of [templates](https://docs.zvid.io/docs/templates/template-basics/): personalized videos from a CRM export, one video per product, per city, per employee. ## Submitting a batch ```bash curl -X POST https://api.zvid.io/api/render/bulk/api-key \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template": "tpl_xxxxxxxxxxxxxxxxxxxx", "name": "spring-campaign", "items": [ { "variables": { "firstName": "Amira", "city": "Cairo" }, "name": "spring-amira" }, { "variables": { "firstName": "Omar", "city": "Alexandria" } }, { "variables": { "firstName": "Lina", "city": "Giza" } } ], "webhookUrl": "https://example.com/hooks/bulk-done" }' ``` Envelope fields: | Field | Required | Notes | | --------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------- | | `template` **or** `payload` | one of them | A stored `tpl_…` id, or a full inline project (with `variables` defaults). | | `items` | yes | 1–500 entries; each is `{ variables, name? }`. Your plan's `maxBulkItems` may be lower — validation errors report it. | | `variables` | no | Batch-level values merged under every item's variables. | | `overrides` | no | Output knobs applied to every job (`name`, dimensions, format, …). | | `name` | no | Batch name shown in the dashboard. | | `webhookUrl` | no | Unsigned [per-request callback](https://docs.zvid.io/docs/automation/webhooks/) for each child's completion or failure; not one batch event. | ## Best-effort validation Items are validated **individually**. Valid items become jobs immediately; invalid ones are returned in `errors` — each entry carries the item's original request index as `item` (0-based) plus field-level `details` — and the batch is only rejected outright when the envelope is malformed or _every_ item fails: ```json { "bulkId": "blk_…", "queued": true, "totalJobs": 2, "rejectedItems": 1, "errors": [ { "item": 1, "error": "Validation failed", "details": [ { "field": "payload.duration", "message": "Duration must be at least 0.1 seconds" } ] } ], "failedToQueue": 0, "creditsReserved": 12, "clientRoom": "api:42:7", "queueAhead": 0, "jobs": [ { "jobId": "550e8400-…", "index": 0, "name": "spring-amira", "creditsReserved": 6 }, { "jobId": "6ba7b810-…", "index": 2, "name": null, "creditsReserved": 6 } ] } ``` `jobs[].index` is each queued job's original position in `items`, so you can line results back up with your source rows even when some items were rejected. Fix the failed rows and resubmit just those — the [dashboard's bulk page](https://docs.zvid.io/docs/dashboard/templates-and-bulk/) has a fix-and-resubmit flow and CSV export for exactly this. ## Credits Video jobs reserve credits individually **before they are queued**, then refund failed work. Image batches reserve one credit per accepted image at the batch level and reconcile failed child jobs when the batch finishes. See [Credits, Plans & Limits](https://docs.zvid.io/docs/credits-and-plans/). Neither validation nor template preview reserves credits. Before submitting a batch, validate representative datasets with `POST /api/render/validate/api-key`. This checks one resolved request at a time; it is not a bulk dry-run endpoint. Split larger imports into batches within your account's `maxBulkItems` and the 500-item hard limit. ## Tracking a batch | Endpoint | Auth | Purpose | | --------------------------- | -------------- | ------------------------------------------------- | | `GET /api/render/bulk` | JWT or API key | List your bulk batches | | `GET /api/render/bulk/{id}` | JWT or API key | Batch status with per-item job states and results | Individual jobs are also visible through the normal `GET /api/jobs/{id}` endpoint, and each fires [webhook events](https://docs.zvid.io/docs/automation/webhooks/) on completion. Keep `bulkId` and every returned `jobs[].jobId`. Separate rejected items from accepted jobs that later fail. A timeout after submission does not establish that nothing was queued: inspect the saved batch/job identifiers before retrying. See [Errors and retries](https://docs.zvid.io/docs/operations/errors-and-retries/). ## Bulk images `POST /api/render/image/bulk` (and `/image/bulk/api-key`) is the same contract with `type: "image"` enforced — useful for thumbnail sets and social-card batches. Images cost **1 credit per image**: 25 images cost 25 credits. Credits are reserved once per batch, with 1 credit refunded for each failed image after the batch finishes. ## Related - [Template basics](https://docs.zvid.io/docs/templates/template-basics/) - [Dynamic content](https://docs.zvid.io/docs/templates/dynamic-content/) - [Bulk renders in the dashboard](https://docs.zvid.io/docs/dashboard/templates-and-bulk/) --- Canonical URL: https://docs.zvid.io/docs/automation/webhooks/ Markdown URL: https://docs.zvid.io/markdown/docs/automation/webhooks.md # Webhooks Zvid sends `render.completed` and `render.failed` events to public HTTPS endpoints. Use a registered webhook for signed account-wide notifications, or pass an unsigned per-request `webhookUrl` for individual jobs. | Delivery type | Scope | Signature | | ------------------------ | ---------------------------------------------------------- | --------------------------------------- | | Registered webhook | Every matching event for your account | HMAC-SHA256 using the endpoint's secret | | Per-request `webhookUrl` | The submitted job; one notification per child job for bulk | Unsigned; no `X-Zvid-Signature` header | Both can be used together, so the same job can produce more than one delivery. A failed notification does not change a completed render's status. ## Registering a webhook This is a complete request. Replace the example receiver URL with your endpoint: ```bash curl -X POST https://api.zvid.io/api/webhooks \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hooks/zvid", "events": ["render.completed", "render.failed"] }' ``` The response is the created endpoint object. Store its `secret` on your server for signature verification. All management routes accept an API key or a dashboard JWT; use an API key for server-to-server integration. | Endpoint | Purpose | | ----------------------------------- | -------------------------------------------------------------------------- | | `GET /api/webhooks` | List: `{ webhooks, usage }` | | `POST /api/webhooks` | Create: endpoint object, including `secret` | | `GET /api/webhooks/{id}` | Details: `{ webhook }` | | `PUT /api/webhooks/{id}` | Update `url`, `description`, `events`, or `status` (`active` / `disabled`) | | `DELETE /api/webhooks/{id}` | Delete: HTTP 200, `{ deleted: true }` | | `GET /api/webhooks/{id}/deliveries` | Delivery log: `{ deliveries }` | | `POST /api/webhooks/{id}/test` | Queue a sample event: `{ queued: true, deliveryId }` | URLs must resolve to public addresses. Hosted production requires HTTPS; local/private hosts and redirects are not supported. Endpoint counts are plan-limited; Free permits one. See the [dashboard guide](https://docs.zvid.io/docs/dashboard/webhooks/) to manage them visually. ## The delivery Each delivery is a JSON `POST` with these headers: | Header | Value | | -------------------- | ----------------------------------------------------- | | `X-Zvid-Event` | `render.completed` or `render.failed` | | `X-Zvid-Delivery-Id` | Delivery identifier; stable across automatic attempts | | `X-Zvid-Timestamp` | Unix time in seconds when this attempt was signed | | `X-Zvid-Job-Id` | Render job ID | | `X-Zvid-Signature` | Registered webhooks only: `sha256=` | The body is an **event envelope**, not the response from `GET /api/jobs/{id}`. A representative completion event is: ```json { "event": "render.completed", "jobId": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-09-22T10:00:00.000Z", "data": { "status": "completed", "url": "https://cdn.zvid.io/videos/example.mp4", "thumbnailUrl": "https://cdn.zvid.io/images/example.jpg", "duration": 10, "size": 3848989, "creditsCharged": 10, "templateId": null } } ``` Read the output URL at `data.url`. The body's ISO `timestamp` is the event creation time; the signature uses the **header's** Unix timestamp. A failure has `event: "render.failed"` and `data.status`, `data.error`, and `data.creditsCharged`. Test deliveries additionally carry `test: true` and use sample output data; do not treat them as production renders. ## Verifying the signature Compute `HMAC-SHA256(secret, "
.")`. Preserve the received bytes: parsing and re-serializing JSON can change the signature. This complete Node.js receiver requires Express and a server-side `ZVID_WEBHOOK_SECRET` environment variable: ```javascript const express = require("express"); const crypto = require("node:crypto"); const app = express(); const secret = process.env.ZVID_WEBHOOK_SECRET; if (!secret) throw new Error("Set ZVID_WEBHOOK_SECRET"); // Mount this route BEFORE any app.use(express.json()). app.post( "/hooks/zvid", express.raw({ type: "application/json", limit: "1mb" }), (req, res) => { const timestamp = req.get("x-zvid-timestamp") || ""; const signature = req.get("x-zvid-signature") || ""; const seconds = Number(timestamp); if ( !/^\d+$/.test(timestamp) || !Number.isSafeInteger(seconds) || Math.abs(Date.now() / 1000 - seconds) > 300 || !/^sha256=[a-f0-9]{64}$/i.test(signature) || !Buffer.isBuffer(req.body) ) { return res.sendStatus(401); } const expected = crypto .createHmac("sha256", secret) .update(timestamp + ".") .update(req.body) .digest(); const received = Buffer.from(signature.slice(7), "hex"); if ( received.length !== expected.length || !crypto.timingSafeEqual(received, expected) ) { return res.sendStatus(401); } let event; try { event = JSON.parse(req.body.toString("utf8")); } catch { return res.sendStatus(400); } // Demonstration only: production receivers must persist/enqueue the event // durably, deduplicated by x-zvid-delivery-id, before acknowledging it. console.log(event.event, event.jobId); return res.sendStatus(204); }, ); app.listen(3000); ``` The five-minute age check is a receiver policy for replay protection; synchronize your server's clock. Reject absent or malformed signatures on the signed route. The receiver intentionally rejects unsigned per-request callbacks. ## Retries and duplicate handling Zvid allows **five total attempts**, including the first. Retry delays are 30 seconds, 60 seconds, 2 minutes, and 4 minutes; scheduling can add delay. Each attempt times out after 10 seconds. Any non-2xx response or transport failure counts as a failed attempt. Redirects are not followed. A successful side effect followed by a lost response can lead to a duplicate. Persist `X-Zvid-Delivery-Id` with your work, and deduplicate repeated attempts. If you configure multiple endpoints or both delivery types, also make your business action idempotent for the job and event. Acknowledge only after durable acceptance, then perform longer work asynchronously. After 20 consecutive deliveries exhaust their retries, a registered endpoint is disabled. Inspect its delivery log, fix the receiver, and re-enable it. See [Errors and retries](https://docs.zvid.io/docs/operations/errors-and-retries/). ## Per-request `webhookUrl` This complete render request creates a one-second video and requests completion or failure notification at the supplied URL: ```bash curl -X POST https://api.zvid.io/api/render/api-key \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payload": { "duration": 1, "visuals": [{ "type": "TEXT", "text": "Hello", "position": "center-center" }] }, "webhookUrl": "https://example.com/hooks/render-done" }' ``` Per-request callbacks use the same event envelope and retry schedule, but they are **unsigned**. Treat them as a notification hint and retrieve the job using your own API key before trusting the output or performing an action. Prefer a registered signed webhook when you need authenticated delivery. On [bulk submissions](https://docs.zvid.io/docs/automation/bulk-rendering/), the callback fires per child job, not once for the entire batch. Track aggregate progress with the bulk endpoint. ## Related - [Webhooks in the dashboard](https://docs.zvid.io/docs/dashboard/webhooks/) - [Bulk rendering](https://docs.zvid.io/docs/automation/bulk-rendering/) - [Render lifecycle](https://docs.zvid.io/docs/operations/render-lifecycle/) --- Canonical URL: https://docs.zvid.io/docs/changes-and-compatibility/ Markdown URL: https://docs.zvid.io/markdown/docs/changes-and-compatibility.md # Documentation changes and compatibility The website, OpenAPI reference, Markdown exports and LLM index are generated together. The [documentation index](https://docs.zvid.io/docs-index.json) includes a content revision that identifies the documentation snapshot. It is not an API version or a guarantee that every deployment has identical capabilities. ## Documentation update prepared 22 September 2026 This update corrects the documented contract and adds readable reference content; it does not announce a new runtime release. - API endpoint bodies include authentication, fields, constraints, examples and responses in the initial HTML. - Authoring/validation and template management endpoints are included in the API reference. - Template preview is documented as a free dry-run returning resolved project JSON and stats. - Webhook examples use the event envelope, distinguish registered signed deliveries from unsigned per-request callbacks, and explain raw-body verification. - Separate schemas describe authored projects, resolved projects and REST request envelopes. - All guides have portable Markdown, with a complete index and a full-text export. - Assistant setup, validation/cost checks, core concepts, task recipes and production error handling have dedicated guides. ## Hosted API and editor differences The editor can represent options that a hosted render route does not accept. Exported JSON must be checked against the current API contract and your account limits. In particular, the hosted request frame-rate ceiling is 60 fps even where editor controls allow higher values. See [editor export](https://docs.zvid.io/docs/editor/export/). Some transition names can appear in one renderer or validation layer without being available in all interfaces. Use the supported intersection documented in [transition effects](https://docs.zvid.io/docs/structure/properties/xfade-effects/) and validate the exact project. ## Authoring is a separate validation stage Template expressions and iteration belong to an authored project. The resolved-project schema describes the result after data resolution. A resolved-only schema can incorrectly reject a valid authored template, while an authoring schema cannot prove that every future variable set will resolve successfully. See [schema resources](https://docs.zvid.io/docs/documentation-resources/). For account-specific restrictions and the current credit estimate, [validate the exact request](https://docs.zvid.io/docs/validate-and-estimate/). Limits, installed integration versions and deployed services can change independently of a cached documentation page. ## Updating an integration Keep the installed SDK or automation package version with your release record. Re-run validation on representative requests when changing templates, media inputs, dimensions, output types or dependency versions. Check [integration availability](https://docs.zvid.io/docs/integrations/), preserve returned job identifiers, and review [retry behavior](https://docs.zvid.io/docs/operations/errors-and-retries/) before enabling automatic retries. If a response disagrees with the reference, retain its status, timestamp and job ID when available, then contact us at [https://zvid.io/contact](https://zvid.io/contact) to send a redacted example. Do not guess an unsupported endpoint or silently discard the discrepancy. --- Canonical URL: https://docs.zvid.io/docs/concepts/glossary/ Markdown URL: https://docs.zvid.io/markdown/docs/concepts/glossary.md # Projects, requests, templates and jobs A Zvid integration turns a **project** into an output through a **render job**. Keep the project data, submission request and job result separate in your code. | Term | Meaning | Where to find it | | --- | --- | --- | | Project | The composition: output size, visual elements, audio, scenes and subtitles | The `payload` field of a render request; [project reference](https://docs.zvid.io/docs/structure/) | | Element | One image, text block, clip or other item | A `visuals` or `audios` array, or subtitle configuration | | Scene | A sequential segment with its own local timeline | `payload.scenes[]`; [scene reference](https://docs.zvid.io/docs/structure/scenes/) | | Request envelope | The object POSTed to the render API | `payload` **or** `template`, plus variables, overrides and supported request options | | Template | A saved reusable project with variable defaults | An owned `tpl_…` ID; [template guide](https://docs.zvid.io/docs/templates/template-basics/) | | Variables | Values substituted into a project | Defaults inside the project; request-time values in the envelope | | Override | A supported output setting applied for a submission | Envelope `overrides`, not an arbitrary deep patch of an element | | Preview | For template API calls, free resolution and validation | Returns a resolved project and stats, not rendered media | | Render job | One asynchronous operation producing media | Submission `jobId`; lookup `GET /api/jobs/{id}` | | Bulk batch | A group of individual render jobs | One design with many variable sets; [bulk guide](https://docs.zvid.io/docs/automation/bulk-rendering/) | | Editor draft | A saved, editable composition | A `prj_…` project and an editor link | | Output | Completed video or image | A completed job's result URL | ## Choose a route - **One-off JSON:** send a complete project under `payload`. - **Reusable design:** create a template, preview its variables, then render its `template` ID with the desired values. - **Many personalized outputs:** use bulk rendering within the account's batch limit. - **Interactive AI creation:** connect the [MCP server](https://docs.zvid.io/docs/ai-assistants/) and use the selected profile's draft/approval workflow. - **Visual authoring:** build in [the editor](https://docs.zvid.io/docs/editor/overview/), then validate against the hosted API before automating the exported JSON. ## Read an example at the right level An **element fragment** describes one item. Place it in the named array of a project. A **project payload** describes the composition but still needs the `payload` request wrapper. A **complete request** includes that wrapper and can be submitted once credentials and referenced assets are configured. For a complete first request and rendered output, follow [Quick Start](https://docs.zvid.io/docs/quick-start/). Then read [layout](https://docs.zvid.io/docs/concepts/layout/), [timing](https://docs.zvid.io/docs/concepts/timing/), and [validation](https://docs.zvid.io/docs/validate-and-estimate/) before combining features. --- Canonical URL: https://docs.zvid.io/docs/concepts/layout/ Markdown URL: https://docs.zvid.io/markdown/docs/concepts/layout.md # Coordinates, sizing and layers Zvid places visual elements on a pixel canvas. Output dimensions establish the coordinate space; each element then has a position, anchor and content size. Use [the common-property reference](https://docs.zvid.io/docs/structure/common-properties/) for the exact supported fields by element type. ## Dimensions and resolution Use a [resolution preset](https://docs.zvid.io/docs/structure/properties/resolution-presets/) for a common output size, or use `resolution: "custom"` with explicit `width` and `height`. A preset can replace the dimensions you supply. When you override a template's width/height without choosing another preset, the submission pipeline switches the output resolution to custom. Dimensions and coordinates are pixels. Changing the canvas size does not automatically redesign the typography or spacing of a composition. Inspect the resulting layout, especially when changing aspect ratio. ## Position and anchor are different | Field | Purpose | | --- | --- | | `position` | A preset such as `center-center`, or `custom` for explicit coordinates | | `x`, `y` | The location used for custom placement | | `anchor` | Which point of the element sits at the location; also affects rotation/scaling origin | | `width`, `height` | Element box or media size, depending on element type | A non-custom position preset computes the location and takes precedence over `x`/`y` and `anchor`. For an exact margin, select `position: "custom"` and supply coordinates and an explicit anchor. With `anchor: "center-center"`, the coordinates name the element's center; with `top-left`, they name its top-left corner. See the complete demonstrations in [Position](https://docs.zvid.io/docs/structure/properties/position/), [Anchor](https://docs.zvid.io/docs/structure/properties/anchor/), and [Text and HTML](https://docs.zvid.io/docs/structure/text-elements/). ## Fit media into a box - **`contain`** preserves all the source content and can leave unused space. - **`cover`** fills the destination area and can crop source edges. These are the two accepted `resize` values; `stretch` is not a supported value. With explicit `width` and `height`, resizing fits the source into that element box. Without a box, it fits the source against the project canvas. Use the accepted field values and paired examples in [Resize](https://docs.zvid.io/docs/structure/properties/resize/). [Crop parameters](https://docs.zvid.io/docs/structure/properties/crop-params/) select source content; resizing determines how that content occupies the destination. Check the support matrix before applying image-specific properties to another element type. ## Keep text readable Set the text box size deliberately, especially for variable content. Use [text fitting](https://docs.zvid.io/docs/structure/text-elements/) when labels vary in length, and validate the longest expected values. HTML lets a single text element own related text and its background, which helps keep alignment consistent. When text sits over media, use sufficient contrast and a background/scrim where needed. Review the rendered example and layout warnings; structural validation does not prove that text is readable or fits inside a design. ## Layer ordering Use the element's `track` to express stacking and consult [common properties](https://docs.zvid.io/docs/structure/common-properties/) for ordering behavior. Avoid depending on accidental ordering across separately rendered scene and global layers. In a scene-based video, project-level elements form the global overlay layer described in [Scenes](https://docs.zvid.io/docs/structure/scenes/). ## Diagnose a misplaced element 1. Confirm the resolved canvas dimensions, including preset effects. 2. Check whether a position preset is overriding your coordinates. 3. Inspect the anchor and destination box dimensions. 4. Check crop/resize behavior and the actual source aspect ratio. 5. Check track ordering, scene membership and timing. 6. Run [validation](https://docs.zvid.io/docs/validate-and-estimate/), inspect layout warnings and review a rendered result. --- Canonical URL: https://docs.zvid.io/docs/concepts/media-assets/ Markdown URL: https://docs.zvid.io/markdown/docs/concepts/media-assets.md # Media URLs, uploads and output files The hosted renderer downloads the media referenced by your project. A URL that works in your signed-in browser may still fail when the render worker requests it without your cookies. ## Source requirements - Use an absolute public HTTP or HTTPS URL that returns the media file itself. - Keep the URL accessible until the queued render has finished. Short-lived signed source links can expire while a job waits. - Do not use a local file path, browser `blob:` URL, login page, or private-network address. - Do not embed credentials in a URL. The worker rejects private/internal addresses and unsafe URL forms. - Respect your plan's source size, duration, resolution and element limits. A small crop does not make an oversized original source exempt from input limits. Validation checks the request structure and account limits. Download failures, actual media decoding, or source changes can still cause a later render failure. ## Upload your own media Use [Create upload](https://docs.zvid.io/docs/endpoints/create-upload/) with a server-side API key. The returned `upload` object describes the uploaded file; use its returned URL in the relevant element's `src`. Do not invent a CDN path from a filename. The editor's [media library](https://docs.zvid.io/docs/editor/media/) provides a visual upload workflow. List and inspect your uploads through the API or dashboard before deleting assets used by active projects and templates. ## Use Zvid's stock library Choose media returned by Zvid's stock search or by the editor's stock library. Use the render-ready source URL from the selected result, rather than a search result page or thumbnail when a full-resolution source is available. Confirm that the media is appropriate for your intended use. Keep the selected source and its relevant metadata with your project. A library search can return different results over time; saving only the original search phrase is not enough to reproduce a composition. ## Diagnose a source failure | Failure | Check | | --- | --- | | Authentication required or link expired | Replace the source with a directly accessible media URL or upload it to Zvid | | Private/unsafe host rejected | Use a public media host; `localhost` on your computer is not reachable as your local filesystem from the renderer | | File too large or source too long | Check the source itself against the active plan limits, then reduce it or select another source | | Invalid media or decoding failed | Verify the response is a real supported media file rather than HTML or an error page | | Wrong dimensions or crop | Check the original media size, crop and [resize settings](https://docs.zvid.io/docs/concepts/layout/) | Record the job ID and returned failure reason when requesting help. Never include an API key in a support message. ## Completed output A completed job returns a media URL. The normal queue response has an object at `result` with `url`; a database-backed job response can return the URL as a string. See [Render lifecycle](https://docs.zvid.io/docs/operations/render-lifecycle/) for a helper that handles both. Persist the job ID and output URL in your application. Download a copy to your own storage when your workflow requires independent retention. The API response does not promise a universal expiration or permanent retention period, and deleting a render can remove its stored output. Do not infer a retention guarantee from the current URL's shape. Use [webhooks](https://docs.zvid.io/docs/automation/webhooks/) or bounded polling to begin downstream processing only after the job has completed. --- Canonical URL: https://docs.zvid.io/docs/concepts/timing/ Markdown URL: https://docs.zvid.io/markdown/docs/concepts/timing.md # Timing, duration and scenes Three different clocks appear in a video project. Use seconds for timing values unless a field's reference explicitly says otherwise. | Clock | What zero means | Examples | | --- | --- | --- | | Project time | Start of the output video | Project-level visual/audio placement and subtitles | | Scene-local time | Start of the containing scene | A visual inside `scenes[1]` starts relative to that scene, not the beginning of the entire output | | Source time | Start of the input media file | `videoBegin`/`videoEnd` and `audioBegin`/`audioEnd` select source content | Trimming source media and moving an element on the output timeline are separate actions. A source segment beginning at 20 seconds can still appear at time zero of a scene. ## Visual and audio placement For visual elements, `enterBegin` and `exitEnd` bound visibility; `enterEnd` and `exitBegin` define the entrance/exit animation intervals. Keep the intervals ordered. See [common element timing](https://docs.zvid.io/docs/structure/common-properties/) and [animations](https://docs.zvid.io/docs/structure/animations/). Audio uses `enter` and `exit` for timeline placement, plus separate source trimming fields. Playback `speed` changes how much output time a selected source segment occupies. For example, ten source seconds played at twice the normal speed occupy five output seconds. Use the supported ranges in [video](https://docs.zvid.io/docs/structure/video-elements/) and [audio](https://docs.zvid.io/docs/structure/audio-elements/). ## Fixed and automatic project duration `durationMode: "fixed"` uses the normal explicit/default project-duration behavior. `durationMode: "auto"` derives duration from the content bounds and scene timeline, with an explicit project duration acting as a minimum. See [project structure](https://docs.zvid.io/docs/structure/) for the exact rules. For hosted Auto-mode requests, give source media a resolvable timing bound: video/GIF needs an explicit end or source bound; audio needs an explicit bound or `matchDuration: true`. Do not assume validation has downloaded and probed every remote source. The renderer's ability to inspect intrinsic media duration is different from the submission validator's ability to prove a request is within account limits. `matchDuration: true` marks an audio track to fit the resolved project/scene span rather than establish the length itself. Its use and interactions with trimming and looping are documented under [audio elements](https://docs.zvid.io/docs/structure/audio-elements/). ## Sequential scenes and overlap Scenes play in array order. Each scene owns a local timeline. A transition overlaps the end of one scene with the beginning of the next; it does not add an extra segment. For explicit scene lengths, the combined length is: ```text sum(scene durations) - sum(actual transition overlaps) ``` For example, three four-second scenes with two one-second overlaps make ten seconds. Each overlap must fit the adjacent scenes. Use [scene transitions](https://docs.zvid.io/docs/structure/scenes/) for placement of `transition` and `transitionDuration` and [transition effects](https://docs.zvid.io/docs/structure/properties/xfade-effects/) for supported names. Scene `duration: -1` requests auto-fit where supported. Elements that can stretch or loop do not necessarily determine a scene's end. Give text/image-only scenes explicit lengths. A default fallback is not a substitute for an intentional timeline. ## Stored templates require explicit scene durations Stored **video templates** must use positive, explicit scene durations. Auto-fit scene duration is not accepted when saving or resolving a stored video template. When iteration repeats a scene, its explicit duration determines each repeated segment's length. Conditions and iteration affect the final scene count and duration before plan limits are checked. Use free [template preview](https://docs.zvid.io/docs/templates/template-basics/) to inspect the resolved project and stats before rendering new data. ## Image projects have no playback timeline Image rendering is a different branch of the project contract. Remove forbidden video timing fields and unsupported media types; do not simply change a video's `type` and submit it. `snapshotTime` can sample supported animated text content, but it does not make VIDEO/GIF elements valid in an image project. Follow [Rendering images](https://docs.zvid.io/docs/rendering-images/). ## Before rendering Check the intended time origin for every field, source bounds after speed changes, transition overlaps, explicit template durations and the resolved output length. [Validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/) the exact request, then use [job tracking](https://docs.zvid.io/docs/operations/render-lifecycle/) to inspect the result. --- Canonical URL: https://docs.zvid.io/docs/credits-and-plans/ Markdown URL: https://docs.zvid.io/markdown/docs/credits-and-plans.md # Credits, Plans & Limits Every render costs **credits**, drawn from your subscription allowance plus any one-time credit packs. Plans also set **limits** — maximum duration, resolution, element counts — that are enforced at submit time. ## What a render costs | Output | Cost | | ---------------------------------------------- | ----------------------------------------------------------------- | | Video with `width × height ≤ 2,073,600` pixels | `ceil(duration × 1)` credits | | Video with `width × height > 2,073,600` pixels | `ceil(duration × 4)` credits | | Image | **1 credit per image**, including bulk renders, at any resolution | `duration` is the resolved output length in seconds. Multiply by the rate, then round the total up to a whole credit. A 10-second 1920×1080 video costs 10 credits; a 2.1-second video above that pixel-area threshold costs 9. Resolution presets resolve before pricing, so `tiktok` (1080×1920) uses the same rate as 1920×1080. Your plan's dimension limits still apply independently. Credits are **reserved** when a job is queued (`creditsReserved` in the submit response) and **refunded automatically** if the render fails or is cancelled. Estimate before submitting with `POST /api/render/validate/api-key` and read `creditsRequired`. [Validation and estimation](https://docs.zvid.io/docs/validate-and-estimate/) are free and do not queue a job or reserve credits. ## Credit pools Your balance has two pools, spent in this order: 1. **Subscription credits** — replenished every billing cycle. 2. **Add-on credits** — one-time [credit packs](https://app.zvid.io/credits) that never expire with the cycle. ```bash curl -X GET https://api.zvid.io/api/credits/balance \ -H "x-api-key: YOUR_API_KEY" ``` ```json { "balance": 1164, "subscriptionCredits": 1064, "addonCredits": { "balance": 100, "totalEarned": 200, "totalSpent": 100 } } ``` `GET /api/credits/transactions` returns the ledger and `GET /api/credits/usage-stats` aggregates usage over 24 h / 7 d / 30 d — both are also visualized on the [dashboard's Credits page](https://docs.zvid.io/docs/dashboard/credits-and-billing/). ## Plan limits Limits scale with your plan. The enforced dimensions: | Limit | Meaning | | ---------------------------------------------------- | --------------------------------------------------------------------------------------- | | `maxDuration` | Output length in seconds | | `maxInputDuration` | Complete video/GIF source length: 7,200 seconds (2 hours), independent of output length | | `maxOutputResolution` | Output width/height in pixels | | `maxVisualElements` | Total visual elements per project | | `maxImagesCount` / `maxVideosCount` / `maxGifsCount` | Per-type element counts | | `maxAudioElements` | Audio tracks | | `maxCaptionElements` | Subtitle cues | | `maxScenes` | Scenes after [`iterate`](https://docs.zvid.io/docs/templates/dynamic-content/) expansion | | `maxIterateItems` | Items per iterated scene | | `maxTemplates` | Stored templates | | `maxBulkItems` | Items per [bulk render](https://docs.zvid.io/docs/automation/bulk-rendering/) | | `maxWebhooks` | Registered [webhooks](https://docs.zvid.io/docs/automation/webhooks/) (Free: 1) | Retrieve your current account-aware limits through the [render schema endpoint](https://docs.zvid.io/docs/validate-and-estimate/). Render validation errors include a `planLimits` object with your plan's active values: ```json { "error": "Validation failed", "details": [ { "field": "payload.duration", "message": "Duration cannot exceed 1800 seconds (30 minutes) based on your Startup plan" } ], "planLimits": { "planName": "Startup", "maxDuration": 1800, "maxOutputResolution": 3820, "maxScenes": 150, "…": "…" } } ``` Current plans and pricing live at [zvid.io/pricing](https://zvid.io/pricing); manage your subscription and buy credit packs from the [dashboard](https://docs.zvid.io/docs/dashboard/credits-and-billing/). ## Related - [Quick Start](https://docs.zvid.io/docs/quick-start/) - [Credits & billing in the dashboard](https://docs.zvid.io/docs/dashboard/credits-and-billing/) - [API Reference — Credits](https://docs.zvid.io/docs/endpoints/get-credit-balance/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/account-password/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/account-password.md # Reset your password Reset your sign-in password through a link sent to your account's email address. ## When to use it Use password reset when you have forgotten your password or need to replace it. The **Settings** page does not contain a password-change form. ## Before you begin You need access to the inbox associated with your Zvid account. ## Request a reset link **Location:** [Sign in](https://app.zvid.io/login) → **Forgot your password?** 1. Click **Forgot your password?** 2. Enter your account's **Email address**. 3. Click **Send reset link**. 4. Check your inbox and spam folder for the reset email. If you entered the wrong address, click **Use a different email** and submit the correct one. For privacy, the request shows a success response whether or not the address belongs to an account. ## Set a new password 1. Open the reset link from the latest email. 2. Enter **New password** and **Confirm new password**. Use at least eight characters with an uppercase letter, a lowercase letter, a number, and a special character from `@$!%*?&`. 3. Click **Update password**. 4. When **Password updated** appears, click **Go to sign in** and use the new password. ## What happens next The new password replaces the previous password. The reset link is single-use and expires after one hour. Requesting another reset link invalidates the earlier one. > **API keys are separate** Changing your password does not delete API keys. If a key has been exposed, [delete that key](https://docs.zvid.io/docs/dashboard/api-keys-delete/) separately. Password reset should not be treated as a way to disconnect every existing application or session. ## Troubleshooting ### The page says “Reset link is invalid” **Cause:** The link is missing its token or is malformed. **Solution:** Click **Request a new reset link** and use the complete link in the new email. ### The password reset fails **Cause:** The token may have expired, been used, or been replaced by a newer request. **Solution:** Request a new link from [Forgot your password?](https://app.zvid.io/forgot-password). If the account is disabled or you cannot access its inbox, contact https://zvid.io/contact. ## Related documentation - [Create an account and sign in](https://docs.zvid.io/docs/dashboard/getting-started-account/) - [Update your profile](https://docs.zvid.io/docs/dashboard/account-settings/) - [Delete or replace an API key](https://docs.zvid.io/docs/dashboard/api-keys-delete/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/account-settings/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/account-settings.md # Update your profile Use **Settings** to update the name shown on your Zvid account and configure your AI agent defaults. ## When to use it Use this page to correct your name, check your membership date, or find the next step for an email or password change. ## Change your name **Location:** **Dashboard → Settings → Profile information**. 1. Select **Settings** in the left sidebar, or open [Settings](https://app.zvid.io/settings). 2. Update **First Name** and **Last Name**. Both fields are required. 3. Click **Save Changes**. The **Profile updated successfully** message confirms that your changes were saved. The profile menu updates with your account information. ## Change your email address **Email Address** is read-only. To request an email change, use the link below the field: https://zvid.io/contact. Do not create a second account as a substitute for changing the email on an existing account; it will have separate resources and credentials. ## Check account details The **Account status** card contains **Email Verified**, **Account Type**, and **Member Since**. > **Check your plan in Subscription** The current Settings interface displays **Free Plan** as a fixed account-type label. To verify your actual paid plan and billing status, open your profile menu and select **Subscription**. ## Configure AI agent tools The **AI agent tools** section below your profile contains MCP tool profiles, a per-render credit ceiling, and **Download n8n Workflow**. Follow [Configure MCP and download an n8n agent](https://docs.zvid.io/docs/dashboard/integrations-mcp-n8n/). ## Available account controls For password recovery, use [Reset your password](https://docs.zvid.io/docs/dashboard/account-password/). To sign out, open the top-bar profile menu and choose **Logout**. The customer dashboard currently has no team-invitation, workspace-switching, role-management, notification-preferences, or account-deletion screen. For an account request you cannot complete in the dashboard, contact https://zvid.io/contact. ## Troubleshooting ### My changes did not save **Cause:** A required name is empty or the update request failed. **Solution:** Enter both names, click **Save Changes**, and wait for the success message. If it fails again, reload Settings before retrying. ## Related documentation - [Create an account and sign in](https://docs.zvid.io/docs/dashboard/getting-started-account/) - [Manage your subscription](https://docs.zvid.io/docs/dashboard/subscription/) - [Configure MCP and n8n](https://docs.zvid.io/docs/dashboard/integrations-mcp-n8n/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/affiliate/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/affiliate.md # Join and use the affiliate program The **Affiliate** page lets you join the referral program and track eligible referral activity, commissions, and payouts. ## When to use it Use this page to obtain your referral link, understand your earnings status, or update the email used for payouts. ## Join the program **Location:** **Dashboard → Affiliate**. 1. Select **Affiliate** in the sidebar, or open [Affiliate](https://app.zvid.io/affiliate). 2. Review the current commission tiers, attribution period, minimum payout, payout method, and refund hold shown on the page. 3. Optionally enter a **Custom code (optional)**. Use 4–32 letters, numbers, or hyphens, with a letter or number at each end. Codes are normalized to lowercase and must be unique. Leave it blank for an automatically generated code. 4. Review the restriction on brand-bidding ads and self-referrals. 5. Click **Join the program**. Your affiliate overview appears after joining. If you see **Your application is under review**, your link starts tracking after approval. A different account-status banner, such as suspension, may prevent participation; follow the message shown. ## Copy and use your referral link 1. Find **Your referral link** on the affiliate overview. 2. Click **Copy link**. 3. Use the copied URL when sharing Zvid. Your code is also shown below the link. The page explains that the `ref` parameter can be used on other `zvid.io` pages. Keep the exact code when adapting a link. ## Track referrals and commissions 1. Review **This month's tier** for current referred revenue, projected commission rate, and progress toward the next tier. 2. Check **Clicks**, **Signups**, **Customers**, and **Payable** for the account summary. **Payable** also shows pending and paid-out amounts. 3. Scroll to **Commissions** to inspect **Date**, **Referral**, **Type**, **Sale**, **Commission**, and **Status**. 4. Click **Next** or **Prev** when more commission records are available. The table shows 10 records per page. 5. Review **Recent referrals**, when present, to see whether a referral has signed up or become a customer. | Commission status | Meaning | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `pending` | Not payable yet. The refund hold or monthly tier settlement may still be outstanding. Hover over the status for provisional-rate or availability details. | | `approved` | Eligible for the payable balance after the applicable hold and settlement. | | `paid` | Included in a recorded payout. | | `void` | Excluded from payable earnings. | Monthly commissions settle at the highest tier reached for that calendar month's referred revenue. The rate shown during the month can therefore be provisional. Use the terms and thresholds displayed in your account rather than assuming an older rate applies. ## Set your payout email **Location:** **Affiliate → Payout settings**. 1. Enter the correct **PayPal email** for your payouts. 2. Click **Save**. 3. Wait for **Payout settings saved**. > **Check the payout address** Payouts use the saved PayPal address. Verify it before saving; changing this field does not reverse a payout already sent. ## Review payouts The **Payouts** section lists the latest 10 recorded payouts, with amounts, dates, methods, and references when available. The page states the current minimum balance and hold period for monthly PayPal payouts. There is no **Request payout** action in the customer dashboard. The page also has no control to rename a referral code or leave the program. For these requests, contact https://zvid.io/contact. ## Troubleshooting ### My custom referral code was rejected **Cause:** The code may not match the format or may already be taken. **Solution:** Use a different valid code, or leave the field empty and let Zvid generate one. ### My link is not tracking **Solution:** Check your affiliate-status banner and use the exact link from **Copy link**. Pending applications start tracking only after approval; suspended accounts require review. Self-referrals and fraudulent activity are not eligible. ### A commission is pending or a payout has not arrived **Solution:** Check its status tooltip, the refund-hold period, whether the calendar month's rate is settled, the minimum payable balance, and your saved **PayPal email**. Review **Payouts** for a recorded payment. For an unresolved status, contact https://zvid.io/contact. ## Related documentation - [Update your account profile](https://docs.zvid.io/docs/dashboard/account-settings/) - [Credits and billing overview](https://docs.zvid.io/docs/dashboard/credits-and-billing/) - [Dashboard overview](https://docs.zvid.io/docs/dashboard/overview/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/api-keys-delete/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/api-keys-delete.md # Delete or replace an API key Deleting an API key revokes its ability to authenticate new requests to Zvid. ## When to use it Delete a key that is unused, lost, or exposed. Replace a key when changing the credential used by an application. ## Before you begin Identify every application or workflow using the key. If you need continuity, create and test a replacement before deleting the old key. If the key is exposed, revoke it promptly. ## Delete a key **Location:** **Dashboard → API Keys → Delete API key**. 1. Open [API Keys](https://app.zvid.io/api-keys) from the sidebar. 2. Find the key by its name and masked prefix. 3. Click the trash icon labeled **Delete API key** on that key's card. 4. Read the browser confirmation, check the key name, and confirm deletion. The **API key deleted successfully** message appears and the card disappears. > **Deletion cannot be undone** Applications using the deleted key will fail to authenticate subsequent requests. The old secret cannot be restored. Deleting a key does not delete the account's projects, templates, or render history. ## Replace a key with minimal interruption 1. [Create a new key](https://docs.zvid.io/docs/dashboard/api-keys/) with a distinct name. 2. Update the saved credential in each application or automation that uses the old key. 3. Verify that those integrations authenticate successfully with the new key. 4. Return to **API Keys** and delete the old key. ## Troubleshooting ### My automation stopped working after deletion **Cause:** It is still sending the revoked key. **Solution:** Update its credential to an active key, then retry the request. If you do not have the full value of another active key, create a new one. ### I cannot find a deleted key The dashboard lists active keys. A removed key is not recoverable from that list. ## Related documentation - [Create an API key](https://docs.zvid.io/docs/dashboard/api-keys/) - [Authentication](https://docs.zvid.io/docs/authentication/) - [Reset your password](https://docs.zvid.io/docs/dashboard/account-password/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/api-keys/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/api-keys.md # Create an API key An API key lets an application or automation authenticate requests to your Zvid account without using your sign-in password. ## When to use it Create a key for a server application, SDK, or Zvid community node in n8n. For an AI client that supports OAuth, you can instead [sign in to Zvid through MCP](https://docs.zvid.io/docs/dashboard/integrations-mcp-n8n/#authorize-an-mcp-client). ## Before you begin Sign in and complete your [account verification and profile](https://docs.zvid.io/docs/dashboard/getting-started-account/). Have a secure place ready to store the key, such as your application's secret settings. ## Create and copy a key **Location:** **Dashboard → API Keys**. 1. Select **API Keys** in the left sidebar, or open [API Keys](https://app.zvid.io/api-keys). 2. Click **Create API key**. ![Zvid API Keys page with the Create API key button.](https://docs.zvid.io/img/dashboard-guide/api-keys-create-button.png) 3. In **Create API Key**, enter an **API Key Name**, such as `Production automation`. Use a different name for each active key. ![Create API Key dialog with an empty API Key Name field, Cancel, and Create API Key.](https://docs.zvid.io/img/dashboard-guide/api-keys-create-dialog.png) 4. Click **Create API Key**. 5. In **API Key Created**, click the copy icon labeled **Copy API key**. 6. Store the complete value in your application's secret settings, then click **Done**. > **Copy the key before closing** The complete `zvid_…` value appears only when the key is created. Afterward, the list displays a masked prefix. You cannot recover a lost secret; create a replacement and [delete the old key](https://docs.zvid.io/docs/dashboard/api-keys-delete/). ## What happens next The key appears as a card with its name, creation date, and masked value. Send the complete key in the `x-api-key` header when calling compatible endpoints at `https://api.zvid.io`. Follow [Authentication](https://docs.zvid.io/docs/authentication/) for request examples, or [Quick Start](https://docs.zvid.io/docs/quick-start/) to submit your first API render. Creating a key does not render media; subsequent render requests use your account's credits and limits. ## Important notes - Store keys in server-side secrets or integration credentials. Do not put them in browser code, public repositories, logs, or screenshots. - The dashboard does not provide a permissions, expiry, rename, or usage-report form for each key. Additional key-management operations are described in the [API reference](https://docs.zvid.io/docs/endpoints/list-api-keys/). - A rate-limit warning can appear on a key card. For the actual response and recovery guidance, use [API error and retry guidance](https://docs.zvid.io/docs/operations/errors-and-retries/) rather than treating the warning as a quota setting. ## Troubleshooting ### A key with this name already exists **Cause:** Another active key uses the same name. **Solution:** Choose a unique name, for example `Production automation September`, and create again. ### The key does not authenticate **Solution:** Confirm that you copied the complete secret rather than its masked prefix, that it has no extra spaces, and that the request sends `x-api-key`. A deleted key cannot be reused. See [Authentication](https://docs.zvid.io/docs/authentication/). ### Copying to the clipboard fails Select and copy the full value directly from **API Key Created** before closing it. If you have already closed it without saving the key, create a replacement. ## Related documentation - [Delete or replace an API key](https://docs.zvid.io/docs/dashboard/api-keys-delete/) - [Add a webhook](https://docs.zvid.io/docs/dashboard/webhooks/) - [Configure MCP and n8n](https://docs.zvid.io/docs/dashboard/integrations-mcp-n8n/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/bulk-renders/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/bulk-renders.md # Create and monitor bulk renders **Bulk renders** creates multiple video or image outputs from one saved template, using one row of variable values per output. ## When to use it Use a batch for product catalogs, personalized messages, campaign variations, or other work that repeats a design with different data. To try one set of values first, use the template's [free preview](https://docs.zvid.io/docs/dashboard/templates/#preview-a-template-for-free). ## Before you begin - Have an active [saved template](https://docs.zvid.io/docs/dashboard/templates/) in your account. - Prepare values for its variables, either manually or in a CSV file. - Have enough credits for the accepted outputs. Rendering reserves credits when you submit the batch. The number of rows allowed in one submission depends on your plan and cannot exceed 500. If a submission exceeds the applicable limit, divide the data into smaller batches. ## Choose a template and batch settings **Navigation: Dashboard → Bulk renders → New bulk render** 1. Select **Bulk renders** in the sidebar. 2. Select **New bulk render**. On an empty list, **Create your first batch** opens the same form. ![New bulk render form showing the Template selector, optional Batch name, and Output format override, with a link to create a template when none are available.](https://docs.zvid.io/img/dashboard-guide/bulk-renders-template-selector.png) 3. Choose the saved design from **Template**. The options show each template's name and version. 4. Optionally enter **Batch name (optional)** to recognize this group later. 5. Leave **Output format override (optional)** set to **Template default**, or choose a named resolution preset for the batch. Selecting a template loads one data row populated with its variable defaults. The form uses the template type to produce videos or images, although several controls currently use the word “video” for both. > **Changing the selected template** Selecting another template clears the data grid and loads that template's defaults. Finish or preserve your data before switching templates. ## Enter data manually 1. In **Data**, optionally enter an output **name** for the first row. 2. Replace the variable values in that row. Each column shows the variable name and type. 3. Select **Add row** to create another row with the saved defaults, or **Duplicate row** to copy an existing row immediately below it. 4. Use **Remove row** to remove an unwanted row before submission. Removing a row changes the current form; it does not cancel an already submitted render. The form has no undo control for removed rows. | Variable type | Entry rule | | --------------- | ------------------------------------------------------------- | | String | Enter the required text, color, or media URL. | | Number | Enter a number; a blank value retains the saved default. | | Boolean | Select or clear the checkbox. | | Array or object | Enter valid JSON in the cell. It is parsed before submission. | A template without variables creates the same content for each row; you can still give the outputs different names. ## Import a CSV file **Navigation: Dashboard → Bulk renders → New bulk render → Select a template → Import CSV** 1. Select **Download CSV template** to download a file with the correct column headers and a sample row. 2. In your spreadsheet editor, replace the sample with your data. Keep one row per output. 3. Save the file as CSV. 4. Select **Import CSV** and choose the file. 5. Check the imported row count and values before rendering. > **Import replaces the grid** Importing a CSV replaces all rows currently in the form. It does not append to them. Keep a copy of any manually entered values you still need. The first CSV row must contain headers, followed by at least one data row. Match template variable names exactly. The optional `name` column names the output; columns not recognized as variables are ignored and listed in the import notification. If a variable column is omitted, its saved default is used. CSV conversion follows these rules: - Numeric values become numbers. Blank numeric cells keep the saved default. - Boolean values `true`, `1`, `yes`, and `y` mean true, ignoring case and surrounding spaces. Other values mean false; use `false` for clarity. - Arrays and objects must contain valid JSON. CSV fields containing commas or quotes need standard CSV quoting; generating the file with a spreadsheet editor is usually simplest. - An empty string cell replaces a string default with empty text. Omitting the column preserves the default. ## Submit the batch 1. Review the template, optional resolution, and number of rows in **Data**. 2. Confirm that each row contains the intended values and that your account has sufficient credits. 3. Select **Render N videos**, where N is the row count. For one row, the label is **Render 1 video**. 4. Read the submission result before attempting to submit again. When all rows are accepted, a confirmation reports the number of queued renders and reserved credits. The dashboard opens the new batch's detail page. Rendering proceeds in the background; returning to another dashboard page does not cancel it. For video batches, credits are reserved for individual jobs. For image batches, credits are reserved for the batch and reconciled after its jobs finish. Check [Credits](https://docs.zvid.io/docs/dashboard/credits-and-billing/) for spending and refunds rather than adding together the item estimates shown on an image batch. ## Correct rejected rows without duplicating accepted work Some rows can pass validation while others fail. In that case, accepted rows are already queued and the form stays open. 1. Read the message stating how many outputs were queued. The batch ID link opens those accepted jobs. 2. Review the **Row N** error messages. 3. Correct the remaining rows in the grid. The form has removed accepted rows and kept only rejected rows. 4. Select **Render N videos** again to submit the corrected rows as another batch. Do not reimport the original complete CSV just to retry validation failures: doing so reintroduces accepted rows and can create duplicate paid outputs. If all rows are rejected, correct the displayed errors before submitting again. ## Monitor a batch **Navigation: Dashboard → Bulk renders → Batch name** 1. Select the batch from **Bulk renders**. 2. Review **Videos** or **Images**, **Completed**, **Failed**, **Pending**, and **Credits** at the top. 3. Inspect each row for its status, dimensions, duration for video, and credit amount. 4. Select **Refresh** to retrieve the latest result immediately. The batch list refreshes processing batches about every five seconds; the detail page refreshes about every four seconds. The dashboard shows the latest 50 batches in the list. Keep a batch's direct link or `blk_…` ID if you need to return to it later. | Batch status | Meaning | | ------------------------- | ----------------------------------------------- | | **Processing** | At least one job is still pending or rendering. | | **Completed** | All outputs completed successfully. | | **Completed with errors** | Some outputs succeeded and others failed. | | **Failed** | The batch's outputs failed. | The progress bar measures finished jobs, including both completed and failed jobs. A batch can reach 100% with errors. The **Credits** summary is the amount reserved for the batch; use the credit transactions to inspect subsequent refunds. ## Open or download completed outputs 1. Open the batch detail page. 2. On a row with an output URL, select **Open video** to open the media in a new tab. 3. Select **Download video** to request a download. These icon titles also appear for image outputs. Depending on your browser and how it handles media links, the download action may open the output instead. You can also find completed jobs in [Renders](https://docs.zvid.io/docs/dashboard/videos/#download-a-video-or-image) and use the **Download** button there. For programmatic result collection, use the [bulk rendering API](https://docs.zvid.io/docs/automation/bulk-rendering/). The dashboard detail page currently provides individual output links, not a CSV export action. ## Inspect failed outputs 1. Open **Bulk renders** and select the affected batch. 2. Find rows marked **failed** and read the error beside their dimensions and credit amount. 3. Correct the source template or affected variable values. 4. Create another bulk render containing only the outputs you need to retry. The detail page has no retry-all or cancel-batch control. A new submission is a separate batch and can spend credits for any outputs that succeed. Keep the failed batch for reference while correcting the next submission. ## Troubleshooting ### No templates are available **Cause:** A batch requires a saved active template in the signed-in account. **Solution:** Follow **create one here** in the form, or open **Templates → New template**. Return to **New bulk render** after creating it. The selector loads up to 50 active templates. ### CSV needs a header row plus at least one data row **Cause:** The imported file did not contain both a header and usable data. **Solution:** Start with **Download CSV template**, retain its header, and add at least one row of values before importing again. ### An imported column was ignored **Cause:** Its header does not exactly match a template variable or the optional `name` field. **Solution:** Compare the header with the downloaded CSV template, correct it, and import the file again. Importing again replaces the current grid. ### Row N contains invalid JSON **Cause:** An array or object cell cannot be parsed. **Solution:** Correct that cell using valid JSON, including double-quoted object keys and strings, then resubmit. A JSON parsing error in the form must be fixed before it can submit the batch. ### Insufficient credits or too many items **Cause:** The accepted outputs need more credits than are available, or the submitted row count exceeds the plan limit. **Solution:** Review [Credits and billing](https://docs.zvid.io/docs/dashboard/credits-and-billing/). Add credits or reduce the batch, and split over-limit data into smaller batches. Read any queued-batch confirmation before resubmitting so that accepted work is not repeated. ### Bulk render not found **Cause:** The batch link may be incorrect or inaccessible to the signed-in account. **Solution:** Open **Bulk renders** in the account that submitted the batch and select it from the list. If the list also fails to load, reload the page before concluding the batch is missing. ## Related documentation - [Create and manage templates](https://docs.zvid.io/docs/dashboard/templates/) - [View and download renders](https://docs.zvid.io/docs/dashboard/videos/) - [Bulk rendering API](https://docs.zvid.io/docs/automation/bulk-rendering/) - [Errors and retries](https://docs.zvid.io/docs/operations/errors-and-retries/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/credits-and-billing/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/credits-and-billing.md # Credits and billing overview Credits pay for rendering; your subscription sets recurring allowances and the limits available to your account. ## Choose a task | Task | Dashboard location | Guide | | ------------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------- | | Check your balance or recent usage | **Credits** in the sidebar | [Understand your credits](https://docs.zvid.io/docs/dashboard/credits/) | | Redeem a credit promo code | **Credits → Have a promo code?** | [Redeem a promo code](https://docs.zvid.io/docs/dashboard/credits/#redeem-a-credit-promo-code) | | Buy a one-time credit pack | **Credits → Get more credits → Buy more credits** | [Buy credits](https://docs.zvid.io/docs/dashboard/subscription/#buy-a-one-time-credit-pack) | | Subscribe, upgrade, downgrade, or change billing period | **Profile menu → Subscription** | [Manage your subscription](https://docs.zvid.io/docs/dashboard/subscription/) | | Stop renewal or undo a scheduled cancellation | **Profile menu → Subscription** | [Cancel a subscription](https://docs.zvid.io/docs/dashboard/subscription-cancel/) | | Resolve a past-due payment | **Subscription → Update payment method** | [Resolve a payment issue](https://docs.zvid.io/docs/dashboard/subscription/#resolve-a-payment-issue) | ## Understand the difference A **render** turns a project into a finished video or image. Its credit cost depends on the output type, video length, and resolution. [Credit calculation](https://docs.zvid.io/docs/credits-and-plans/) explains the formula. Your **plan** also sets limits such as maximum duration, dimensions, and webhook count. Buying extra credits adds to your balance; it does not upgrade these limits. Choose a higher plan if a render fails a plan limit even though you have enough credits. The **Transaction history** on Credits records credit activity. It is not an invoice list. The customer dashboard currently has no separate invoice-history or billing-address editor. For billing requests without a dashboard control, contact https://zvid.io/contact. ## Related documentation - [Understand your credits](https://docs.zvid.io/docs/dashboard/credits/) - [Manage your subscription](https://docs.zvid.io/docs/dashboard/subscription/) - [Credits, plans, and limits](https://docs.zvid.io/docs/credits-and-plans/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/credits/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/credits.md # Check credits and usage The **Credits** page shows how many rendering credits are available and the recent activity that changes your balance. ## When to use it Check Credits before a render or batch, after a purchase, or when a request reports insufficient credits. ## Check your balance **Location:** **Dashboard → Credits**. 1. Select **Credits** in the sidebar, or open [Credits](https://app.zvid.io/credits). 2. Read **Total available credits**. 3. Check the **subscription** and **one-time** amounts below the total. 4. Click **Refresh** to retrieve the latest balance and activity. | Credit pool | How it works | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Subscription | Your recurring allowance. Subscription credits are used first and replenish for the next monthly credit period, including on annually billed plans. | | One-time | Credits from packs and eligible promotions. These are used after subscription credits and do not expire with the subscription cycle. | ## Understand a render's cost | Output | Credit cost | | ----------------------------------------------------- | ------------------------------------------ | | Video at or below 2,073,600 pixels (`width × height`) | 1 credit per second | | Video above 2,073,600 pixels | 4 credits per second | | Still image | 1 credit per image, including bulk renders | Video totals round up to a whole credit. For example, a 10-second 1920×1080 video costs 10 credits. A portrait 1080×1920 video uses the same rate. Plan dimension and duration limits apply separately. Credits are reserved when the render is queued. Failed or cancelled renders receive automatic refunds. See [Credits, plans, and limits](https://docs.zvid.io/docs/credits-and-plans/) for calculation and validation details. ## Review usage and transactions 1. Review **this month**, **today**, **daily average**, and **total used** below the balance. 2. Scroll to **Transaction history** to inspect descriptions, dates, and credit amounts. 3. Click **Refresh** after a render, refund, or purchase to reload the page data. > **Scope of the dashboard history** Usage cards are calculated from a 30-day usage response; **total used** is not a lifetime total. The page shows the latest 50 transactions and has no date filter, pagination, or export control. For a longer ledger or a different API usage timeframe, use the [Credits API reference](https://docs.zvid.io/docs/endpoints/get-credit-balance/). Use each transaction's description and the refreshed balance to understand what happened. Recent reservations and later refunds are separate activity. ## Redeem a credit promo code **Location:** **Dashboard → Credits → Have a promo code?** 1. Enter the code in **Have a promo code?** 2. Click **Redeem**. 3. Wait for the confirmation showing how many credits were added. The balance and history refresh automatically. ![Have a promo code section on the Credits page with a code field and Redeem button.](https://docs.zvid.io/img/dashboard-guide/credits-promo-code.png) Credit promo codes grant credits. A **Discount code (optional)** in Subscription is a separate checkout discount; enter each code in the location specified with its offer. ## Get more credits Click **Get more credits**. On **Subscription**, you can [buy a one-time credit pack or choose a plan](https://docs.zvid.io/docs/dashboard/subscription/). A pack adds credits but does not change your plan's rendering limits. ## Troubleshooting ### I have insufficient credits **Cause:** The available balance is below the render's required credit amount; other queued work may already have reserved credits. **Solution:** Click **Refresh**, review recent activity, and compare the balance with your render estimate. Buy credits, change your plan, or reduce the output duration or resolution before submitting again. ### My promo code was rejected **Cause:** Codes can have expiry dates, eligibility restrictions, and redemption limits. **Solution:** Check the spelling and the offer's terms, confirm it is a credit promo code, and review the error shown after **Redeem**. Do not repeatedly redeem a code that has already succeeded. ### A purchase or refund is not visible **Solution:** Allow the operation to finish, then click **Refresh**. Purchases are applied after payment processing completes. If the balance still does not reflect a completed operation, contact https://zvid.io/contact with its date and render or payment reference; do not send card details or credentials. ## Related documentation - [Manage your subscription](https://docs.zvid.io/docs/dashboard/subscription/) - [Credits and billing overview](https://docs.zvid.io/docs/dashboard/credits-and-billing/) - [Render validation and estimates](https://docs.zvid.io/docs/validate-and-estimate/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/first-video/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/first-video.md # Create and render your first video Create an editable project, then render it into a video file you can watch and download. ## When to use it Follow this walkthrough when you want to make a first video using the dashboard and visual editor. A **project** is your editable design. A **render** is the cloud job that turns that design into a finished file. Saving a project and rendering a video are separate actions. ## Before you begin - [Create an account and complete setup](https://docs.zvid.io/docs/dashboard/getting-started-account/). - Check your available [rendering credits](https://docs.zvid.io/docs/dashboard/credits/). Credits pay for cloud rendering; browser preview does not start a paid render. ## How to create a video **Navigation: Dashboard → Create video → Editor.** 1. Sign in at [app.zvid.io](https://app.zvid.io). 2. Select **Create video** in the top bar. The [visual editor](https://docs.zvid.io/docs/editor/overview/) opens in a new tab. Allow the browser to open that tab if it was blocked. 3. Enter a descriptive **Project name**, such as `My first video`. 4. Choose a **Resolution preset** for your destination. On a compact screen, open **Project settings** to see these controls. 5. Select **Text** in the tool rail, then **Add a heading**. Click the added text, or use **Selected element actions → Edit properties**, then enter your message under **Design → Content**. See [add and style text](https://docs.zvid.io/docs/editor/text/). ![Zvid editor Text panel showing Add a heading, Add a subheading, Add a line of body text, and Add a small caption.](https://docs.zvid.io/img/dashboard-guide/editor-text-tools.png) 6. Use the [timeline](https://docs.zvid.io/docs/editor/timeline/) to check when the heading appears and how long it remains visible. For a first test, keep the project short. 7. Use **Play (Space)** to preview it. Adjust the text, layout or timing until you are satisfied. The browser preview is an editing aid; the cloud output is the final result. ## How to save the editable project 1. Select **Save** in the editor toolbar. On a compact screen, its icon remains available even when the text label is hidden. 2. If prompted, sign in with the same Zvid account. 3. In **Save project to your account**, check the **Project name** and select **Save project**. 4. Return to **Dashboard → Projects** to find the saved draft. Use **Open in editor** to continue editing later. See [save, import and reopen projects](https://docs.zvid.io/docs/editor/projects/) for saving an existing project or making another cloud copy. ## How to render your first video **Navigation: Editor → Render.** 1. Select **Render** in the editor toolbar. 2. Review the project information and available credits in **Render video**. Correct any validation errors shown in the dialog. ![Zvid Render video dialog showing the Start render button that submits the project for cloud rendering.](https://docs.zvid.io/img/dashboard-guide/editor-render-dialog.png) 3. Select **Start render** once. This submits a cloud rendering job and can spend credits. 4. Follow the queue and rendering progress. When rendering finishes, use **Download** in the dialog or open the dashboard's **Renders** page. > **Submitting another render can incur another charge. If the connection drops after you select **Start render**, check **Dashboard → Renders** before submitting again. Closing the dialog does not cancel a submitted cloud job.** ## Where the finished video is stored **Navigation: Dashboard → Renders.** The completed render has a **Ready** badge and an output URL. Use its three-dot menu → **View** to open the output, or **Download** to save a copy on your device. The saved design stays separately under **Projects**. The render list loads the latest 50 jobs for the selected media type. Download important deliverables for your own records; a project draft is not a downloaded video. See [find, view and download renders](https://docs.zvid.io/docs/dashboard/videos/). ## Troubleshooting ### The editor did not open **Cause:** Your browser may have blocked the new tab. **Solution:** Allow the editor tab or open [editor.zvid.io](https://editor.zvid.io) directly. Use the same account when saving and rendering. ### Start render is unavailable or the render fails **Cause:** The project may have validation errors, assets may be inaccessible, or your account may lack available credits. **Solution:** Read the displayed error, correct the project and check **Credits**. Follow [troubleshoot a render](https://docs.zvid.io/docs/dashboard/troubleshooting-renders/) before submitting another job. ## What happens next You have an editable draft and a separate finished video. Reopen the draft to make another version, or turn a repeated design into a [reusable template](https://docs.zvid.io/docs/dashboard/templates/). ## Related documentation - [Dashboard overview](https://docs.zvid.io/docs/dashboard/overview/) - [Preview a video](https://docs.zvid.io/docs/editor/preview/) - [Render and export options](https://docs.zvid.io/docs/editor/export/) - [Understand credits](https://docs.zvid.io/docs/dashboard/credits/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/getting-started-account/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/getting-started-account.md # Create an account and sign in Your Zvid account connects the dashboard, visual editor, and API resources you create. ## When to use it Follow this guide the first time you use Zvid, or when sign-in asks you to finish email verification or your profile. ## Create your account **Location:** [Zvid sign-in page](https://app.zvid.io/login) → **Sign up for free**. 1. Click **Sign up for free**. You can also open [Create your account](https://app.zvid.io/register) directly. 2. Enter **First name**, **Last name**, and **Email address**. Use an inbox you can access. 3. Enter **Password** and repeat it in **Confirm password**. Use at least eight characters, including an uppercase letter, a lowercase letter, a number, and a special character from `@$!%*?&`. 4. Review the linked **Terms of Service** and **Privacy Policy**, then click **Create account**. 5. On **Check your email**, open the verification email and follow its link. Successful verification takes you to **Tell us about yourself**. If you need to sign in again, use the same email and password. ## Finish your profile **Location:** **Tell us about yourself**, shown after verification or when signing in with an incomplete profile. 1. Select your **Job title** and **Country / Region**. Type into these fields to find an option. 2. Choose **Individual** or **Business** under **Are you using Zvid as a business or individual?** 3. For **Business**, enter **Company name**. **Company website** and **Company size / Team size** are optional. 4. Choose **Where did you hear about us?** If you select **Other**, complete **Please specify**. 5. Select **Industry** and at least one **Primary use case (you can select multiple)**. 6. Select **Expected volume of videos / API calls per month** and **Technical level**. 7. Click **Finish**. The dashboard opens, or Zvid resumes the editor or application connection that started your sign-in. The **Business** profile choice records how you use Zvid; it does not create a shared team workspace. ## Sign in or sign out 1. Open [app.zvid.io](https://app.zvid.io). 2. Enter **Email address** and **Password**, then click **Sign in**. 3. To sign out, open your profile menu in the top bar and select **Logout**. If verification or onboarding is incomplete, Zvid shows that step before the dashboard. ## Troubleshooting ### I did not receive a verification email **Cause:** The message may be in spam, delivery may have failed, or the resend cooldown may still be active. **Solution:** Check your spam or junk folder. On **Check your email**, wait for **Resend in…** to finish, then click **Resend Verification Email**. A successful resend starts a five-minute cooldown. If the screen says **Your account is ready to verify**, your account exists but the first email was not sent; use the same resend button. ### My verification link is invalid or expired **Solution:** Click **Go to Login**, sign in, and request a fresh email from the verification screen. Use the most recent verification link. ### I cannot finish the profile form **Solution:** Complete all required selections, including at least one use case. Business accounts require a company name; selecting **Other** as the referral source requires an explanation. If the option lists are empty, reload the page and try again. ### I forgot my password Use **Forgot your password?** on the sign-in page. Follow [Reset your password](https://docs.zvid.io/docs/dashboard/account-password/). ## Related documentation - [Dashboard overview](https://docs.zvid.io/docs/dashboard/overview/) - [Update your profile](https://docs.zvid.io/docs/dashboard/account-settings/) - [Understand your credits](https://docs.zvid.io/docs/dashboard/credits/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/integrations-mcp-n8n/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/integrations-mcp-n8n.md # Configure MCP and download an n8n agent The **AI agent tools** settings choose which Zvid tools an AI client can use and the maximum credits it can spend on one render. ## When to use it Use these settings before connecting an AI client through Model Context Protocol (MCP) or downloading the dashboard's n8n AI-agent workflow. For direct REST applications, start with [API keys](https://docs.zvid.io/docs/dashboard/api-keys/). ## Choose your tool profile and credit ceiling **Location:** **Dashboard → Settings → AI agent tools**. 1. Open [Settings](https://app.zvid.io/settings) from the sidebar. 2. Scroll to **AI agent tools**. 3. Select a profile: | Profile | Intended access | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Creator** — Recommended | Authoring with planning, examples, creative libraries, Zvid's stock library, validation, projects, and templates. Drafts require exact project JSON; rendering follows an approval flow. | | **Read Only** | Inspect existing media, account credits, and usage without creating or rendering. | | **Automation** | Trusted workflows that need direct renders, capped bulk rendering, and webhook operations. | | **Developer** | All enabled MCP tools for trusted development, including direct rendering and automation. Update/delete tools remain disabled. | 4. Set **Maximum credits per render** to a whole number from 1 to 10,000. The default is 30. 5. Click **Save MCP Defaults** and wait for **MCP defaults updated**. ![AI agent tools in Settings showing Creator, Read Only, Automation, and Developer profiles and Maximum credits per render.](https://docs.zvid.io/img/dashboard-guide/settings-ai-agent-tools.png) > **The credit ceiling applies even after approval** A render above this amount is blocked even if the user approved its quote. The AI agent cannot change this value. This is a per-render ceiling, not a daily or monthly spending budget. ## Download the n8n AI-agent workflow 1. Save any changed MCP settings first. 2. Click **Download n8n Workflow** in **AI agent tools**. 3. Import the downloaded `zvid-ai-agent-.json` file into n8n using its workflow import action. 4. Read the imported workflow's **Setup** note. 5. Open **OpenRouter Chat Model** and select an **OpenRouter API** credential. 6. Open **Zvid MCP Tools**, create or select an **MCP OAuth2 API** credential, keep dynamic client registration enabled, and sign in to Zvid. 7. Review the Zvid consent screen and complete [authorization](#authorize-an-mcp-client). 8. Open the Chat Trigger's test chat to try the agent. Read and approve any render quote before proceeding with a credit-spending request. The workflow connects through the built-in **MCP Client Tool**. Its endpoint includes the profile and credit ceiling selected at download time. **Tools to Include** can further narrow the tools exposed by that profile. > **Downloads keep their saved settings** Changing dashboard defaults later does not update an already imported workflow. Download a new copy or deliberately update that workflow's endpoint settings. Dashboard defaults apply to future downloads and MCP clients that do not explicitly select their own profile or ceiling. The downloaded agent uses n8n's built-in nodes. If you instead use a workflow with Zvid action or trigger nodes, a workspace owner/admin may need to install **`@zvid/n8n-nodes-zvid`** from **Settings → Community nodes** before configuring Zvid credentials. See [Integrations](https://docs.zvid.io/docs/integrations/) for the separate community-node setup. ## Authorize an MCP client **Location:** Start the connection in your MCP client → Zvid sign-in → **Connect to Zvid**. 1. Configure your client to connect to `https://mcp.zvid.io/mcp`, following its MCP connection instructions. 2. Start sign-in from the client and sign in to your Zvid account when prompted. 3. On **Connect to Zvid**, check the **Application** name and website, when shown. 4. Read the listed access, including whether the selected profile allows rendering and use of credits. 5. Click **Allow access** to authorize, or **Deny** to reject the request. You return to the requesting client. With OAuth, no API key is shared; the client receives an access token and may refresh it. The customer dashboard currently has no connected-app management list. Use the client's disconnect/revoke controls when available; removing local settings alone should not be treated as confirmed server-side revocation. ## Troubleshooting ### Download n8n Workflow is disabled **Cause:** Your changes have not been saved, a download is already starting, or MCP preferences failed to load. **Solution:** Click **Save MCP Defaults** first. If the page shows a preferences-loading error, reload Settings before downloading. ### Maximum render credits is rejected Enter a whole number between 1 and 10,000, then save again. ### The consent screen says “Request unavailable” The request is invalid or expired. Return to the MCP client and start sign-in again; do not reuse the old consent URL. ### A tool or render is unavailable Check the saved profile, explicit settings in your MCP client's endpoint, and **Tools to Include** in n8n. If a render exceeds the ceiling, reduce its estimated cost or have the workflow owner deliberately change the ceiling. Profile defaults do not rewrite existing workflows. ## Related documentation - [Authentication methods](https://docs.zvid.io/docs/authentication/) - [Integrations reference](https://docs.zvid.io/docs/integrations/) - [Check credits and usage](https://docs.zvid.io/docs/dashboard/credits/) - [Add a webhook](https://docs.zvid.io/docs/dashboard/webhooks/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/overview/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/overview.md # Dashboard overview The [Zvid Dashboard](https://app.zvid.io) is where you manage saved designs, rendered media, automation, and your account. ## When to use it Use the dashboard to return to a design, find a finished video or image, create personalized outputs from templates, or manage credits and developer access. The [visual editor](https://editor.zvid.io) opens separately and uses the same Zvid account. Three concepts help you choose where to start: | Concept | What it contains | Where to find it | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | | **Project** | A saved, editable design made of scenes and elements. A scene is a timed section of a video; elements include text, images, and audio. | **Projects** | | **Render** | A job that turns a design into a finished video or image. Rendering uses credits. | **Renders** | | **Template** | A reusable design with variables, such as a product name or image URL, that you can replace for each output. | **Templates** | Saving a project and rendering it are separate actions. Save the design to continue editing later; render it to produce downloadable media. ## Find your way around Open [app.zvid.io](https://app.zvid.io) and sign in. Use the left sidebar to choose a page. On a small screen, select **Open sidebar** in the top bar; select **Close sidebar** to dismiss it. ![Zvid dashboard sidebar showing Dashboard, Projects, Renders, Bulk renders, Templates, Credits, API Keys, Webhooks, Affiliate, Settings, and Discord community.](https://docs.zvid.io/img/dashboard-guide/dashboard-navigation.png) | Sidebar label | What you can do | | --------------------- | --------------------------------------------------------------------------------- | | **Dashboard** | Review your balance and recent render activity. | | **Projects** | [Open, rename, and delete saved drafts](https://docs.zvid.io/docs/dashboard/projects-and-renders/). | | **Renders** | [Find, view, download, rename, and delete rendered outputs](https://docs.zvid.io/docs/dashboard/videos/). | | **Bulk renders** | [Create and monitor batches from template data](https://docs.zvid.io/docs/dashboard/bulk-renders/). | | **Templates** | [Create, edit, preview, duplicate, and archive reusable designs](https://docs.zvid.io/docs/dashboard/templates/). | | **Credits** | [Check balances, spending, and transactions](https://docs.zvid.io/docs/dashboard/credits-and-billing/). | | **API Keys** | [Create and manage credentials for API access](https://docs.zvid.io/docs/dashboard/api-keys/). | | **Webhooks** | [Configure notifications sent to your application](https://docs.zvid.io/docs/dashboard/webhooks/). | | **Affiliate** | Open your affiliate account area. | | **Settings** | Open account settings. | | **Discord community** | Open the Zvid community in another tab. | The top bar also contains **Create video**, **Create image** on wider screens, your profile menu, and the light/dark theme control. **Toggle color theme** switches the dashboard appearance. > **Project search** The top-bar **Search projects…** field currently opens **Projects**, but does not narrow the saved-project list. Open the project you need from the list. The search field on **Renders** separately filters renders by name or resolution. ## Create your first video **Navigation: Dashboard → Create video** 1. Select **Create video** in the top bar. The visual editor opens in a new tab. 2. Follow the [editor overview](https://docs.zvid.io/docs/editor/overview/) to add scenes and content. 3. [Save your project](https://docs.zvid.io/docs/editor/projects/) so you can return to the editable design from **Projects**. 4. Follow [Rendering & Export](https://docs.zvid.io/docs/editor/export/) to produce the finished video. Review the rendering cost and your available credits before submitting. 5. Return to the dashboard and select **Renders**. Wait for the card to show **Ready**, then use **Download**. To create a still image, select **Create image**, or open **Renders → New image**. Saving a draft does not itself produce a downloadable image or video. ## Read the dashboard home **Navigation: Dashboard** - **Credits** shows your available balance, split into subscription and one-time credits, and credits used in the last 30 days. Select the card to open **Credits**. - **Videos** shows recent render counts, including completed and failed jobs. Select it to open **Renders**. - **Processing** shows jobs in the recent list that are waiting or rendering. - **Recent activity** shows up to six recent jobs with status, name, resolution, duration, credit amount, and date. Select **View all →** to open the render library. - **Manage API keys** opens **API Keys**; **Read the docs** opens the documentation. The render totals are a snapshot of the most recent 50 jobs loaded by the page. Use **Credits** for the documented usage views; do not treat the home render count as an account-wide report or a guaranteed 30-day total. The sidebar credit panel also shows the available total and its two balances. **View usage →** opens **Credits**; **Upgrade plan** opens **Subscription**. ## Open your subscription or sign out **Navigation: Top bar → Open profile menu** 1. Select your avatar or name in the top bar. 2. Select **Subscription** to manage your plan, or **Logout** to sign out and return to the sign-in page. For subscription tasks, see [Credits and billing](https://docs.zvid.io/docs/dashboard/credits-and-billing/). ## Use the community invitation Select **Join Discord** in the home-page invitation to open the community. Select **Dismiss community invitation** to hide the invitation in this browser. The **Discord community** sidebar link remains available. For customer support, visit https://zvid.io/contact. ## What happens next Your saved design is available in **Projects**. Each submitted output has its own job in **Renders**. Reusable templates and bulk batches appear in their respective sections. ## Related documentation - [Projects and saved drafts](https://docs.zvid.io/docs/dashboard/projects-and-renders/) - [View and download renders](https://docs.zvid.io/docs/dashboard/videos/) - [Templates and bulk rendering](https://docs.zvid.io/docs/dashboard/templates-and-bulk/) - [Visual editor](https://docs.zvid.io/docs/editor/overview/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/projects-and-renders/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/projects-and-renders.md # Manage projects and saved drafts **Projects** lists designs saved from the visual editor so you can continue editing them later. ## When to use it Use a project for work you want to revise: a campaign design, a reusable scene arrangement, or an image layout. To find finished media instead, open [Renders](https://docs.zvid.io/docs/dashboard/videos/). ## Before you begin Sign in with the account that saved the project. Saving in the editor creates the cloud draft shown here. An unsaved design in a browser does not appear in the dashboard list. ## Create a project **Navigation: Dashboard → Projects → New project** 1. Select **Projects** in the sidebar. ![Zvid Projects page with New project in the upper right and Open the editor in the empty-project message.](https://docs.zvid.io/img/dashboard-guide/projects-new-project.png) 2. Select **New project**. On an empty page, **Open the editor** leads to the same editor. 3. Build your design in the editor and [save it](https://docs.zvid.io/docs/editor/projects/). 4. Return to **Projects** and reload the page if the new draft has not appeared. The saved project appears with its name, version, project ID, and updated time. Still-image projects also show an **image** badge. ## Open a saved project **Navigation: Dashboard → Projects** 1. Find the project by name in the list. 2. Select the project name or the **Open in editor** icon beside it. 3. Continue editing in the new editor tab, then save your changes. Each project has a direct editor link containing its `prj_…` ID. The link identifies the saved design; it does not make the project public or grant access to another account. ## Copy a project ID 1. Open **Projects**. 2. Select the ID beneath the project name. Its tooltip is **Copy project ID**. 3. Wait for **Project ID copied to clipboard** before pasting it into your application or support request. If copying fails, select the visible ID and copy it manually. A project ID identifies the editable draft, not a completed render. ## Rename a project 1. On **Projects**, select the **Rename** pencil icon beside the project. 2. In **Rename project**, replace **Name** with the new name. Use a nonempty name of up to 255 characters. 3. Select **Rename**. Select **Cancel** to leave the name unchanged. The project list updates and shows **Project renamed**. The project retains its ID, so an existing editor deep link still identifies it. ## Delete a project > **Permanent draft deletion** Deleting a project permanently removes the saved draft. The dashboard has no restore control. Rendered videos and images already produced from the project are unaffected. Applications that rely on reopening this saved project will no longer be able to load it. 1. Open **Projects** and locate the draft to remove. 2. Select **Delete project**, the trash icon on its row. 3. Read the browser confirmation and confirm the deletion, or cancel to keep the draft. The row disappears and **Project deleted** appears. To remove finished output as well, use the separate [render deletion action](https://docs.zvid.io/docs/dashboard/videos/#delete-a-render). ## Renders A saved project and a render are different records. A project stores the editable design; a render produces a video or image and uses credits. Rendering again creates another output job. Continue with [View, download, and manage renders](https://docs.zvid.io/docs/dashboard/videos/) for status, playback, downloads, renaming, and deletion. ## Troubleshooting ### Unable to load your projects **Cause:** The dashboard could not retrieve the saved-project list. **Solution:** Select **Try again** in the error panel. During a failed refresh, previously loaded projects can remain visible; their presence does not mean the latest refresh succeeded. ### A saved project is missing **Cause:** The design may not have been saved to this account, or it may be outside the first 50 drafts displayed by the dashboard. **Solution:** Confirm the editor save completed and that the dashboard uses the same account, then reload **Projects**. For a larger library, use the [editor's project browser](https://docs.zvid.io/docs/editor/projects/). An existing direct editor link can also reopen a project you still own. ### Searching does not change the list **Cause:** The top-bar **Search projects…** field currently opens the project page without applying a filter. **Solution:** Find the project in the visible list, or use the editor's project browser. Do not interpret an unchanged list as a failed save. ## Related documentation - [Editor projects and saving](https://docs.zvid.io/docs/editor/projects/) - [View and download renders](https://docs.zvid.io/docs/dashboard/videos/) - [Create reusable templates](https://docs.zvid.io/docs/dashboard/templates/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/subscription-cancel/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/subscription-cancel.md # Cancel a subscription or undo cancellation Cancellation schedules your paid subscription to end and your account to move to the Free plan. ## When to use it Use cancellation to stop renewal of an active paid subscription. To keep a paid tier at a different price or billing period, [change your plan](https://docs.zvid.io/docs/dashboard/subscription/) instead. ## Before you begin Check your next billing date and the limits your projects and automations require. The cancellation control is shown for an active paid subscription. ## Schedule cancellation **Location:** **Dashboard → profile menu → Subscription → Cancel subscription**. 1. Open your profile menu and choose **Subscription**. 2. Under **Manage your plan**, click **Cancel subscription**. 3. In **Confirm cancellation**, review **Current** and **Free plan active at**. 4. Click **Confirm** to submit cancellation. The dialog's **Cancel** button only closes the dialog without cancelling your subscription. 5. Read **Cancellation scheduled**, click **OK**, and check **Scheduled change** on the page. ## What happens next Your subscription is scheduled to end at the displayed date, normally the end of the current billing period. Until then, the scheduled cancellation can be removed while your subscription remains active. > **Review future rendering needs** When cancellation takes effect, Free plan allowances and limits apply. Automations that require paid limits may fail future submissions even if you retain one-time credits. Cancellation is not an account-deletion or project-deletion action, and it is not a request for a refund. ## Undo a scheduled cancellation 1. Open **Subscription** before the cancellation takes effect. 2. Confirm that your paid subscription is still active and shows a scheduled cancellation. 3. Click **Undo cancellation**. 4. Wait for **Cancellation removed**, then click **OK**. The scheduled cancellation is removed and the paid subscription continues. This action does not show an additional confirmation dialog. ## Troubleshooting ### Cancel subscription is not visible **Cause:** The account may already be Free, have a different subscription status, or already have cancellation scheduled. **Solution:** Check **Current plan** and status. A scheduled cancellation replaces the button with **Undo cancellation**. For a billing issue that the page cannot resolve, contact https://zvid.io/contact. ### Undo cancellation is not visible **Solution:** Check whether the cancellation has already taken effect. The dashboard can undo a pending cancellation only while the paid subscription is active. If the account is now Free, use [Subscribe](https://docs.zvid.io/docs/dashboard/subscription/#subscribe-to-a-paid-plan) to start a paid subscription again. ## Related documentation - [Manage your subscription](https://docs.zvid.io/docs/dashboard/subscription/) - [Check credits and usage](https://docs.zvid.io/docs/dashboard/credits/) - [Credits, plans, and limits](https://docs.zvid.io/docs/credits-and-plans/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/subscription/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/subscription.md # Manage your subscription and buy credits The **Subscription** page manages your paid plan, billing period, and one-time credit purchases. ## When to use it Use this page when you need more credits or higher plan limits, want to change your billing period, or need to resolve a payment issue. ## Open your subscription **Location:** **Dashboard → profile menu → Subscription**. 1. Open your profile menu in the top bar. 2. Select **Subscription**, or open [Subscription](https://app.zvid.io/subscription) directly. 3. Under **Manage your plan**, check **Current plan**, its status, **Next billing**, and any **Scheduled change** shown. You can also open this page with **Get more credits** on the Credits page. ## Subscribe to a paid plan 1. Choose **Monthly** or **Yearly**. For a new account, **Yearly** is initially selected; check the choice before continuing. 2. Review the available plan cards and their listed features. With **Yearly**, the large monthly amount is an equivalent monthly rate; the card also shows the amount **billed yearly**. 3. If offered, enter your checkout promotion in **Discount code (optional)**. ![Manage your plan showing the Monthly and Yearly controls and optional discount-code field for a Free account.](https://docs.zvid.io/img/dashboard-guide/subscription-cadence.png) 4. Click **Subscribe** on the chosen plan. 5. Review the plan, billing period, discount, and final amount in the Paddle checkout, then complete checkout if correct. Payment and subscription updates are processed after checkout. Reload Subscription to check the updated status and [refresh Credits](https://docs.zvid.io/docs/dashboard/credits/) to see the current balance. Use the displayed catalog and checkout for current amounts; prices are not fixed in this guide. ## Upgrade your plan 1. Choose the billing period you want. 2. Click **Upgrade** on a higher plan. 3. In **Confirm upgrade**, review **Current**, **New**, **Cadence**, and **Prorated charge (pay now)**. The preview can include total, subtotal, tax, and discount. 4. Click **Confirm** to submit the upgrade, or **Cancel** to leave the plan unchanged. 5. Read **Upgrade in progress**, then click **OK**. > **An upgrade can charge your payment method** The confirmation previews the prorated amount due now. The final amount may vary slightly. Review it before selecting **Confirm**. The upgrade may take a few minutes to appear. If it takes more than 10 minutes, use the support link shown in the result: https://zvid.io/contact. ## Downgrade or change your billing period 1. Choose **Monthly** or **Yearly**. 2. To move to a lower tier, click **Downgrade** on that plan. To keep your tier and change its billing period, select the other period and click **Change cadence** on your current plan's card. 3. Review the new plan or cadence and **Expected effective date**. 4. Click **Confirm** to submit, or **Cancel** to close without submitting. 5. Read the scheduled-change result and check the date under **Manage your plan**. Changes are generally scheduled for the end of the current billing period. Use the effective date in your confirmation and subscription summary. Lower plan limits can affect future renders and the number of resources you can create. The dashboard does not provide a general undo button for downgrades or cadence changes. ## Buy a one-time credit pack **Location:** **Subscription → Buy more credits**. 1. Scroll to **Buy more credits**. 2. Review a pack's credit quantity and one-time price. 3. Click **Buy credits** and review the Paddle checkout. 4. Complete checkout if the amount is correct. 5. After **Payment received**, allow a few moments for credits to be applied, then open **Credits** and click **Refresh**. Packs work with any plan, including Free. Their credits do not expire and add to your balance. A pack does not upgrade plan limits or create a recurring subscription. ## Resolve a payment issue When a subscription is `past_due`, the page shows **Payment issue — action required**. 1. Click **Update payment method** in that banner. 2. Complete the required update in the Paddle customer portal, which opens in a new tab. 3. Return to Subscription and reload to check the status after processing. This button appears for past-due subscriptions. The dashboard does not provide a separate always-visible payment-method or invoice-history screen. ## Troubleshooting ### Subscribe or Buy credits stays on “Loading…” **Cause:** Checkout has not finished loading. **Solution:** Reload the page and wait for the button label to change. If it remains unavailable, contact https://zvid.io/contact. ### A plan change shows “Something went wrong” **Solution:** Close the message and reload to check your current plan and scheduled changes before retrying. Use the support link in the dialog if the requested state remains unclear. ## Related documentation - [Cancel a subscription or undo cancellation](https://docs.zvid.io/docs/dashboard/subscription-cancel/) - [Check credits and usage](https://docs.zvid.io/docs/dashboard/credits/) - [Credits, plans, and limits](https://docs.zvid.io/docs/credits-and-plans/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/templates-and-bulk/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/templates-and-bulk.md # Templates and bulk rendering Templates let you reuse a design while changing its content; bulk rendering applies many sets of content to that design in one submission. ## When to use it Use a template when the layout stays the same but product details, text, images, colors, or other variables change between outputs. Use a batch when you already have multiple sets of values to process. | Task | Start here | | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | Create, edit, duplicate, or archive a reusable design | [Manage templates](https://docs.zvid.io/docs/dashboard/templates/) | | Validate one set of variable values without creating media | [Preview a template for free](https://docs.zvid.io/docs/dashboard/templates/#preview-a-template-for-free) | | Produce one personalized video or image | [Render from a template](https://docs.zvid.io/docs/dashboard/templates/#render-from-a-template) | | Produce outputs from a table or CSV | [Create a bulk render](https://docs.zvid.io/docs/dashboard/bulk-renders/) | | Build automation with code | [Template basics](https://docs.zvid.io/docs/templates/template-basics/) and [bulk rendering API](https://docs.zvid.io/docs/automation/bulk-rendering/) | ## Templates **Navigation: Dashboard → Templates** ![Zvid Templates page showing the New template button used to start a reusable video or image design.](https://docs.zvid.io/img/dashboard-guide/templates-new-template.png) 1. Create a template with **New template**, or save one from the [visual editor](https://docs.zvid.io/docs/editor/templates/). 2. Select the template name to open its detail page. 3. In **Use template**, enter the variable values for an output. 4. Select **Preview (free)** to resolve the saved template into JSON and validate it. 5. When ready to create media, select **Render video**. This submits a paid render and opens **Renders**. A template has a `tpl_…` ID that your applications can use for repeated API renders. The dashboard's template list contains your saved templates. ## Bulk renders **Navigation: Dashboard → Bulk renders → New bulk render** 1. Choose a saved template. 2. Enter one data row per output, or select **Import CSV**. 3. Review the row count, variable values, and output format. 4. Select **Render N videos**, where N is the displayed row count. 5. Monitor the new batch and open or download completed outputs. Rendering reserves credits. Rows rejected during validation can be corrected and submitted separately; accepted rows may already be rendering. Follow the [bulk-render guide](https://docs.zvid.io/docs/dashboard/bulk-renders/) to avoid submitting successful rows twice. ## What happens next Single renders appear in **Renders**. Batch submissions also appear in **Bulk renders**, where you can inspect counts, errors, and individual outputs. ## Related documentation - [Manage templates](https://docs.zvid.io/docs/dashboard/templates/) - [Create and monitor bulk renders](https://docs.zvid.io/docs/dashboard/bulk-renders/) - [Credits and billing](https://docs.zvid.io/docs/dashboard/credits-and-billing/) - [Variables and templates in the editor](https://docs.zvid.io/docs/editor/templates/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/templates/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/templates.md # Create and manage templates **Templates** stores reusable video and image designs with variables you can replace for each render. ## When to use it Use a template when you want many outputs with the same layout and different content. For example, keep the same product-video design while changing its title, photo, and brand color. Use [bulk rendering](https://docs.zvid.io/docs/dashboard/bulk-renders/) when those values are ready in multiple rows or a CSV file. ## Before you begin Sign in with the account that owns the template. To create a template in the dashboard, use valid project JSON, including defaults for its variables. For a visual authoring workflow, start with [Variables & Templates in the editor](https://docs.zvid.io/docs/editor/templates/). Creating or saving a template validates it against the applicable plan limits. Producing media requires enough rendering credits; **Preview (free)** does not create media or use rendering credits. ## Browse your saved templates **Navigation: Dashboard → Templates** 1. Select **Templates** in the sidebar. 2. Choose **All**, **Video**, or **Image** to narrow the list. 3. Select a template name or its **Edit / use template** icon to open its detail page. Each row shows the template version, description when provided, variable count, updated time, and `tpl_…` ID. The page displays up to 50 active templates; it is your saved-template list. ## Create a template **Navigation: Dashboard → Templates → New template** ![Zvid Templates page with the New template button above an empty template list.](https://docs.zvid.io/img/dashboard-guide/templates-new-template.png) 1. Select **New template**. 2. Enter **Name**. Optionally enter **Description (optional)**. 3. In **Project JSON**, adapt the prefilled starter or paste your project definition. Keep a default in `variables` for every placeholder, and give every video scene an explicit duration. ![New template dialog showing Name, Description, the prefilled Project JSON editor, and the Cancel and Create template buttons.](https://docs.zvid.io/img/dashboard-guide/templates-create-dialog.png) 4. Select **Create template**. 5. If validation reports a field and message, correct that field and submit again. The dialog closes, **Template created** appears, and the new template joins the list. Select its name to use it. **Cancel** closes the dialog without creating a template. For the project structure and placeholder rules, see [Template basics](https://docs.zvid.io/docs/templates/template-basics/). The JSON editor provides syntax assistance and property suggestions; successful parsing alone does not guarantee a valid renderable design. ## Copy a template ID 1. Open **Templates** or a template's detail page. 2. Select its visible `tpl_…` ID. The tooltip is **Copy template ID for API use**. 3. Wait for **Template ID copied to clipboard**. Use that ID in [template-based API requests](https://docs.zvid.io/docs/templates/template-basics/#rendering-with-data). An ID identifies a template; API calls still require authentication for its owning account. ## Edit a template and save a new version **Navigation: Dashboard → Templates → Template name** 1. In the **Template** section, edit **Name**, **Description**, or **Project JSON**. 2. Select **Save (new version)**. 3. Resolve any displayed validation errors and save again if necessary. After a successful save, the version number increases and the confirmation identifies the new version. The template retains its ID. Future uses of that ID use the updated definition, so coordinate changes with automations that depend on its variables and layout. Saving also resets the **Use template** inputs to the updated defaults. To work visually, select **Open in editor** on the detail page, or **Open in the visual editor** in the list. Follow the [editor template workflow](https://docs.zvid.io/docs/editor/templates/) to save your changes. > **Save before previewing edits** **Preview (free)** and **Render video** use the saved template. Save changes in **Project JSON** before testing them with these actions. ## Duplicate a template 1. Return to **Templates**. 2. Select the **Duplicate (save as…)** icon on the template's row. 3. Open the newly created **Copy of …** template, change its name or content, and select **Save (new version)**. Duplication creates a separate template with a different ID and counts toward your template allowance. Automations using the original ID keep using the original template. ## Set values for one output **Navigation: Dashboard → Templates → Template name → Use template** 1. Review the fields populated from the saved variable defaults. 2. Change the values for this output using the appropriate input: | Variable type | What to enter | | --------------- | ---------------------------------------------------------- | | String | Text such as a title, color, or URL. | | Number | A number. Leaving it blank keeps the saved default. | | Boolean | Select or clear the checkbox. | | Array or object | Valid JSON matching the data structure your template uses. | 3. Optionally choose **Output format override (optional)**. **Template default** keeps the saved resolution; another choice applies a named resolution preset to this use of the template. The field named **Output format override (optional)** controls resolution presets here. Values entered in **Use template** are inputs for previewing or rendering; they do not replace the saved variable defaults. Variables marked **unused** are declared but not referenced by the saved design. ## Preview a template for free 1. Set the desired values in **Use template**. 2. Select **Preview (free)**. 3. Review **Resolved preview**: scene count, total duration when available, substitution count, and the resolved project JSON. The preview resolves placeholders and validates the resulting project. It creates no job and no video or image. For a visual canvas preview, use the [editor preview](https://docs.zvid.io/docs/editor/templates/#preview-toggle). For a credit estimate through the API, see [Validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/). ## Render from a template 1. Save any changes to the template definition. 2. Set the output values and optional resolution in **Use template**. 3. Run **Preview (free)** and correct any validation errors. 4. Select **Render video** when ready to spend rendering credits. 5. The dashboard confirms the reserved credits and opens **Renders**. Wait for **Ready**, then view or download the result. The button is currently labeled **Render video** for both video and image templates. The saved template type determines whether the result is a video or still image. ## Archive a template > **Existing automations can be affected** Archiving hides the template from the active list and prevents new renders using its ID. Existing rendered media is not deleted. The dashboard has no unarchive or undo control. Update integrations that still submit this template ID before archiving it. 1. Open **Templates**. 2. Select **Archive template** on the appropriate row. 3. Read the browser confirmation and confirm, or cancel to keep it active. The template disappears from the list and **Template archived** appears. Archiving an unused template can free an active-template allowance for a new one. ## Troubleshooting ### Project JSON is not valid JSON **Cause:** The definition cannot be parsed, for example because of missing quotes, an extra comma, or an unmatched bracket. **Solution:** Correct the JSON in **Project JSON**, then select **Create template** or **Save (new version)** again. If parsing succeeds but field errors remain, follow those messages and the [template requirements](https://docs.zvid.io/docs/templates/template-basics/). ### A variable is not valid JSON **Cause:** An array or object field in **Use template** contains invalid JSON. **Solution:** Correct the named variable's JSON and select **Preview (free)** again. Plain text fields do not need JSON string quotes. ### Template limit reached **Cause:** Your active templates have reached the plan allowance. **Solution:** Archive a template you no longer need, or review your [subscription](https://docs.zvid.io/docs/dashboard/credits-and-billing/) for a higher allowance. Check any integrations using the template before archiving it. ### Preview succeeds but rendering fails **Cause:** A free preview validates the resolved project but does not complete a media render. Rendering can still encounter account limits, insufficient credits, or media-download problems. **Solution:** Read the submission error, check **Credits**, and correct the reported issue. For a job already marked **Failed**, use the [render recovery guidance](https://docs.zvid.io/docs/dashboard/videos/#my-render-failed). ### Template not found **Cause:** The link may refer to an unavailable template or a template owned by another account. **Solution:** Open **Templates** in the owning account and choose an active template from the list. An archived template cannot be used for new renders. ## Related documentation - [Create and monitor bulk renders](https://docs.zvid.io/docs/dashboard/bulk-renders/) - [View and download renders](https://docs.zvid.io/docs/dashboard/videos/) - [Template variables and dynamic content](https://docs.zvid.io/docs/templates/dynamic-content/) - [API keys](https://docs.zvid.io/docs/dashboard/api-keys/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/troubleshooting-renders/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/troubleshooting-renders.md # Troubleshoot failed or delayed renders Use the render status and the error shown at submission to choose the next action. ## When to use it Use this guide if a job fails, stays in the queue, does not download, or differs from its browser preview. ## How to check a render **Navigation: Dashboard → Renders.** 1. Open [Renders](https://app.zvid.io/videos). 2. Choose **All**, **Videos** or **Images** to match your output. 3. Enter the name or resolution in **Search by name or resolution…**. This searches the latest 50 loaded jobs for the selected type. 4. Read the job's badge. **Ready** means completed; **Rendering** means active processing; **Waiting**, **Pending** or **Stalled** have not produced a completed output; **Failed** means the job did not complete successfully. 5. If you submitted from the editor, also read its render dialog. It can show the error message and field-level details. A bulk render's detail page shows errors for failed items. ![Zvid Renders page with the Failed status filter selected and two failed render cards showing Preview unavailable.](https://docs.zvid.io/img/dashboard-guide/renders-failed-filter.png) > **The standard Renders card does not expose a full error-detail panel or a retry button. For deeper diagnostics, developers can retrieve job status through the [API reference](https://docs.zvid.io/docs/endpoints/get-render-job/). Keep the original error message when it is displayed.** ## My render failed **Cause:** A failed job can result from invalid project data or a media source that the cloud renderer cannot load or process. The specific error determines the cause. **Solution:** 1. Read the error in the editor's render dialog or the relevant [bulk item](https://docs.zvid.io/docs/dashboard/bulk-renders/). 2. Return to the saved draft under **Projects → Open in editor**, or the template you used. 3. Correct the field or asset identified in the error. Do not assume changing the render's display name changes the project. 4. Preview the project. For templates, run **Preview (free)** again; this checks the resolved project JSON, not the final media. 5. Deliberately submit a new render only after correcting the cause. A new submission may spend credits. If the failure has no visible explanation, contact https://zvid.io/contact with the job ID when available, the approximate time, and the error text. Do not send API keys or webhook secrets. ## My render is taking longer than expected **Cause:** The job may still be queued or processing. A network interruption may also leave the browser showing an older status. **Solution:** 1. Check **Renders** and keep the status filter on **All** to include waiting jobs. The **Processing** filter does not include every queued state. 2. Reload the page to fetch the latest stored state. The editor also checks accepted jobs while its render dialog is open. 3. Check whether a completed job already exists before rendering again. 4. If the state remains unchanged and you need assistance, include the job information when contacting support. No completion-time guarantee is implied by a progress percentage. Do not create repeated paid submissions to refresh progress. ## I do not have enough credits **Cause:** The requested render requires more available credits than the account currently has, including reservations for other work. **Solution:** Open **Credits**, select **Refresh**, and inspect your subscription and one-time balances. Wait for existing jobs to finish or review available options under **Subscription**. You can also reduce the project's dimensions or duration and validate the revised request. See [credits and usage](https://docs.zvid.io/docs/dashboard/credits/). ## An image, video or audio asset could not be loaded **Cause:** A source URL may require sign-in, expire, return an HTML page instead of media, or refer to an unsupported or corrupt file. **Solution:** 1. Inspect the selected item's source in the editor. 2. Use a direct media URL that the cloud service can access. A page's share link is not necessarily a media-file URL. 3. Replace an expired or inaccessible URL, or use the editor's **Upload** workflow. 4. Check the revised project before submitting again. See [media asset requirements](https://docs.zvid.io/docs/concepts/media-assets/). ## My video is Ready but will not download **Cause:** **Download** needs a usable output URL and a successful browser fetch. Connectivity or browser restrictions can interrupt the download. **Solution:** Open the three-dot menu → **View** to check whether the output opens, then retry **Download**. If neither works, reload the list and contact support with the job information. A failed download does not itself mean the cloud render failed. ## The finished video differs from the preview **Cause:** Browser preview and cloud rendering use different playback and rendering paths. Some effects cannot be reproduced exactly in the editor preview. **Solution:** Compare the actual output with the intended layout, then check the element's timing, font, fit mode and source. See [preview limitations](https://docs.zvid.io/docs/editor/preview/) and [canvas and positioning](https://docs.zvid.io/docs/editor/canvas/). Change the project and submit a new render only when needed. ## What happens next After correcting the cause, follow the new job until it reaches **Ready** and download the output. Keep the saved draft for future edits. ## Related documentation - [Find and manage renders](https://docs.zvid.io/docs/dashboard/videos/) - [Template preview and rendering](https://docs.zvid.io/docs/dashboard/templates/) - [Bulk renders](https://docs.zvid.io/docs/dashboard/bulk-renders/) - [API errors, timeouts and retries](https://docs.zvid.io/docs/operations/errors-and-retries/) - [Troubleshoot webhook deliveries](https://docs.zvid.io/docs/dashboard/webhooks-deliveries/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/videos/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/videos.md # View, download, and manage renders **Renders** is the dashboard library of video and image jobs submitted from your account. ## When to use it Open **Renders** to check whether an output is ready, play a completed video, download media, or manage your render history. To edit the underlying design, return to [Projects](https://docs.zvid.io/docs/dashboard/projects-and-renders/) or [Templates](https://docs.zvid.io/docs/dashboard/templates/). ## Create a video or image **Navigation: Dashboard → Renders** 1. Select **New video** or **New image**. 2. Create the design in the visual editor that opens. 3. Follow [Rendering & Export](https://docs.zvid.io/docs/editor/export/) to submit it for rendering. 4. Return to **Renders** to monitor the job. The older `/videos/add` dashboard address also opens the video editor. If that redirect does not complete, select **Open the editor**. Rendering uses credits. Saving an editor project alone does not add a finished output to this page. ## Find a render **Navigation: Dashboard → Renders** 1. Choose **All**, **Videos**, or **Images** in the first filter group. 2. Choose **All**, **Completed**, **Processing**, or **Failed** in the second group. 3. Enter a name or resolution, such as `1920x1080`, in **Search by name or resolution…**. 4. Clear the search and return both groups to **All** to broaden the results. ![Zvid Renders page showing media-type and status filters above completed sample image renders with Ready badges and Download buttons.](https://docs.zvid.io/img/dashboard-guide/renders-filters-download.png) The page loads the latest 50 jobs for the selected media type. The status filter and search apply to those loaded jobs. For older history outside this list, developers can use the [jobs API](https://docs.zvid.io/docs/endpoints/list-render-jobs/). > **Waiting jobs** **Processing** includes jobs actively rendering. To see jobs still marked **Waiting** or **Pending**, use the **All** status filter. ## Understand render status | Card label | Meaning and next action | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | **Ready** | Rendering completed. Use **View** or **Download** when the output is available. | | **Rendering** | The job is active. A percentage bar appears when progress updates are available for a processing job. | | **Waiting** | The job is waiting in the queue. Keep the **All** filter selected to see it. | | **Pending** | The job has not reached a displayed rendering or completion state. Reload the page if it does not update. | | **Stalled** | Processing has been interrupted. Check again for a final status; see [render lifecycle](https://docs.zvid.io/docs/operations/render-lifecycle/) for recovery behavior. | | **Failed** | The job did not produce a successful output. Follow the recovery steps below. | Cards show the name, dimensions, creation date, and credit amount. Videos also show duration. An **image** badge identifies a still-image job; a **template** badge identifies an output created from a template. The credit figure uses consumed credits when available and otherwise the estimate. While the page is open, live status and progress messages update the cards. A completion notification refreshes the list with the output details. Reload the page to retrieve the current state if live updates stop. ## Preview or open a completed output 1. Find a card marked **Ready**. 2. Hover over its thumbnail and select **Play video** or **View image**. Alternatively, open the three-dot menu and select **View**. 3. The media opens in a new browser tab. Use the browser's playback or image controls. To share an available output URL, copy the address from that media tab. An output URL opens the finished media; it does not open the editable project. ## Download a video or image 1. Find a **Ready** card with a **Download** button. 2. Select **Download**. 3. Look for **Download started**, then find the file in your browser's downloads. The downloaded filename includes the render's job ID. The **Download** button appears only after completion and when an output URL is available. ## Rename a render 1. Open the card's three-dot menu and select **Rename**. 2. In **Rename Job**, enter a different, nonempty **New name**. 3. Select **Save**, or **Cancel** to keep the current name. The card updates and **Video renamed successfully** appears. Renaming changes the job's displayed name; it does not edit the video or image content. ## Delete a render > **Permanent output deletion** Deleting removes the job from your history and requests deletion of its stored output and thumbnail. The action has no dashboard undo. Download anything you need first, and update applications or published pages that rely on the output URL because that URL may stop working. 1. Open the card's three-dot menu and select **Delete**. 2. Read **Confirm Deletion**. 3. Select **Confirm** to delete, or **Cancel** to keep the job. The deleted card disappears. This action does not delete a saved editor project or template. An actively rendering job cannot be deleted. Wait for it to finish and try again. When a queued job can be canceled before rendering starts, its reserved credits are refunded. Deleting an already completed job does not refund its rendering cost. ## Troubleshooting ### No renders match your filters **Cause:** The selected type, status, or search excludes all loaded jobs. **Solution:** Clear **Search by name or resolution…**, then choose **All** in both filter groups. Remember that the page loads only the latest 50 jobs for the selected type. ### My render failed **Cause:** Rendering could not finish. The render library card shows the failure state but does not display a detailed error panel or a retry button. **Solution:** If the job belongs to a batch, open [Bulk renders](https://docs.zvid.io/docs/dashboard/bulk-renders/#inspect-failed-outputs) and read its error there. For an API submission, inspect the job through [Get render job](https://docs.zvid.io/docs/endpoints/get-render-job/). Correct the original project, template, variables, or media URLs and submit a new render from that source. See [Errors and retries](https://docs.zvid.io/docs/operations/errors-and-retries/) for error-specific recovery. ### A render is taking longer than expected **Cause:** The job may still be waiting, actively processing, or showing an older state because live updates were interrupted. **Solution:** Use the **All** status filter and reload the page. Check the current state before submitting the same work again; another submission creates a separate job and can spend credits. If the job remains stuck, provide its ID and submission time at https://zvid.io/contact. ### Download URL not available or download failed **Cause:** The output is not available yet, or the browser could not retrieve it. **Solution:** Confirm the card is **Ready**, reload **Renders**, and try **Download** again. Use **View** to check whether the media URL opens. If the output also fails to open, contact https://zvid.io/contact with the job ID. ## Related documentation - [Projects and saved drafts](https://docs.zvid.io/docs/dashboard/projects-and-renders/) - [Render from a template](https://docs.zvid.io/docs/dashboard/templates/#render-from-a-template) - [Monitor a bulk render](https://docs.zvid.io/docs/dashboard/bulk-renders/#monitor-a-batch) - [Credits and billing](https://docs.zvid.io/docs/dashboard/credits-and-billing/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/webhooks-deliveries/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/webhooks-deliveries.md # Test and manage webhook deliveries Use an endpoint's controls to verify delivery, inspect failures, and manage whether it receives new render notifications. ## When to use it Use these controls after [adding a webhook](https://docs.zvid.io/docs/dashboard/webhooks/), when your receiver changes, or when notifications stop arriving. ## Send a test and inspect the result **Location:** **Dashboard → Webhooks → endpoint card**. 1. Open [Webhooks](https://app.zvid.io/webhooks) and find your endpoint by its URL or description. 2. If its status is disabled, click **Enable**. 3. Click **Send test**. 4. Wait for **Test delivery queued — check your receiver**. 5. Check your receiver, then click **Deliveries** on the endpoint card. ![Webhook endpoint controls showing subscribed events, Send test, Signing secret, Deliveries, Disable, and the delete icon.](https://docs.zvid.io/img/dashboard-guide/webhooks-endpoint-actions.png) The test sends a synthetic `render.completed` event with `test: true`, even if your endpoint subscribes only to failures. It does not create a video or spend render credits; sample media URLs and credit values in its payload are illustrative. The **Deliveries** panel shows the latest 10 records. Each record includes its status, event, attempt count, creation time, and, when available, HTTP response code and error message. To load updated results, close and reopen **Deliveries**. | Delivery status | Meaning | | --------------- | --------------------------------------------------------- | | `pending` | The event is waiting for delivery or another attempt. | | `success` | The receiver returned a successful HTTP response. | | `failed` | Delivery could not complete; inspect the displayed error. | ## View or copy the signing secret 1. Click **Signing secret** on the endpoint card. 2. Click **Copy secret** in **Signing Secret**. 3. Store it in your receiver's secret configuration, then click **Done**. See [signature verification](https://docs.zvid.io/docs/automation/webhooks/#verifying-the-signature) for the exact algorithm and request headers. ## Pause and resume notifications 1. Click **Disable** to pause an active endpoint. 2. Confirm that its status changes to disabled. 3. When the receiver is ready, click **Enable** and send a new test. Disabling preserves the URL, events, and secret and continues to use a plan slot. Delivery attempts check the endpoint's current status; a request already sent cannot be recalled. Enabling clears the consecutive-failure count but does not replay missed events. ## Delete an endpoint 1. Find the endpoint you want to remove. 2. Click its trash icon labeled **Delete webhook**. 3. Check the URL in the browser confirmation and confirm deletion. > **Deletion cannot be undone** The endpoint configuration and secret are removed. New events and queued attempts cannot be delivered through that endpoint, and its card and delivery panel disappear from the dashboard. Existing delivery records are retained internally, but the dashboard provides no restore action. Creating the endpoint again gives it a new secret that your receiver must use. Deleting an endpoint does not delete renders or recall notifications already sent. Use **Disable** when you only need a temporary pause. ## Troubleshooting ### Send test was queued, but nothing arrived **Cause:** Queued confirms submission, not successful delivery. The receiver may be unavailable, the endpoint disabled, or the URL unreachable. **Solution:** Check that the endpoint is active, open **Deliveries**, and inspect its HTTP code or error. Make the receiver return `2xx` within 10 seconds without redirects, then send another test. ### Deliveries keep failing Zvid makes up to five total attempts, including the first, with exponential backoff. A receiver must verify the signature against the original request body. Fix the response, timeout, public reachability, or signature problem reported by your receiver and delivery record. After 20 consecutive deliveries exhaust their attempts, the endpoint is automatically disabled. Fix the receiver before clicking **Enable**, then **Send test**. There is no customer dashboard replay button for old events. ## Related documentation - [Add a webhook](https://docs.zvid.io/docs/dashboard/webhooks/) - [Webhook payloads and retry behavior](https://docs.zvid.io/docs/automation/webhooks/) - [Renders](https://docs.zvid.io/docs/dashboard/projects-and-renders/) --- Canonical URL: https://docs.zvid.io/docs/dashboard/webhooks/ Markdown URL: https://docs.zvid.io/markdown/docs/dashboard/webhooks.md # Add a webhook A webhook sends an HTTP POST to your server when a render completes or fails, so your application can react without repeatedly checking its status. ## When to use it Use a registered webhook for ongoing render notifications to an application or automation. For a single render request, see [per-request callbacks](https://docs.zvid.io/docs/automation/webhooks/#per-request-webhookurl). ## Before you begin - Prepare a public HTTPS endpoint that accepts JSON POST requests and returns a `2xx` response within 10 seconds. - Implement [signature verification](https://docs.zvid.io/docs/automation/webhooks/#verifying-the-signature) in your receiver. - Check the endpoint allowance displayed on **Webhooks**. Registered endpoints count toward your plan's limit even when disabled. ## Register an endpoint **Location:** **Dashboard → Webhooks**. 1. Select **Webhooks** in the sidebar, or open [Webhooks](https://app.zvid.io/webhooks). 2. Click **Add webhook**. 3. Enter **Endpoint URL**, for example `https://example.com/hooks/zvid`, using your own receiver's URL. 4. Optionally enter **Description** to identify the integration. 5. Under **Events**, select one or both options: **Render completed** (`render.completed`) and **Render failed** (`render.failed`). Both are selected initially. 6. Click **Add webhook**. 7. In **Webhook Created**, click **Copy secret**, save the signing secret in your receiver's configuration, then click **Done**. ## What happens next Your endpoint appears with its URL, description, status, and selected events. It is active immediately. Use **Send test** to check the connection, following [Test and manage webhook deliveries](https://docs.zvid.io/docs/dashboard/webhooks-deliveries/). > **You can view the signing secret again** Click **Signing secret** on the endpoint card, then **Copy secret** in the dialog. Keep this value private. It is different from an API key. ## Important notes - Registered endpoint deliveries include an `X-Zvid-Signature` header. One-off `webhookUrl` callbacks are unsigned; do not apply the same signature assumptions to both. - The dashboard creates endpoints and controls their status. To edit an existing URL, description, or event selection, use the [Update webhook endpoint](https://docs.zvid.io/docs/endpoints/update-webhook/) API. - The receiver must use a public address. Localhost, private-network destinations, and redirects are not accepted for delivery. ## Troubleshooting ### Add webhook is disabled **Cause:** You have reached the endpoint allowance shown at the top of the page. **Solution:** Select **Upgrade to add more**, or [delete an endpoint](https://docs.zvid.io/docs/dashboard/webhooks-deliveries/#delete-an-endpoint) you no longer need. Disabling an endpoint does not free a slot. ### The endpoint cannot be created **Solution:** Check the HTTPS URL and select at least one event. Confirm that your destination is publicly accessible. If creation continues to fail, review your plan usage before trying again. ## Related documentation - [Test and manage webhook deliveries](https://docs.zvid.io/docs/dashboard/webhooks-deliveries/) - [Webhook payloads and signature verification](https://docs.zvid.io/docs/automation/webhooks/) - [Manage your subscription](https://docs.zvid.io/docs/dashboard/subscription/) --- Canonical URL: https://docs.zvid.io/docs/documentation-resources/ Markdown URL: https://docs.zvid.io/markdown/docs/documentation-resources.md # Documentation for AI assistants and tools Use the same Zvid documentation in your browser, an AI conversation, an API client or a local validator. The HTML reference and Markdown exports contain the same technical material. You do not need to execute page JavaScript to read API fields and examples. ## Start with the task | Task | Read first | | --- | --- | | Render a first video | [Quick Start](https://docs.zvid.io/docs/quick-start/), then [validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/) | | Render a still image | [Rendering images](https://docs.zvid.io/docs/rendering-images/) | | Write project JSON | [Project structure](https://docs.zvid.io/docs/structure/), then the relevant element reference | | Connect ChatGPT or another assistant | [AI assistants](https://docs.zvid.io/docs/ai-assistants/) | | Personalize one design | [Templates](https://docs.zvid.io/docs/templates/template-basics/) and [dynamic content](https://docs.zvid.io/docs/templates/dynamic-content/) | | Operate a production integration | [Render lifecycle](https://docs.zvid.io/docs/operations/render-lifecycle/), [errors and retries](https://docs.zvid.io/docs/operations/errors-and-retries/), [webhooks](https://docs.zvid.io/docs/automation/webhooks/) | ## Downloadable formats | Resource | What it contains | | --- | --- | | [LLM orientation](https://docs.zvid.io/llms.txt) | Key facts and a short route into the documentation | | [Complete Markdown index](https://docs.zvid.io/docs-index.md) | Every documentation page with its purpose and links | | [JSON index](https://docs.zvid.io/docs-index.json) | Page metadata for indexing and tool integration | | [Full documentation export](https://docs.zvid.io/llms-full.txt) | All pages with source boundaries; useful for offline ingestion | | [OpenAPI specification](https://docs.zvid.io/openapi.yaml) | Public operations, authentication, requests, responses and examples | | [Authoring project schema](https://docs.zvid.io/schemas/render-payload.schema.json) | Project input, including supported template-authoring constructs | | [Resolved project schema](https://docs.zvid.io/schemas/resolved-project.schema.json) | The concrete project after variable/iteration resolution | | [Render request schema](https://docs.zvid.io/schemas/render-request.schema.json) | The outer request: either `payload` or `template`, plus supported options | Each documentation page has a **Markdown** link. For example, the [Quick Start Markdown](https://docs.zvid.io/markdown/docs/quick-start.md) contains the guide's text and code without navigation or interactive controls. Start with individual pages instead of sending the entire export when your task needs only a few topics. ## Which contract should I use? The **request envelope** is what you POST to the API. Its `payload` is a **project**, and the project's `visuals` contain **elements**. A code block labelled “element fragment” is not a complete API request. For templates, authored input can contain placeholders, variables, conditions and iteration. These resolve before final project validation. A resolved-project schema should not be used to reject an unresolved template solely because it contains authoring constructs. Static schemas help catch structural errors. They do not replace account-specific limits, cross-field rules, media access checks or actual rendering. Use [free server validation](https://docs.zvid.io/docs/validate-and-estimate/) for the request you intend to submit. `GET /api/render/schema/api-key` supplies current, account-aware schema guidance. ## Facts to preserve when generating code - The API origin is `https://api.zvid.io`; documented operation paths include `/api/`. - REST API keys are created at [app.zvid.io/api-keys](https://app.zvid.io/api-keys) and sent as `x-api-key`. - A render request contains exactly one of `payload` or `template`. - Rendering spends credits and is asynchronous. Submission returns `jobId`; job lookup reports `state`. - Free validation and template preview do not queue render jobs. - A signed registered webhook and a one-off `webhookUrl` callback have different verification properties. - Use the response shape documented for the specific operation; list/get/create endpoints do not all share the same wrapper. ## Freshness and citations Exports are generated with the website and carry source information. Cite a page's canonical HTML URL when answering a user, and use its Markdown equivalent when clean source text is useful. If a live authenticated validation result differs from a downloaded schema, keep the returned field errors and check the current reference before submitting a paid render. The LLM index is a discovery aid. It does not grant access to an account or guarantee that a particular assistant will retrieve or cite a page. --- Canonical URL: https://docs.zvid.io/docs/editor/animations/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/animations.md # Add animations and transitions Animate how an element enters and leaves, transition between footage, or build layered motion in Design Studio. ## When to use it Use enter and exit effects for individual titles and graphics. Use video transitions to join footage, and [scene transitions](https://docs.zvid.io/docs/editor/scenes/#how-to-add-a-scene-transition) to join whole compositions. ## Before you begin Open a video project and add the element you want to animate. Give it enough time on the timeline for its enter, visible, and exit windows. ## How to animate an element **Navigation:** **Editor → select an element → Effects**. 1. Select a visual element on the stage or timeline. 2. Open **Effects → Enter animation** and choose an effect. 3. Set its **Duration**. 4. Choose an effect under **Exit animation** and set its **Duration**. 5. Play from before the element begins to after it ends. ![Zvid Effects panel for the selected demo heading with Enter animation and Exit animation controls.](https://docs.zvid.io/img/dashboard-guide/editor-effects.png) The enter effect runs from **Appears at (enterBegin)** to **Enter anim ends (enterEnd)**. The exit effect runs from **Exit anim starts (exitBegin)** to **Disappears at (exitEnd)**. Use the **Timing** tab or yellow clip handles for precise boundaries. ## How to transition between video clips 1. Add two video elements in the same scene or editing context. 2. Select the first video and open **Effects**. 3. Expand **Transition to another video**. 4. Select **Target video**, then choose **Effect**. 5. Set **Overlap duration** and review both clips' timing. 6. Preview the join, then inspect a render for the final appearance. Set **Target video** to **none** to remove the link. A video with a linked transition suppresses its separate exit animation during rendering. ## How to adjust color filters 1. Select an image, video, or SVG element. 2. Open **Effects → Color filters**. 3. Adjust **Brightness**, **Contrast**, **Saturation**, **Hue rotate**, **Blur**, **Invert**, or **Color tint**. 4. Check the stage and the final render when color matching matters. The filter controls are separate from animation timing. See the [filter reference](https://docs.zvid.io/docs/structure/properties/filter-options/) for their JSON behavior. ## How to create layered or custom motion For a visual workflow, open **Design → New design**, add layers, and use each layer's **Animation** section. [Design Studio](https://docs.zvid.io/docs/editor/text-and-design/) provides entrance, loop, and text animation presets with the controls relevant to each preset. For existing text or SVG elements with custom animation code: 1. Select the element and open **Effects → Animation code (customCode)**. 2. Edit **CSS** and, if required, **JavaScript**. 3. Set **Loop duration** and review any warning. 4. Preview the animation and render a test output before reusing it in a template. ## Important notes Browser previews approximate some transition effects. Effects marked **≈** can differ from the rendered output. Chroma key is represented by a badge in the editor. See [effect preview differences](https://docs.zvid.io/docs/structure/properties/xfade-effects/) and [preview guidance](https://docs.zvid.io/docs/editor/preview/). ## What happens next Effects remain editable in the project. [Save](https://docs.zvid.io/docs/editor/projects/) your changes and [render](https://docs.zvid.io/docs/editor/export/) when you are ready to verify the complete result. --- Canonical URL: https://docs.zvid.io/docs/editor/audio/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/audio.md # Add audio, music, and sound effects Use audio tracks to add music, narration, and short sound effects to a video project. ## When to use it Add background music across a whole movie, narration for one scene, or an effect at a specific moment. Audio has its own tracks in the timeline and does not appear as a visual element on the stage. ## Before you begin Open a **Video project**. Sign in if you want to upload audio. Select a scene for scene-local sound, or **Scenes → Global overlays** for audio across the movie. ## How to add audio **Navigation:** **Editor → Audio**. 1. Put the playhead at the start of the desired audio. 2. Use **Add audio by URL**, **Your uploads**, or **Music** to choose a source. See [Add and manage media](https://docs.zvid.io/docs/editor/media/). 3. Use **Play preview** to listen to a music track, then click the result to add it. 4. For a short cue, choose **Sound effects**, enter a search term, preview the effect, and click its add control. 5. Select the resulting audio clip in an **A** track on the timeline. ![Zvid Audio tool with Add audio by URL expanded and Music and Sound effects choices below.](https://docs.zvid.io/img/dashboard-guide/editor-audio-url.png) ## How to place and trim audio 1. Select the audio clip to open its properties. 2. Under **Timeline placement**, set **Starts at (enter)**. 3. Set **Ends at** for a fixed playback window, or enable **Match project length**. When editing a scene, this option reads **Match scene length**. 4. Under **Source trim**, set **Audio begin** and **Audio end** to select the part of the source file to use. 5. Under **Mix**, set **Volume**, **Speed (atempo)**, and **Track**. 6. Use timeline playback to listen with the other audio and footage. **Match project length** and **Match scene length** let background music follow that timeline without extending it. A shorter audio segment loops to fill its playback window. The timeline's waveform and loop markers help you see this behavior. ## How to mute or remove sound 1. To mute one audio track, set its **Volume** to `0%`. 2. To mute audio embedded in footage, select that video and set **Timing → Source trim → Volume** to `0%`. 3. To remove an audio element, select it and choose **Delete** from its element actions, or press `Delete`. The timeline **Mute (M)** control only mutes the editor preview; it does not set the output volume. Deleting a timeline element removes that use of the source, not the uploaded file. ## What happens next The audio settings are stored with the project. Check [full-movie playback](https://docs.zvid.io/docs/editor/preview/) after changing scene lengths or transitions, then [render](https://docs.zvid.io/docs/editor/export/) to verify the final mix. ## Related documentation - [Timeline and automatic duration](https://docs.zvid.io/docs/editor/timeline/) - [Scene-local and global content](https://docs.zvid.io/docs/editor/scenes/) - [Audio schema reference](https://docs.zvid.io/docs/structure/audio-elements/) --- Canonical URL: https://docs.zvid.io/docs/editor/canvas/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/canvas.md # Canvas settings and positioning Set the output frame and arrange each element's position, size, rotation, and stacking order. ## When to use it Choose a landscape, portrait, or square canvas before composing your video. Use the stage for visual placement and **Layout** fields when you need exact coordinates. ## How to change video dimensions and frame rate **Navigation:** **Editor → Project settings**; the settings appear directly in the top bar on wider screens. 1. Open **Project settings** if its controls are collapsed. 2. Choose a **Resolution preset**, or select **custom**. 3. For **custom**, enter **Width (px)** and **Height (px)**. 4. For video projects, set **Frame rate** using the control marked **fps**. 5. Choose **Background color** and **Output format**. 6. Check the stage and reposition content after a size or aspect-ratio change. ![Zvid editor project toolbar with resolution, duration, frame rate, background, and output format controls.](https://docs.zvid.io/img/dashboard-guide/editor-project-toolbar.png) Hosted video renders accept integer frame rates from 1 to 60 and output formats **mp4**, **mov**, **avi**, and **webm**. Some editor controls accept broader input, but your render must still meet hosted validation and plan limits. See [Validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/) and [resolution presets](https://docs.zvid.io/docs/structure/properties/resolution-presets/). Image projects use **png**, **jpg**, or **webp** and hide video duration and frame-rate controls. Image quality and transparency are available in the [render dialog](https://docs.zvid.io/docs/editor/export/#how-to-render-an-image). ## How to position and resize an element **Navigation:** **Editor → select an element → Design → Layout**. 1. Click or tap the element on the stage. If it is not visible at the current time, select it on the timeline. 2. Drag to move it, resize with its handles, or use the rotation handle. 3. In **Layout**, set **Position preset** for a standard position, or enter **X (anchor point)** and **Y (anchor point)**. 4. Choose **Anchor**: this is the point of the element placed at the X and Y coordinates. 5. Set **Width**, **Height**, **Rotation**, and **Opacity** as needed. 6. Use **Flip horizontally** or **Flip vertically** to mirror the element. A position preset overwrites the current X/Y coordinates. An empty width can use the source's intrinsic or measured size. For images and footage, use [Resize mode](https://docs.zvid.io/docs/editor/images/) to control fit and cropping. ## How to arrange overlapping elements 1. Select a visual element and open **Selected element actions**, or right-click it. 2. Choose **Bring forward (track +1)** or **Send backward (track −1)** in a video project. Higher visual tracks appear in front. 3. Alternatively, set **Design → Layout → Track (z)** or move the clip to another visual timeline track. 4. In an image project, use **Layers** or the **Bring forward** and **Send backward** actions to reorder the layer stack. Use Shift+click to select several stage elements and move them together. **Duplicate** creates another element; **Delete** removes the selected element from the project. Use **Undo** for an unintended local edit. ## How to manage an image project's layers 1. In an image project, open **Layers**. 2. Click a row to select its element. The top of the list is the front of the image. 3. Drag a row to reorder it, or use **Bring forward** and **Send backward**. 4. Use **Replace image (keeps size, position and effects)** on an image layer to choose a new source. 5. Use **Duplicate** to copy a layer or **Delete layer** to remove it from the composition. Removing a layer does not delete its uploaded asset. Use **Undo** to restore an unintended local layer deletion. ## How to zoom, pan, and align 1. Use **Zoom in** or **Zoom out** below the stage. Clicking the zoom percentage uses **Reset to fit**. 2. Turn on **Pan canvas** and drag to move the view around a zoomed composition. Turn it off before moving elements. 3. Enable **Toggle snapping** to align elements with stage guides. 4. Use **Toggle safe margins** to show composition guides. Canvas zoom, safe margins, and pan change the editing view. They do not change output dimensions. ## How to add an animated canvas background 1. Open **Canvas** in the tool rail. 2. Browse **Canvas presets**. Hover a tile with a preview to see it animate. 3. Click a preset to add its full-frame element. 4. Select it and use its properties to adjust timing and layout. 5. For code-level changes, open **Effects → Animation code (customCode)**. ![Zvid Canvas tool displaying the Canvas presets gallery used to add animated background elements.](https://docs.zvid.io/img/dashboard-guide/editor-canvas-presets.png) These presets add elements to the composition; they are separate from the project **Background color** setting. See [Animations and transitions](https://docs.zvid.io/docs/editor/animations/) before editing animation code. ## What happens next Your geometry and output settings are saved with the project. Check [preview](https://docs.zvid.io/docs/editor/preview/) at the new dimensions, then [render](https://docs.zvid.io/docs/editor/export/) to verify final framing and output. ## Related documentation - [Timeline and duration](https://docs.zvid.io/docs/editor/timeline/) - [Images and crop controls](https://docs.zvid.io/docs/editor/images/) - [Anchor reference](https://docs.zvid.io/docs/structure/properties/anchor/) --- Canonical URL: https://docs.zvid.io/docs/editor/export/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/export.md # Render, download, and export Render a finished video or image in the cloud, or export the project JSON for automation. ## When to use it Use **Render** to create media you can watch, download, or share. Use **Export** when you want to keep the editable payload or use it in code. **Save** keeps a cloud draft for later editing. ## Before you begin For cloud rendering, sign in and have sufficient credits. Check your dimensions, frame rate, duration, source URLs, and variable defaults. Rendering uses the same account limits and credit system as the API. ## How to render a video **Navigation:** **Editor → Render**. 1. [Preview](https://docs.zvid.io/docs/editor/preview/) the project and save it if you need to keep the editable version. 2. Click **Render**. 3. If validation errors are listed, close the dialog and fix the reported fields. 4. If prompted, click **Sign in to render** and sign in. 5. Check the output summary and available credit balance. 6. Click **Start render**. 7. Wait through **Queued** and **Rendering in the cloud**. The dialog displays progress and, once rendering is underway, reserved credits. 8. When complete, play the result and click **Download**. ![Zvid Render video dialog showing the Start render action for submitting the current project to the cloud.](https://docs.zvid.io/img/dashboard-guide/editor-render-dialog.png) The finished media also appears in [your dashboard's videos](https://app.zvid.io/videos). Click **Render again** to submit another render of the current composition. > **Estimate before spending credits** The editor dialog shows your available balance before submission; it does not show a hosted cost estimate before **Start render**. For an account-aware estimate and validation, export the JSON and use [Validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/). Closing the render dialog does not cancel the cloud job. Check the dashboard for its outcome rather than starting another job solely because the dialog was closed. ## How to render an image 1. Open an **Image project** and click **Render**. 2. In **Render image**, choose **Format**: **png**, **jpg**, or **webp**. 3. Adjust **Quality** for JPG or WebP. PNG is lossless, so its quality control is disabled. 4. Enable **Transparent background** for PNG or WebP if needed. JPG does not support transparency. 5. Click **Start render**, then **Download** when the image is ready. Image renders use one credit per output image. See [Rendering images](https://docs.zvid.io/docs/rendering-images/) for format and API details. ## How to export JSON or code **Navigation:** **Editor → Export**. 1. Click **Export**. 2. Choose a tab: - **project.json** for the composition payload. - **Node.js**, **CLI**, or **HTTP API** for a usage snippet. - **Validation** for local errors and warnings. 3. Click **Copy** to copy the selected payload or code tab. 4. Click **Download JSON** to save the project JSON file. 5. Before an automated cloud render, run [hosted validation](https://docs.zvid.io/docs/validate-and-estimate/) with your account credentials. ![Zvid export dialog with project.json, Node.js, CLI, HTTP API, and Validation tabs, plus Copy and Download JSON controls.](https://docs.zvid.io/img/dashboard-guide/editor-export-dialog.png) The local **valid for rendering** status checks editor rules. It does not prove that every remote source can be downloaded or that the payload meets your account's hosted limits. ## How to edit an element's JSON 1. Select a visual element. 2. Open **JSON → Raw element JSON** in its properties. 3. Edit the object and click **Apply**. 4. If parsing or schema errors appear, correct them before applying. 5. Use **Revert** to discard the unsubmitted JSON edits. The whole-project **Export** dialog is for copying or downloading. Use [Import](https://docs.zvid.io/docs/editor/projects/#how-to-import-project-json) to replace a project with edited JSON. Element actions also provide **Copy JSON** and **Paste element JSON**. ## Troubleshooting ### Validation errors block rendering **Cause:** Local validation found invalid project fields. **Solution:** Read the field paths and messages in the render dialog or **Export → Validation**, correct those values, and reopen **Render**. Hosted validation may report additional account or plan restrictions. ### There are not enough credits **Cause:** The account balance cannot cover the reservation required for this render. **Solution:** Read the required and available amounts in the error, check your balance in the dashboard, and adjust the project or add credits as appropriate. Return to the dialog and click **Try again**, then **Start render** after the issue is resolved. ### The render service cannot be reached **Cause:** The editor could not connect to the rendering service. **Solution:** Check your connection, then use **Try again**. If the dialog reports an expired session, sign in again. If a job was already accepted, check the dashboard before submitting a duplicate. ### A render fails after submission **Cause:** The cloud service returned a render error. **Solution:** Read **Render failed** and any field details. Correct the reported issue in the project, then retry. For account render records and troubleshooting, see [Projects and renders](https://docs.zvid.io/docs/dashboard/projects-and-renders/). ## What happens next A render produces finished media; JSON export keeps the editable composition and does not create media. Keep a saved project or JSON copy when you expect to make revisions. ## Related documentation - [Save and import projects](https://docs.zvid.io/docs/editor/projects/) - [Preview behavior](https://docs.zvid.io/docs/editor/preview/) - [Templates](https://docs.zvid.io/docs/editor/templates/) - [Media asset requirements](https://docs.zvid.io/docs/concepts/media-assets/) - [Validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/) --- Canonical URL: https://docs.zvid.io/docs/editor/images/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/images.md # Add and edit images and GIFs Place still or animated images in your composition and adjust their source, size, crop, and appearance. ## When to use it Use images for product shots, logos, backgrounds, and slides. Use GIFs in video projects for short animated visuals. ## How to add an image or GIF **Navigation:** **Editor → Images** or **GIFs**. 1. Move the playhead to the insertion point and select the scene if your video uses scenes. 2. Choose **Images** or **GIFs**, then add a [URL, upload, or stock result](https://docs.zvid.io/docs/editor/media/). 3. Select the new element on the stage or timeline. 4. In **Design → Source**, check **URL / path**. 5. Set **Resize mode** to **contain — letterbox, fully visible** to preserve the entire image, or **cover — fill frame, may crop** to fill the available frame. 6. Use **Design → Layout** for exact position and dimensions. ![Zvid Images tool showing the expanded URL entry and Add image action for inserting a hosted image.](https://docs.zvid.io/img/dashboard-guide/editor-images-url.png) Selecting a **Resize mode** fits media to the project frame and clears explicit width and height. Choose **none (use width/height)** and use **Layout** when you want independent dimensions. ## How to replace an image without rebuilding its layout 1. Select the image. 2. Open **Selected element actions**, or right-click the image. 3. Click **Replace image…**. 4. Choose a new source from the **Images** panel. Use **Cancel** if you want to keep the old source. The replacement keeps the selected image's size, position, and effects. This replacement action is available for image elements; other media sources can be edited through **URL / path**. ## How to crop or style an image 1. Select the image and open **Design**. 2. Expand **Crop**, turn on **Enable**, and set **X**, **Y**, **Width**, and **Height** for the source rectangle. 3. Expand **Rounded corners**, turn on **Enable**, and enter the corner values you need. 4. For a video project, open **Ken Burns zoom**, enable it, and adjust **Depth** to create a slow zoom. 5. Open **Effects → Color filters** to adjust brightness, contrast, saturation, hue, blur, inversion, or tint. 6. [Preview](https://docs.zvid.io/docs/editor/preview/) and inspect a [render](https://docs.zvid.io/docs/editor/export/) when the final appearance matters. To remove a green-screen color, use **Design → Chroma key → Enable**, then set **Key color**, **Similarity (0–100)**, and **Blend (0–100)**. The editor displays a badge for this effect; inspect rendered output to see the removed color. ## Timing and GIF playback In video projects, use **Timing → Timeline window** to set when the image appears and disappears. For a GIF, **Timing → Playback → Speed** controls playback speed. Image projects hide the timeline, GIF tool, and time-based settings. ## What happens next The edited asset remains a separate element that you can move, duplicate, animate, or delete. Deleting the element does not delete its source from **Your uploads**. ## Related documentation - [Media sources and uploads](https://docs.zvid.io/docs/editor/media/) - [Positioning and canvas settings](https://docs.zvid.io/docs/editor/canvas/) - [Animations](https://docs.zvid.io/docs/editor/animations/) - [Video clips](https://docs.zvid.io/docs/editor/video/) --- Canonical URL: https://docs.zvid.io/docs/editor/media/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/media.md # Add and manage media Use the **Images**, **Videos**, **Audio**, and **GIFs** tools to add assets from a URL, your uploads, or Zvid's stock library. ## When to use it Use uploaded assets for your own product photography, footage, and music. Use a reachable URL for existing hosted media, or search Zvid's stock library for additional content. ## Before you begin Account uploads require signing in. Source URLs must remain reachable by the render service when a render runs. A file that opens on your computer is not necessarily accessible to cloud rendering; see [Media assets](https://docs.zvid.io/docs/concepts/media-assets/). ## How to add media by URL **Navigation:** **Editor → Images / Videos / Audio / GIFs**. 1. Select the appropriate media tool. 2. Expand **Add image by URL**, **Add video by URL**, **Add audio by URL**, or **Add GIF by URL**. 3. Paste the source URL. If you are authoring a template, use the variable insertion control to choose a defined placeholder. 4. Click **Add image**, **Add video**, **Add audio**, or **Add GIF**. 5. Select the inserted item to adjust its properties and [timing](https://docs.zvid.io/docs/editor/timeline/). ![Zvid Images panel with Add image by URL expanded, its URL field, and Add image button.](https://docs.zvid.io/img/dashboard-guide/editor-images-url.png) Media is added to the current scene or global timeline. Check the selected scene and playhead before adding several assets. ## How to upload a file 1. Open the relevant media tool and find **Your uploads**. 2. Click **Upload**, then choose a matching file, or drop it into the uploads area. 3. Wait for the upload progress to finish. 4. Click the uploaded item to insert it. Visual assets can also be dragged onto the canvas. 5. Use **Play preview** for an audio asset before adding it. Uploads belong to your account library and can be reused in other projects. Upload acceptance and render acceptance are separate: duration, resolution, file size, and plan limits may still prevent a render. See [media asset requirements](https://docs.zvid.io/docs/concepts/media-assets/) for the current rules. ## How to search the stock library 1. Open a media tool and find **Stock**. In **Audio**, select **Music** first. 2. Enter a search term. Press Enter to run the search immediately. 3. Scroll to load additional results, or use **Load more** when shown. 4. Hover a video or GIF result to preview it; use **Play preview** for audio. 5. Click a result to add it at the playhead, or drag a visual result onto the canvas or timeline. For short audio cues, open **Audio → Sound effects**. Search the list, preview a sound, then click its add control. ## How to delete an uploaded asset 1. In **Your uploads**, find the file. 2. Click **Delete upload**. 3. Read the confirmation and confirm only when you no longer need the hosted file. > **Deleting an upload can break future renders** Deletion removes the hosted asset, not just an element in the current project. Other projects or templates referencing its URL can stop loading it once the file is gone. There is no restore action in this panel. To remove one use from your composition, select the element and choose **Delete** instead. ## Troubleshooting ### Uploads or stock results do not load **Cause:** The request failed, or uploads need an account session. **Solution:** Sign in if prompted. Use **Retry** in the affected section and check the message beside it. For a URL source, confirm that the URL returns the media itself without requiring a private browser session. ## What happens next Each visual asset becomes an editable element; audio becomes a timeline track. Continue with the controls for your asset: - [Images and GIFs](https://docs.zvid.io/docs/editor/images/) - [Video clips](https://docs.zvid.io/docs/editor/video/) - [Audio and music](https://docs.zvid.io/docs/editor/audio/) - [Position and canvas settings](https://docs.zvid.io/docs/editor/canvas/) --- Canonical URL: https://docs.zvid.io/docs/editor/overview/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/overview.md # Visual editor overview The [Zvid Editor](https://editor.zvid.io) lets you arrange media, text, and graphics on a canvas, control their timing, and render a finished video or image. ## When to use it Use the editor to create a video visually, customize a saved project, or design a reusable template for automation. A **project** is the editable composition; a **render** is the finished media file produced from it. You can edit and export JSON without signing in. Cloud saving, account uploads, saved designs, and rendering require a Zvid account. Rendering uses credits. ## How to create a project **Navigation:** [Editor](https://editor.zvid.io) → **New**. 1. Open the editor. On a compact screen, open **More editor actions** to reveal **New**. 2. Click **New**, then **Video project** or **Image project**. 3. Enter a **Project name** in the top bar. 4. Choose your [dimensions and output settings](https://docs.zvid.io/docs/editor/canvas/#how-to-change-video-dimensions-and-frame-rate). 5. Add [text](https://docs.zvid.io/docs/editor/text/), [images](https://docs.zvid.io/docs/editor/images/), or other [media](https://docs.zvid.io/docs/editor/media/). 6. [Save your project](https://docs.zvid.io/docs/editor/projects/), [preview it](https://docs.zvid.io/docs/editor/preview/), then [render](https://docs.zvid.io/docs/editor/export/) when ready. ![Zvid editor with a demo heading selected, its Design properties in the left panel, the canvas in the center, and the timeline below.](https://docs.zvid.io/img/dashboard-guide/editor-overview.png) > **Save before starting over** New replaces the current working document. Save the current project or download its JSON before starting another composition. ## Find the right part of the workspace | Area | What you use it for | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | Top bar | Name, project settings, undo/redo, examples, import, **Save**, **Render**, and **Export**. | | Tool rail | **Images**, **Videos**, **Audio**, **GIFs**, **Text**, **Design**, **Shape**, **Canvas**, **Scenes**, **Subtitles**, and **Variables**. | | Tool panel | Browse content or edit the selected element's properties. The inspector replaces the library in this panel; **Back to library** returns to it. | | Stage | View the composition and select, move, resize, and rotate elements. | | Timeline | Set when content appears, trim clips, work with audio and scenes, and control playback. | | Account menu | **My projects**, **Save as template**, **Open dashboard**, credit balance, and **Sign out**. | A visual element's properties use **Design**, **Timing**, **Effects**, and **JSON** tabs. Image projects hide timing tools and add **Layers** for arranging static elements. If a newly inserted element is selected but the library is still open, click it on the canvas or use **Selected element actions → Edit properties** to open its inspector. ## How to use a smaller screen 1. Open **Project settings** for dimensions, frame rate, background, and output format. 2. Open **More editor actions** for **Examples**, **New**, **Import**, **Shortcuts**, **Product tour**, and **Help**. 3. Swipe the bottom tool rail to reveal more tools. Tap a tool to open its panel; tap **Done** to return to the canvas. 4. Select an element and use **Selected element actions** for operations such as duplicate or delete. 5. Use **Pan canvas** to move around a zoomed canvas, then turn it off to resume editing. ## What happens next Your composition stays editable as project JSON. **Save** creates or updates a cloud draft; **Render** produces media; **Export** gives you JSON and code snippets for automation. These are separate actions. ## Help and related documentation Open **Product tour** for a walkthrough, or **Help** → **Chat with support**. You can also contact Zvid at https://zvid.io/contact. - [Save, open, and import projects](https://docs.zvid.io/docs/editor/projects/) - [Timeline and timing](https://docs.zvid.io/docs/editor/timeline/) - [Scenes and transitions](https://docs.zvid.io/docs/editor/scenes/) - [Variables and reusable templates](https://docs.zvid.io/docs/editor/templates/) - [Dashboard overview](https://docs.zvid.io/docs/dashboard/overview/) --- Canonical URL: https://docs.zvid.io/docs/editor/preview/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/preview.md # Preview your video Play your composition in the editor to check timing, content, and layout before spending credits on a render. ## When to use it Preview after editing text, moving clips, changing audio, or reordering scenes. Editor playback is a browser preview; rendering creates the final downloadable media. ## How to preview the current timeline **Navigation:** **Editor → timeline playback controls**. 1. Click the time ruler where you want playback to begin. 2. Click **Play (Space)**, or press Space outside an input field. 3. Click **Pause (Space)** to stop. 4. Use **Previous frame** and **Next frame** to inspect an exact moment. 5. Use **Previous start point (Home)** and **Next start point (End)** to jump between content starts. 6. Toggle **Loop (L)** for repeated playback and **Mute (M)** to mute the preview. ![Zvid editor timeline showing a demo text clip, playback, frame stepping, loop, mute, and time controls.](https://docs.zvid.io/img/dashboard-guide/editor-timeline-playback.png) Dragging the ruler scrubs through time. Preview mute does not change the video's exported audio; edit the element's **Volume** to change the output. ## How to preview all scenes 1. Open **Scenes → Global overlays**. 2. Above the timeline, select **full movie**. 3. Click **Play (Space)** and inspect transitions between scenes. 4. To edit a scene, select its scene card. To edit global content, switch back to **overlays**. **full movie** is read-only. The preview resolves repeated scenes and conditions using the current variable defaults; a single-scene editing view previews the first item of a repeating scene. ## How to check template content 1. Open **Variables** and set realistic default values. 2. Enable **preview** to show resolved values on the stage. 3. Check text length, image sources, conditional content, and scene count. 4. Use **full movie** when the project contains repeated or conditional scenes. Local variable preview is separate from the API's template preview, which returns resolved JSON rather than a rendered video. ## Troubleshooting ### I cannot select an element in the full movie **Cause:** **full movie** mode is read-only. **Solution:** Select the scene containing the element, or switch to **overlays** to edit global elements. ### The preview differs from the rendered video **Cause:** The browser stage approximates some rendering effects and depends on available fonts and browser media playback. **Solution:** Inspect the completed cloud render for final output. Effects marked **≈** are approximations, and chroma key is shown as a badge rather than removing the color on the stage. Check font loading and media access if content is missing. ## What happens next When timing and content look right, [save the project](https://docs.zvid.io/docs/editor/projects/) and [start a render](https://docs.zvid.io/docs/editor/export/). Previewing locally does not produce a download or consume rendering credits. ## Related documentation - [Keyboard shortcuts](https://docs.zvid.io/docs/editor/shortcuts/) - [Scenes](https://docs.zvid.io/docs/editor/scenes/) - [Variables and templates](https://docs.zvid.io/docs/editor/templates/) - [Rendering and exporting](https://docs.zvid.io/docs/editor/export/) --- Canonical URL: https://docs.zvid.io/docs/editor/projects/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/projects.md # Save, open, and import projects Save an editable cloud draft so you can return to your composition from another browser. ## When to use it Save a project while developing a video, keep a separate copy before a major change, or import JSON produced by your automation. Saving a draft does not render media or spend rendering credits. ## Before you begin Sign in to the editor with the account you use at [app.zvid.io](https://app.zvid.io). Importing and exporting JSON are also available without signing in. ## How to save a project **Navigation:** **Editor → Save**. 1. Click **Save** in the top bar. 2. For a new draft, enter **Project name** in **Save project to your account**. 3. Click **Save project** and wait for the success notification. 4. After editing an already linked project, click **Save** again to update that same cloud draft. ![Zvid editor toolbar with the Save, Render, Export, Examples, New, and Import actions.](https://docs.zvid.io/img/dashboard-guide/editor-project-toolbar.png) The editor also keeps working state in browser storage. Use a cloud save or [Download JSON](https://docs.zvid.io/docs/editor/export/#how-to-export-json-or-code) for a copy that does not depend on that browser's storage. ## How to open, rename, or copy a project **Navigation:** **Editor → account menu → My projects**. 1. Click your account avatar and select **My projects**. 2. Click a project's name or **Open**. Its content replaces the current editor document. 3. To rename a saved project, click its **Rename** icon, enter the new name, and press Enter or leave the field. 4. To save the current document as a separate draft, click **Save current as new**, enter a name, and click **Save project**. The editor's project dialog is a recent-project list. For dashboard project management, see [Projects and renders](https://docs.zvid.io/docs/dashboard/projects-and-renders/). ## How to delete a cloud draft 1. Open **account menu → My projects**. 2. Find the saved project and click **Delete**. 3. Click the same control again when its tooltip reads **Click again to delete**. > **Cloud deletion is separate from editor undo** This removes the saved draft from your account. The dialog has no restore action. Deleting the currently open draft removes its cloud link; its working content remains in the editor, where you can save it as a new draft. Do not rely on editor Undo to restore a deleted cloud record. ## How to import project JSON **Navigation:** **Editor → Import**; on compact screens, **More editor actions → Import**. 1. Save or export the current document if you need to keep it. 2. Click **Import**. 3. Click **Open .json file** to select and immediately import a file, or paste JSON into the editor. **Paste from clipboard** can fill the text area for you. 4. For pasted content, click **Import project**. 5. Review any import warning, then open **Export → Validation** to check the result. Import replaces the working document. You can use **Undo** to return to its previous content during the editing session. ## How to start from an example **Navigation:** **Editor → Examples**; on compact screens, **More editor actions → Examples**. 1. Save the current project or export its JSON before loading another composition. 2. Open **Examples**, then select **Videos** or **Images**. 3. Search for a topic and select a category, or choose **All**. 4. Hover a card to play its preview, then click the card to load it into the editor. 5. Edit the example and use **Save** to keep your composition. Cards marked **PRO** require a paid plan. If a selected category has no results, use **show all matches**; use **clear filters** to reset the search. If the gallery cannot load, click **Retry**. ## Open a project or template by link An editor link can select a saved project or template: ```text https://editor.zvid.io/?project=prj_example https://editor.zvid.io/?template=tpl_example ``` Replace the example identifier with one from your account. A project link connects **Save** to that existing draft. A template link loads the template content for editing without linking it to an existing project draft. The link itself does not grant access to another account's private content. ## Troubleshooting ### Clipboard import fails **Cause:** The browser could not read the clipboard. **Solution:** Paste directly into the JSON editor, then click **Import project**. Correct any displayed parsing error before retrying. ### Saving asks me to sign in again **Cause:** Your account session may have expired. **Solution:** Sign in when prompted, then retry **Save**. If the request still fails, keep a local copy with **Export → Download JSON** while you resolve the account or network error. ## What happens next A saved project remains an editable draft. Use [Save as template](https://docs.zvid.io/docs/editor/templates/#how-to-save-a-render-template) when you need a template identifier for repeated renders, or [render and download](https://docs.zvid.io/docs/editor/export/) a finished file. --- Canonical URL: https://docs.zvid.io/docs/editor/scenes/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/scenes.md # Manage scenes and scene transitions Divide a video into scenes, each with its own content, background, and local timeline. ## When to use it Use scenes for slideshows, story sequences, and reusable sections such as an intro, product section, and outro. Content inside a scene uses time measured from that scene's start. **Global overlays** holds elements that use the whole-movie timeline, such as a persistent logo or music track. ## Before you begin Open a **Video project**. Save a copy before changing an established project from a flat timeline to scenes or back. ## How to add and edit scenes **Navigation:** **Editor → Scenes**. 1. Click **Add scene**. 2. Select the scene card to edit its local timeline. 3. In **Scene settings**, enter **Scene id** and set **Background color** if it should differ from the project background. 4. Leave **Duration → Auto** enabled to follow scene content. To use a fixed duration, turn **Auto** off and enter a value beside **Set length**. 5. Add media, text, and audio while that scene is selected. 6. Add more scenes and repeat. ![Zvid Scenes panel showing Global overlays and the Add scene control used to organize a video into scenes.](https://docs.zvid.io/img/dashboard-guide/editor-scenes.png) The active scene's duration and the complete project duration are shown separately. If fixed-length scene content extends beyond its boundary, choose **Extend scene to fit** above the timeline. ## How to reorder or delete a scene 1. In **Scenes**, find the scene card. 2. Use **Move up** or **Move down** to change its order. 3. To remove it, click **Delete scene**. > **Scene deletion removes its contents** Delete scene immediately removes the scene and its local visual and audio elements from the working project. Use **Undo** during the editing session to recover an unintended deletion. Global overlays are separate from the deleted scene. Review transitions after reordering or deletion. ## How to add a scene transition 1. Create at least two scenes. 2. Select the earlier scene. 3. Under **Scene settings → Transition to next scene**, choose an effect. 4. Set **Transition duration** to the overlap between the two scenes. Keep it shorter than either adjacent scene. 5. Select **Global overlays**, then **full movie** above the timeline and play across the join. Choose **None (hard cut)** to remove the transition. The final scene has no outgoing scene transition. Reordering updates transitions to point to the next scene. Overlap reduces the final movie length: two five-second scenes with a one-second transition produce a nine-second sequence. Some effects use approximate browser previews; inspect a render for the final result. ## How to add content across the movie 1. Open **Scenes → Global overlays**. 2. Select **overlays** above the timeline if you are in **full movie** preview. 3. Add your logo, title, or audio track. 4. Set its timing against the entire project rather than a single scene. ## How to convert between a timeline and scenes 1. For a project without scenes but with visual or audio content, open **Scenes** and click **convert timeline → scene**. Existing visual and audio elements move into the first scene. 2. To move scene content into one root timeline, open **Scenes** and click **flatten**. 3. Review timing, layering, backgrounds, and playback after conversion. > **Flatten changes scene structure** Flatten moves local visual and audio elements to the main timeline and removes the scene containers. Scene-level backgrounds, transitions, conditions, and repeat settings are not preserved as scene behavior. Save a separate draft before flattening a composition that depends on these settings. ## What happens next The [full-movie preview](https://docs.zvid.io/docs/editor/preview/) shows the sequence and transitions. For scenes that repeat for each product or depend on a flag, use [variables, iteration, and conditions](https://docs.zvid.io/docs/editor/templates/). ## Related documentation - [Timeline and timing](https://docs.zvid.io/docs/editor/timeline/) - [Element animations and video transitions](https://docs.zvid.io/docs/editor/animations/) - [Scene schema reference](https://docs.zvid.io/docs/structure/scenes/) --- Canonical URL: https://docs.zvid.io/docs/editor/shapes/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/shapes.md # Add shapes and edit SVG Add vector graphics from the shape library and edit their markup, layout, effects, and timing. ## When to use it Use shapes for arrows, badges, frames, callouts, and decorative graphics. For visual fill and shape controls inside a layered composition, use [Design Studio](https://docs.zvid.io/docs/editor/text-and-design/). ## How to add a shape **Navigation:** **Editor → Shape**. 1. Open **Shape**. 2. Search using **Search shapes… (arrow, badge, wave)**, or select a category. 3. Browse **All**, **Basic**, **Text**, **Callouts**, **Backgrounds**, **Marketing**, **Charts**, **Frames**, **Motion**, **UI**, or **Accents**. 4. Scroll to load more results. 5. Click a shape to insert it into the current scene or canvas. ![Zvid Shape tool with its search field, shape category chips, and vector shape gallery.](https://docs.zvid.io/img/dashboard-guide/editor-shape.png) ## How to edit shape markup and layout 1. Select the shape and open **Design**. 2. In **SVG markup**, edit the SVG if you need to change its paths, fill, stroke, or other markup attributes. 3. Leave the field to apply it. The editor reports whether the markup parses as SVG. 4. Open **Layout** to change position, dimensions, rotation, opacity, or stacking order. 5. For a video project, use **Timing** and **Effects** to control its window and animations. The SVG inspector edits markup. It does not have the same visual fill and stroke controls as a shape layer in Design Studio. ## Troubleshooting ### SVG edits do not appear **Cause:** The editor rejected markup that does not parse as SVG, or a placeholder cannot resolve. **Solution:** Correct the markup in **SVG markup**, keeping a valid SVG root element. Define any variables in **Variables**, then apply the change again. Review the notification before continuing. ### The shape library fails to load **Cause:** The library request failed. **Solution:** Use **Retry** in the panel and check your connection. Existing inserted shapes remain part of the project. ## What happens next The shape is saved as an SVG element. You can duplicate or delete it with **Selected element actions** and use **Undo** for local composition edits. ## Related documentation - [Canvas layout and positioning](https://docs.zvid.io/docs/editor/canvas/) - [Design Studio](https://docs.zvid.io/docs/editor/text-and-design/) - [Animations and filters](https://docs.zvid.io/docs/editor/animations/) - [SVG schema reference](https://docs.zvid.io/docs/structure/svg-elements/) --- Canonical URL: https://docs.zvid.io/docs/editor/shortcuts/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/shortcuts.md # Editor keyboard shortcuts Use shortcuts to move through the timeline and edit a composition without repeatedly opening menus. ## How to open the shortcut reference **Navigation:** **Editor → Keyboard shortcuts (?)**; on compact screens, **More editor actions → Shortcuts**. 1. Click the keyboard icon or press **?** outside a text field. 2. Review **Playback**, **Editing**, and **View**. 3. Press Escape to close the dialog. ![Zvid Keyboard shortcuts dialog listing Playback, Editing, and View shortcuts.](https://docs.zvid.io/img/dashboard-guide/editor-shortcuts.png) ## Playback | Shortcut | Action | | ------------ | ------------------------------------------------------------------- | | Space | Play or pause. | | Left / Right | Step one frame when no element is selected; Shift steps one second. | | Home / End | Jump to the previous or next content start point. | | L | Toggle preview loop. | | M | Toggle preview mute. | With an element selected, arrow keys move it instead of stepping through frames. Use the visible **Previous frame** and **Next frame** controls when you want frame stepping without clearing the selection. ## Editing | Shortcut | Action | | -------------------------------------- | -------------------------------------------------------------- | | Ctrl+Z | Undo a local project edit. | | Ctrl+Y or Ctrl+Shift+Z | Redo a local project edit. | | Ctrl+D | Duplicate the selection. | | Delete | Delete the selection. | | S | Split the selected clip at the playhead when a split is valid. | | Shift+click | Add or remove an element from a stage selection. | | Arrow keys | Nudge a selected element by one pixel; Shift uses ten pixels. | | Double-click or Enter on selected text | Edit text in place on the canvas. | ## Project and view | Shortcut | Action | | ---------------------- | ---------------------------------------------- | | Ctrl+scroll | Zoom the canvas or timeline under the pointer. | | Ctrl+Plus / Ctrl+Minus | Zoom the timeline. | | Escape | Clear selection or close a dialog. | | ? | Open the shortcut dialog. | The editor recognizes the platform modifier key for supported commands, including Command on macOS. Keyboard actions are kept inside active dialogs; text fields retain their normal typing behavior. > **Selected text can start typing** When an editable text element is selected, printable keys such as S, L, M, or ? can start inline text editing. Clear the text selection first, or use the corresponding visible control. ## Touch and pointer alternatives Use the visible playback buttons, **Selected element actions**, **More editor actions**, and **Pan canvas** when a keyboard is unavailable. **Undo** and **Redo** are available in the top bar or compact actions menu. ## Important notes Undo applies to local composition edits. It does not restore deleted account uploads, cloud project records, or saved design-library entries. Save or export a copy before major changes. ## Related documentation - [Editor overview](https://docs.zvid.io/docs/editor/overview/) - [Timeline and timing](https://docs.zvid.io/docs/editor/timeline/) - [Canvas positioning](https://docs.zvid.io/docs/editor/canvas/) - [Save projects](https://docs.zvid.io/docs/editor/projects/) --- Canonical URL: https://docs.zvid.io/docs/editor/subtitles/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/subtitles.md # Add and edit subtitles Create word-timed captions that are drawn into your rendered video. ## When to use it Use subtitles for spoken content or synchronized captions. Use [text elements](https://docs.zvid.io/docs/editor/text/) for titles that are not part of a caption sequence. ## Before you begin Open a video project. Have a caption file ready if you want to import captions, or use the playhead to add them manually. ## How to import captions **Navigation:** **Editor → Subtitles → Captions**. 1. Click **import**. 2. Choose an SRT, VTT, ASS/SSA, or Whisper JSON file. 3. Wait for the imported-caption notification. 4. Select a caption to review its text and timing. ![Zvid Subtitles panel showing the Captions import action and Add caption at playhead control.](https://docs.zvid.io/img/dashboard-guide/editor-subtitles.png) > **Import replaces the caption list** Importing a file replaces the current inline captions. Applicable imported styles can also update subtitle styling. Save a copy first if you need to keep the old captions; use **Undo** for an unintended local import. If an imported project already references a subtitle URL, the panel displays that URL with **Load into editor**. Click it to fetch and convert those captions into an editable list. Successful loading replaces the URL reference with inline captions. There is no separate URL-entry form in this panel; see the [subtitle reference](https://docs.zvid.io/docs/structure/subtitle/) for URL-based project JSON. ## How to add and edit a caption 1. Move the playhead to the caption's start. 2. Open **Subtitles** and click **Add caption at playhead**. 3. Select the caption in the list. 4. Set **Start**, **End**, and **Text**. 5. In **Word timings**, adjust each word's text, start, and end as needed. 6. Scrub or play that interval to check synchronization. Editing **Text** redistributes word timings proportionally. **even retime** redistributes the current words across the caption; review the result before using karaoke or other word-level animations. ## How to split, merge, or delete captions 1. Select a caption. 2. Click **split** to divide it at its midpoint, or **merge ↓** to combine it with the following caption. 3. To shorten long captions in bulk, set **Max words per line**, then click **Split captions**. 4. To remove one caption, click **Delete caption** in its row. These operations change the project's caption list. **Undo** can reverse local caption edits during the editing session. ## How to style subtitles 1. Expand **Subtitle style**. 2. Choose **Mode** to control how words appear or highlight. 3. Set **Font family**, **Font size (px)**, and **Color**. 4. For modes using active-word styling, set **Active word color**, **Active word background**, and **Word box radius** as applicable. 5. Adjust **Bold**, **Italic**, caption **Background**, **Box padding**, **Box radius**, **Outline width**, and **Outline color**. 6. Set **Position**, **Vertical margin**, **Horizontal margin**, and **Text transform**. 7. Preview the captions over the actual background. The mode-specific controls appear when relevant. Inspect a rendered output when exact line wrapping, font appearance, or word highlighting matters. ## Troubleshooting ### Caption import fails **Cause:** The file could not be parsed or contained no recognized captions. **Solution:** Check the import error notification, confirm the file's format and content, and import it again. For a URL-based project, confirm that the caption URL can be fetched by the browser before using **Load into editor**. ## What happens next Captions appear in the subtitle lane and on the stage at their scheduled times. [Save](https://docs.zvid.io/docs/editor/projects/), [preview the movie](https://docs.zvid.io/docs/editor/preview/), then [render](https://docs.zvid.io/docs/editor/export/) to verify the finished captions. ## Related documentation - [Timeline and timing](https://docs.zvid.io/docs/editor/timeline/) - [Subtitle modes and schema](https://docs.zvid.io/docs/structure/subtitle/) - [Text elements](https://docs.zvid.io/docs/editor/text/) --- Canonical URL: https://docs.zvid.io/docs/editor/templates/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/templates.md # Variables and render templates Turn a composition into a reusable template by replacing changing content with variables. ## When to use it Use a template when the layout stays the same but each output needs different text, images, products, or scene selections. A variable has a name and default value; a placeholder inserts that value into the composition. ## How to define and use a variable **Navigation:** **Editor → Variables**. 1. Enter a name in **variableName**. 2. Choose **string**, **number**, **boolean**, **array**, or **object**, then click **Add**. 3. Enter a realistic default value. For arrays and objects, enter valid JSON and use **format** to format and apply it. 4. Click the variable's placeholder to copy it. 5. Paste it into a supported field, or use that field's variable insertion menu. 6. Enable **preview** in **Template variables** to show the resolved value on the stage. ![Zvid Variables panel with the preview toggle and controls to enter a variable name, choose its type, and add it.](https://docs.zvid.io/img/dashboard-guide/editor-variables.png) For example, define a string named **title** and insert {'{{title}}'} into text. Object values can use dot paths such as {'{{product.name}}'}. A placeholder used alone can retain its value type; a placeholder inside a longer sentence becomes text. ## Preview variable values {#preview-toggle} Enable **preview** in **Variables** to inspect the composition with its current defaults. The project keeps the raw placeholders when preview is enabled. Replacing a bound field with a literal value removes that field's placeholder. ## How to rename or delete variables 1. In **Variables**, inspect the usage count beside the variable. 2. Click **Rename**, enter the new name, and press Enter. 3. Update any placeholders that still use the previous name. Renaming the declaration does not automatically rewrite those references. 4. To remove a variable, click **Delete variable**. If it is still referenced, click again to confirm. Deleting a referenced variable leaves unresolved placeholders until you remove or replace them. Review **Problems** after either action. For an undefined variable, click **define** and supply a safe default. ## How to repeat a scene for each array item 1. Add an **array** variable containing the items to render. 2. Open **Scenes** and select the scene to repeat. 3. In **Scene settings → Repeat for each item (iterate)**, select the array variable. 4. Set **Item alias** if you want a name other than **item**. 5. Use placeholders such as {'{{item.title}}'} for an item's fields and {'{{index}}'} for its position. 6. Review the repeat count on the scene card, then [preview the full movie](https://docs.zvid.io/docs/editor/preview/#how-to-preview-all-scenes). The scene editing view previews the first array item. The full-movie preview expands the sequence using current defaults. ## How to show or hide content conditionally 1. Define a **boolean** variable with a safe default. 2. For a scene, open **Scenes → Scene settings → Condition**. 3. For a visual element in a video project, open **Timing → Template → Condition**. In an image project, the **Template** section is under **Design**. 4. Insert the variable placeholder. 5. Check the **on**, **off**, or **?** state indicator and resolve any missing variable. A condition resolving to false, zero, or an empty value removes that content from the render. Editing views can keep conditionally hidden content selectable; use full-movie preview to inspect the resolved sequence. ## How to save a render template **Navigation:** **Editor → account menu → Save as template**. 1. Sign in and finish defining defaults for every placeholder. 2. Click your account avatar, then **Save as template**. 3. In **Save as render template**, enter **Template name** and an optional **Description**. 4. Review the variable summary and resolve any missing-default warning. 5. Click **Save template**. 6. Copy the displayed template identifier, or follow **your dashboard** to manage it. Click **Done** to close the dialog. The editor's **Save as template** creates a new template record. It is separate from **Save**, which updates a linked project draft. Saving a template validates the composition against your plan; saving a draft does not make it render-ready. ## Troubleshooting ### A placeholder is rejected or the template will not save **Cause:** A referenced variable is missing, an expression is unsupported, JSON is invalid, or the composition fails a plan or payload check. **Solution:** Open **Variables → Problems**, define missing defaults, and fix invalid JSON or references. Use simple names or dot paths rather than arbitrary expressions. Retry **Save template** and use the field details shown in its error message to correct remaining issues. ## What happens next Use the template identifier with the API or integrations and supply per-render variable values. Creating the template does not render a video. A local preview shows values on the stage; the API's free template preview returns resolved JSON. ## Related documentation - [Template basics and API rendering](https://docs.zvid.io/docs/templates/template-basics/) - [Dynamic content](https://docs.zvid.io/docs/templates/dynamic-content/) - [Bulk rendering](https://docs.zvid.io/docs/automation/bulk-rendering/) - [Validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/) - [Save projects](https://docs.zvid.io/docs/editor/projects/) --- Canonical URL: https://docs.zvid.io/docs/editor/text-and-design/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/text-and-design.md # Build graphics in Design Studio Create a layered graphic or animated title, then insert it into your project as one editable element. ## When to use it Use Design Studio for lower thirds, product cards, gradient text, and compositions that combine several layers. Use the regular [Text tool](https://docs.zvid.io/docs/editor/text/) for a simple heading. ## How to create a design **Navigation:** **Editor → Design → New design**. 1. Open **Design**, then click **New design**. 2. Start with the **Text**, **Shape**, or **Image** layer buttons, or open **Templates** and select a starting design. 3. Click a layer to edit its properties. Drag it in the preview or enter its **X** and **Y** position. 4. Click **Canvas settings** to set the design's **Width**, **Height**, **Background**, **Font**, and **Loop** options. 5. Preview the composition in the center of the dialog. 6. Click **Insert element** to add it to the project. ![Zvid Design Studio dialog with Text, Shape, and Image layer buttons, a demo text preview, typography controls, and the Insert element action.](https://docs.zvid.io/img/dashboard-guide/editor-design-studio.png) Selecting a template replaces the composition currently open inside Design Studio. **Cancel** closes the dialog without applying its current changes to the project. ## How to edit layers 1. Select a layer in the layer list. 2. For text, edit the content, size, weight, spacing, line height, alignment, case, and wrap width. Use **Fill**, **Outline**, **Shadow**, and **Highlight pill** for styling. 3. For shapes, choose the shape and adjust its dimensions and fill. **Stroke width** or **Corner radius** appears for shapes that support it. 4. For images, enter a reachable **URL**, then set size, **Radius**, and **Fit**. 5. Use **Move up (toward front)** or **Move down (toward back)** to reorder a layer. 6. Use **Hide**, **Show**, **Duplicate**, or **Delete layer** as needed. Deleting a layer removes it from the composition you are editing. Existing project content is updated when you click **Update element**. ## How to animate a design layer 1. Select the layer. 2. In **Animation**, choose a preset. 3. Adjust the controls shown for that preset: **Duration** or **Period**, **Delay**, **Stagger**, **Easing**, or **From**. 4. Inspect the live preview. 5. Click **Insert element** for a new design, or **Update element** for an existing one. Text-specific presets can animate letters or words; **Stagger** is the delay between them. Design Studio uses one selected font family per design. Loop settings control the design's own animation; the element's [timeline window](https://docs.zvid.io/docs/editor/timeline/) controls when it is visible in the movie. ## How to reopen and reuse a design 1. Select an element created in Design Studio. 2. Open **Design → Design Studio → Edit design visually** in its properties, or choose **Edit in Design Studio** from its element actions. 3. Make your changes and click **Update element**. 4. When signed in, open the tool rail's **Design → Your designs** to reuse a saved design. 5. Click a saved design to insert another instance into the current project. For a plain text element, **Create an animated design** opens the visual design workflow. Saved designs are reusable building blocks; they are distinct from account render templates. ## How to delete a saved design 1. Open **Design → Your designs**. 2. Click **Delete design** on the item. 3. Confirm the deletion. This removes the design from your saved library. Existing project elements that already use it remain intact. There is no restore action in the saved-design panel. ## What happens next The inserted graphic behaves like one project element, with its own position and timing. Its layer data remains available for reopening in Design Studio. Save the project and [render](https://docs.zvid.io/docs/editor/export/) to inspect the final result. ## Related documentation - [Text elements](https://docs.zvid.io/docs/editor/text/) - [Shapes and SVG](https://docs.zvid.io/docs/editor/shapes/) - [Animations](https://docs.zvid.io/docs/editor/animations/) - [Variables and templates](https://docs.zvid.io/docs/editor/templates/) --- Canonical URL: https://docs.zvid.io/docs/editor/text/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/text.md # Add and style text Add editable text elements and style their content, typography, position, and timing. ## When to use it Use text elements for headings, product descriptions, labels, and end cards. For captions synchronized to speech, use [Subtitles](https://docs.zvid.io/docs/editor/subtitles/). For layered animated titles, use [Design Studio](https://docs.zvid.io/docs/editor/text-and-design/). ## How to add text **Navigation:** **Editor → Text**. 1. Select the scene and insertion time if you are working on a video. 2. Open **Text**. 3. Choose **Heading**, **Subheading**, **Body**, or **Caption**. 4. Click the inserted text on the canvas, or open **Selected element actions → Edit properties**. 5. In **Design → Content**, choose **Plain text** and replace the sample content. 6. Click outside the field to apply the change. ![Zvid Text tool showing Heading, Subheading, Body, and Caption presets.](https://docs.zvid.io/img/dashboard-guide/editor-text-tools.png) You can also double-click a text element on the canvas to edit it in place. With text selected, press `Enter` to begin inline editing. ## How to change font, color, and alignment 1. Select the text element and open **Design → Typography**. 2. Search **Font family (Google Fonts)** and select a family. Use **Show more fonts** to expand the results, or enter a family name. 3. Set **Size**, **Weight**, **Color**, and **Align**. 4. Adjust **Letter spacing** and **Line height** if needed. 5. Use **Transform** for case changes and **Decoration** for underline, line-through, or overline. 6. Open **Layout** to position, size, or rotate the element. Font preview needs access to the font service. Test your longest expected text at the intended output dimensions before rendering a template. ## How to use HTML or a variable 1. To use markup, open **Design → Content → HTML** and edit the HTML field. 2. For changing content, first define a default in **Variables**. 3. In the text or HTML field, use **Insert a variable at the cursor**, or enter a supported placeholder such as `{{title}}`. 4. Enable **Variables → preview** to check the resolved content. Use [Design Studio](https://docs.zvid.io/docs/editor/text-and-design/) when you want to build layered graphics without editing markup. For advanced HTML behavior, see [Text and HTML elements](https://docs.zvid.io/docs/structure/text-elements/). ## How to set timing and animation 1. In a video project, select the text and open **Timing**. 2. Set **Appears at (enterBegin)** and **Disappears at (exitEnd)**. 3. Open **Effects** to add an **Enter animation** or **Exit animation**. 4. Preview from before the text begins through its exit. ## What happens next The text stays editable in the project and exports as a text element. [Save](https://docs.zvid.io/docs/editor/projects/) your changes, then [preview](https://docs.zvid.io/docs/editor/preview/) or [render](https://docs.zvid.io/docs/editor/export/). ## Related documentation - [Design Studio](https://docs.zvid.io/docs/editor/text-and-design/) - [Positioning](https://docs.zvid.io/docs/editor/canvas/) - [Animations](https://docs.zvid.io/docs/editor/animations/) - [Variables](https://docs.zvid.io/docs/editor/templates/) --- Canonical URL: https://docs.zvid.io/docs/editor/timeline/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/timeline.md # Timeline and timing Use the timeline to decide when each element appears and how long the video or scene lasts. ## When to use it Use timeline controls to synchronize text, pictures, footage, sound, and captions. The **playhead** marks the time currently shown on the stage. A clip's left and right edges mark its visible or audible window. The timeline is available for video projects. Image projects use **Layers** instead. ## How to change an element's timing **Navigation:** **Editor → select a visual element → Timing → Timeline window**. 1. Select a clip on the timeline. 2. Drag the body left or right to move it in time, or drag its edges to trim its window. 3. For precise values, open **Timing**. 4. Set **Appears at (enterBegin)** and **Disappears at (exitEnd)**. 5. If the element has enter or exit animations, adjust **Enter anim ends (enterEnd)** and **Exit anim starts (exitBegin)**. 6. Scrub the ruler or play the video to check the change. ![Timing panel for a text element with appearance, disappearance, enter-animation end, and exit-animation start fields.](https://docs.zvid.io/img/dashboard-guide/editor-text-timing.png) Times are local to the selected scene when you are editing a scene. **Global overlays** uses the whole-movie timeline. Timing a clip is separate from [trimming a video source](https://docs.zvid.io/docs/editor/video/#how-to-trim-and-change-playback) or [trimming an audio source](https://docs.zvid.io/docs/editor/audio/#how-to-place-and-trim-audio). ## How to set the project length **Navigation:** **Editor → project duration in the top bar**; on compact screens, open **Project settings** first. 1. Click the project duration. 2. Enable **Fit content automatically** to follow scenes, timed elements, audio, and captions as they change. 3. Set **Minimum project length** if you want to preserve extra time beyond the content. 4. For a fixed length in a project without scenes, turn off **Fit content automatically** and set **Project length**. A project with scenes always includes the full scene sequence and uses **Minimum project length** for additional time. 5. Use **Fit duration** in the timeline to remove extra time or fit content. New video projects use automatic duration. An empty automatic timeline starts at 10 seconds until timed content determines its length. Existing documents keep their saved timing until you enable automatic fitting. Open-ended text and images follow the containing timeline. Audio with **Match project length** or **Match scene length** follows the window without extending it. For scene durations, see [Manage scenes](https://docs.zvid.io/docs/editor/scenes/). ## How to fix content that is cut off 1. Look for **content exceeds duration** above the timeline. 2. Click **Extend project to fit**, or **Extend scene to fit** when editing a scene. 3. Preview the end of the timeline again. The warning means content extends past a fixed boundary and will be cut off. Extending the boundary changes the length of the output; check your credit estimate and plan limits before rendering. ## How to organize tracks and split clips 1. Drag a visual clip to a different **V** track to change its stacking order. Higher visual track numbers render on top. 2. Use the track controls to add a visual or audio track. **Delete empty track** removes an unused extra track. 3. Select a clip and put the playhead inside it. 4. Open its element actions and click **Split at playhead**, or press S outside a field. 5. Use **Duplicate** or **Delete** from the same menu to copy or remove an element. Deleting an element changes the project; it does not delete the uploaded source. Editor **Undo** can reverse local composition edits. ## Timeline navigation | Control | Action | | ------------------------------------------------------------ | ------------------------------------------------------- | | **Play (Space)** / **Pause (Space)** | Start or stop preview playback. | | **Previous frame** / **Next frame** | Move one frame at a time. | | **Previous start point (Home)** / **Next start point (End)** | Jump between content start points. | | **Loop (L)** | Repeat preview playback. | | **Mute (M)** | Mute preview audio. | | **Timeline zoom (Ctrl+scroll)** | Change the time scale for precise edits or an overview. | | **Collapse timeline** / **Expand timeline** | Hide or restore timeline lanes. | Drag or click the time ruler to scrub. Snapping helps align clip boundaries and other timing targets; toggle it with the stage **Toggle snapping** control. Yellow clip handles adjust animation windows. ## What happens next The timeline reflects your placement and duration changes. [Preview the full movie](https://docs.zvid.io/docs/editor/preview/) when using scenes so you can check the sequence and transition overlaps. ## Related documentation - [Scenes and transitions](https://docs.zvid.io/docs/editor/scenes/) - [Enter and exit animations](https://docs.zvid.io/docs/editor/animations/) - [Timing schema reference](https://docs.zvid.io/docs/structure/common-properties/#timing) - [Keyboard shortcuts](https://docs.zvid.io/docs/editor/shortcuts/) --- Canonical URL: https://docs.zvid.io/docs/editor/video/ Markdown URL: https://docs.zvid.io/markdown/docs/editor/video.md # Add and trim video clips Add footage to a video project and choose which part plays, when it appears, and how its audio is mixed. ## When to use it Use video clips for product demonstrations, recorded footage, stock backgrounds, and montages. A **source trim** selects a portion of the original file; a **timeline window** controls where that clip appears in your composition. ## Before you begin Open a **Video project**. Video tools are hidden in image projects. Use a file you have uploaded or a URL the cloud render service can access. ## How to add and fit a clip **Navigation:** **Editor → Videos**. 1. Select the scene and place the playhead where the clip should begin. 2. Add footage from a [URL, upload, or stock result](https://docs.zvid.io/docs/editor/media/). 3. Select the clip and open **Design → Source**. 4. Set **Resize mode** to **contain — letterbox, fully visible** or **cover — fill frame, may crop**. 5. Adjust its [layout](https://docs.zvid.io/docs/editor/canvas/#how-to-position-and-resize-an-element) if it should occupy only part of the canvas. ![Zvid Videos tool with Add video by URL expanded and the Add video button below the source field.](https://docs.zvid.io/img/dashboard-guide/editor-video-url.png) ## How to trim and change playback 1. Select the clip on the stage or timeline and open **Timing**. 2. Under **Source trim**, set **Video begin** to the time in the source file where playback should start. 3. Set **Video end** to the source time where playback should stop, or leave it automatic to use the remaining source. 4. Set **Playback speed**. A value of `1` uses normal speed; changing speed changes how the selected source segment plays. 5. Set **Volume**. Use `0%` to mute the clip's own audio while keeping its picture. 6. Under **Timeline window**, set **Appears at (enterBegin)** and **Disappears at (exitEnd)** to position the clip in the composition. 7. Play across the edited boundary and check the result. ## How to split a clip 1. Select the clip and place the playhead inside it. 2. Open **Selected element actions** or right-click the clip. 3. Click **Split at playhead**, or press `S` outside a text field. 4. Select each resulting segment to edit it separately. The split action is disabled when the current selection or playhead cannot be split. Use **Undo** if the cut is in the wrong place. ## What happens next Your source file stays unchanged; the project stores the trim, timing, speed, and volume settings. To join footage with an effect, use [Transition to another video](https://docs.zvid.io/docs/editor/animations/#how-to-transition-between-video-clips), or arrange clips in [scenes](https://docs.zvid.io/docs/editor/scenes/). ## Troubleshooting ### The clip is cut off before it finishes **Cause:** Its visible window or the containing scene/project ends before the desired footage. **Solution:** Check **Video begin**, **Video end**, and **Playback speed**, then extend **Disappears at (exitEnd)** if necessary. If the timeline shows a content-length warning, use **Extend scene to fit** or **Extend project to fit**. ## Related documentation - [Timeline and timing](https://docs.zvid.io/docs/editor/timeline/) - [Audio and music](https://docs.zvid.io/docs/editor/audio/) - [Preview and render differences](https://docs.zvid.io/docs/editor/preview/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/create-api-key/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/create-api-key.md # Create API key `POST /api/api-keys` Generate a new API key for the authenticated user ## 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 <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 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 Required properties: `name`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.name` | string | Yes | Name for the API key Minimum length: `1`. Maximum length: `100`. | ### Request example: Example ```bash curl --request POST 'https://api.zvid.io/api/api-keys' \ --header "x-api-key: $ZVID_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "name": "Production automation" }' ``` ## Responses ### HTTP 201 API key created successfully Content type: `application/json`. Unknown properties are rejected. Required properties: `id`, `apiKey`, `name`, `keyPrefix`, `created`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.id` | integer | Yes | | | `response.apiKey` | string | Yes | Full API key (only shown once) | | `response.name` | string | Yes | | | `response.keyPrefix` | string | Yes | Stored prefix derived from the key | | `response.created` | boolean | Yes | | **Example response** ```json { "id": 789, "apiKey": "zvid_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "name": "Production API Key", "keyPrefix": "zvid_d3f4a1b2c3d", "created": true } ``` ### HTTP 400 Validation error or business error (e.g. duplicate name) Content type: `application/json`. Exactly one of the listed alternatives must match. **oneOf alternative 1** Schema: [ValidationError](#schema-validation-error). **oneOf alternative 2** Schema: [Error](#schema-error). ### HTTP 401 Unauthorized 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 | ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema api key created **ApiKeyCreated** Unknown properties are rejected. Required properties: `id`, `apiKey`, `name`, `keyPrefix`, `created`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ApiKeyCreated.id` | integer | Yes | | | `ApiKeyCreated.apiKey` | string | Yes | Full API key (only shown once) | | `ApiKeyCreated.name` | string | Yes | | | `ApiKeyCreated.keyPrefix` | string | Yes | Stored prefix derived from the key | | `ApiKeyCreated.created` | boolean | Yes | | ### 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 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 | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/create-project/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/create-project.md # Create project `POST /api/projects` Save a new editor draft project. Provide the project JSON under `payload` (`project` is accepted as an alias). The draft becomes editable at `https://editor.zvid.io?project=`. ## 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 <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 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 Unknown properties are rejected. Required properties: `name`. Exactly one of the listed alternatives must match. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.name` | string | Yes | Minimum length: `1`. Maximum length: `255`. | | `body.payload` | [RenderPayload](#schema-render-payload) | No | | | `body.project` | [RenderPayload](#schema-render-payload) | No | Compatibility alias for payload; never supply both. | **oneOf alternative 1** Required properties: `payload`. Type: any JSON value. Additional JSON Schema constraints: ```json { "not": { "required": [ "project" ] } } ``` **oneOf alternative 2** Required properties: `project`. Type: any JSON value. Additional JSON Schema constraints: ```json { "not": { "required": [ "payload" ] } } ``` ### Request example: Example ```bash curl --request POST 'https://api.zvid.io/api/projects' \ --header "x-api-key: $ZVID_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "name": "Greeting draft", "payload": { "name": "Hello Zvid", "width": 1280, "height": 720, "duration": 5, "visuals": [ { "type": "TEXT", "text": "Hello, Zvid!" } ] } }' ``` ## Responses ### HTTP 201 Project created Content type: `application/json`. Required properties: `project`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.project` | [Project](#schema-project) | Yes | | ### HTTP 400 Validation failed 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 Unauthorized 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 | ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 project **Project** An editor draft project, including its full project JSON. Open it in the visual editor at https://editor.zvid.io?project=<id>. Required properties: `id`, `name`, `type`, `version`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Project.id` | string | Yes | | | `Project.userId` | integer | No | | | `Project.name` | string | Yes | | | `Project.payload` | [RenderPayload](#schema-render-payload) | No | | | `Project.type` | `"video"` / `"image"` | Yes | Derived from the stored payload's type | | `Project.version` | integer | Yes | Incremented on every update | | `Project.createdAt` | string | No | Format: `date-time`. | | `Project.updatedAt` | string | No | Format: `date-time`. | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/create-template/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/create-template.md # Create template `POST /api/templates` Save a reusable project. Requires explicit positive scene durations for video templates. Defaults must resolve successfully; no video is rendered. ## 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 <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 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 Unknown properties are rejected. Required properties: `name`. Exactly one of the listed alternatives must match. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.name` | string | Yes | Minimum length: `1`. Maximum length: `255`. | | `body.description` | string | No | Maximum length: `2000`. | | `body.payload` | [RenderPayload](#schema-render-payload) | No | | | `body.project` | [RenderPayload](#schema-render-payload) | No | Compatibility alias for payload. Supply exactly one. | **oneOf alternative 1** Required properties: `payload`. Type: any JSON value. Additional JSON Schema constraints: ```json { "not": { "required": [ "project" ] } } ``` **oneOf alternative 2** Required properties: `project`. Type: any JSON value. Additional JSON Schema constraints: ```json { "not": { "required": [ "payload" ] } } ``` ### Request example: template ```bash curl --request POST 'https://api.zvid.io/api/templates' \ --header "x-api-key: $ZVID_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "name": "Greeting", "payload": { "name": "Hello Zvid", "width": 1280, "height": 720, "duration": 5, "visuals": [ { "type": "TEXT", "text": "{{title}}", "position": "center-center", "style": { "fontSize": 64, "color": "#ffffff" } } ], "backgroundColor": "#14213d", "variables": { "title": "Hello, Zvid!" } } }' ``` ## Responses ### HTTP 201 Template created Content type: `application/json`. Required properties: `template`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.template` | [Template](#schema-template) | Yes | | ### 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 create **TemplateCreate** Unknown properties are rejected. Required properties: `name`. Exactly one of the listed alternatives must match. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateCreate.name` | string | Yes | Minimum length: `1`. Maximum length: `255`. | | `TemplateCreate.description` | string | No | Maximum length: `2000`. | | `TemplateCreate.payload` | [RenderPayload](#schema-render-payload) | No | | | `TemplateCreate.project` | [RenderPayload](#schema-render-payload) | No | Compatibility alias for payload. Supply exactly one. | **oneOf alternative 1** Required properties: `payload`. Type: any JSON value. Additional JSON Schema constraints: ```json { "not": { "required": [ "project" ] } } ``` **oneOf alternative 2** Required properties: `project`. 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 template envelope **TemplateEnvelope** Required properties: `template`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateEnvelope.template` | [Template](#schema-template) | Yes | | ### schema template **Template** Required properties: `id`, `name`, `project`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Template.id` | string | Yes | Pattern: `^tpl_[A-Za-z0-9]{20}$`. | | `Template.userId` | integer | No | | | `Template.name` | string | Yes | | | `Template.description` | string OR null | No | At least one listed alternative must match. | | `Template.project` | [RenderPayload](#schema-render-payload) | Yes | | | `Template.type` | `"video"` / `"image"` | No | | | `Template.variablesSummary` | array of [TemplateVariableSummary](#schema-template-variable-summary) | No | | | `Template.version` | integer | No | | | `Template.status` | string | No | | | `Template.createdAt` | string | No | Format: `date-time`. | | `Template.updatedAt` | string | No | Format: `date-time`. | **Nested field: `Template.description`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Template.variablesSummary`** **Array item: `Template.variablesSummary[]`** Schema: [TemplateVariableSummary](#schema-template-variable-summary). ### schema template variable summary **TemplateVariableSummary** One declared template variable and whether the project uses it. Required properties: `name`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateVariableSummary.name` | string | Yes | | | `TemplateVariableSummary.type` | string | No | | | `TemplateVariableSummary.used` | boolean | No | | | `TemplateVariableSummary.default` | any JSON value | No | Declared default value (any JSON type) | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/create-upload/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/create-upload.md # Upload a file `POST /api/uploads` Multipart upload using the file field. Caps: images 25 MiB, GIFs 50 MiB, audio 100 MiB, video 300 MiB. Account storage defaults to 2 GiB but can vary; read usage.maxTotalBytes from the list response. Use upload.url as an element source. ## 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 <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 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. ### multipart/form-data Required properties: `file`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.file` | string | Yes | Format: `binary`. | ### Request example ```bash curl --request POST 'https://api.zvid.io/api/uploads' \ --header "x-api-key: $ZVID_API_KEY" \ --form "file=@/path/to/your-file.png" ``` ## Responses ### HTTP 201 New upload metadata Content type: `application/json`. Required properties: `upload`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.upload` | [Upload](#schema-upload) | Yes | | **Representative successful response response** ```json { "upload": { "id": "upl_example", "kind": "image", "fileName": "photo.jpg", "mimeType": "image/jpeg", "sizeBytes": 12345, "width": 1200, "height": 630, "duration": null, "url": "https://example.com/photo.jpg", "createdAt": "2026-09-22T12:00:00Z" } } ``` ### HTTP 400 Unsupported type or size/quota exceeded 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 | | ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema upload **Upload** Required properties: `id`, `kind`, `fileName`, `sizeBytes`, `url`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Upload.id` | string | Yes | | | `Upload.kind` | `"image"` / `"video"` / `"audio"` / `"gif"` | Yes | | | `Upload.fileName` | string | Yes | | | `Upload.mimeType` | string | No | | | `Upload.sizeBytes` | integer | Yes | | | `Upload.width` | number OR null | No | At least one listed alternative must match. | | `Upload.height` | number OR null | No | At least one listed alternative must match. | | `Upload.duration` | number OR null | No | At least one listed alternative must match. | | `Upload.url` | string | Yes | | | `Upload.createdAt` | string | No | | **Nested field: `Upload.width`** At least one listed alternative must match. **anyOf alternative 1** Type: number. **anyOf alternative 2** Type: null. **Nested field: `Upload.height`** At least one listed alternative must match. **anyOf alternative 1** Type: number. **anyOf alternative 2** Type: null. **Nested field: `Upload.duration`** At least one listed alternative must match. **anyOf alternative 1** Type: number. **anyOf alternative 2** Type: null. ### 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 | | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/create-webhook/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/create-webhook.md # Create webhook `POST /api/webhooks` Register an account endpoint. Registered deliveries are signed with HMAC-SHA256(secret, timestamp + "." + raw body). There are at most 5 total attempts (initial plus retries after 30, 60, 120 and 240 seconds), a 10-second HTTP timeout, and no redirect following. Endpoints are disabled after 20 consecutive exhausted deliveries. Creation and GET by ID return the signing secret. ## 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 <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 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 Unknown properties are rejected. Required properties: `url`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.url` | string | Yes | Maximum length: `2048`. Format: `uri`. | | `body.description` | string | No | Maximum length: `255`. | | `body.events` | array of `"render.completed"` / `"render.failed"` | No | Minimum items: `1`. Items must be unique. | **Nested field: `body.events`** Minimum items: `1`. Items must be unique. **Array item: `body.events[]`** Type: `"render.completed"` / `"render.failed"`. ### Request example: endpoint ```bash curl --request POST 'https://api.zvid.io/api/webhooks' \ --header "x-api-key: $ZVID_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "url": "https://example.com/hooks/zvid", "events": [ "render.completed", "render.failed" ] }' ``` ## Responses ### HTTP 201 Webhook created (includes secret) Content type: `application/json`. Required properties: `id`, `url`, `events`, `status`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.id` | string | Yes | | | `response.url` | string | Yes | | | `response.description` | string | No | | | `response.events` | array of `"render.completed"` / `"render.failed"` | Yes | | | `response.status` | `"active"` / `"disabled"` | Yes | | | `response.secret` | string | No | Returned by creation and single-webhook lookup; omitted from list/update responses. | | `response.consecutiveFailures` | integer | No | | | `response.lastSuccessAt` | string OR null | No | At least one listed alternative must match. | | `response.lastFailureAt` | string OR null | No | At least one listed alternative must match. | | `response.lastFailureReason` | string OR null | No | At least one listed alternative must match. | | `response.createdAt` | string | No | | | `response.updatedAt` | string | No | | **Nested field: `response.events`** **Array item: `response.events[]`** Type: `"render.completed"` / `"render.failed"`. **Nested field: `response.lastSuccessAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `response.lastFailureAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `response.lastFailureReason`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Representative successful response response** ```json { "id": "whk_abcdefghijklmnopqrst", "url": "https://example.com/hooks/zvid", "events": [ "render.completed" ], "status": "active", "secret": "whsec_example_not_a_real_secret", "created": true } ``` ### HTTP 400 Invalid URL (the hosted API requires a public HTTPS address and rejects private hosts) 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 Unauthorized 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 | ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema webhook request **WebhookRequest** Unknown properties are rejected. Required properties: `url`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `WebhookRequest.url` | string | Yes | Maximum length: `2048`. Format: `uri`. | | `WebhookRequest.description` | string | No | Maximum length: `255`. | | `WebhookRequest.events` | array of `"render.completed"` / `"render.failed"` | No | Minimum items: `1`. Items must be unique. | **Nested field: `WebhookRequest.events`** Minimum items: `1`. Items must be unique. **Array item: `WebhookRequest.events[]`** Type: `"render.completed"` / `"render.failed"`. ### schema webhook **Webhook** Required properties: `id`, `url`, `events`, `status`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Webhook.id` | string | Yes | | | `Webhook.url` | string | Yes | | | `Webhook.description` | string | No | | | `Webhook.events` | array of `"render.completed"` / `"render.failed"` | Yes | | | `Webhook.status` | `"active"` / `"disabled"` | Yes | | | `Webhook.secret` | string | No | Returned by creation and single-webhook lookup; omitted from list/update responses. | | `Webhook.consecutiveFailures` | integer | No | | | `Webhook.lastSuccessAt` | string OR null | No | At least one listed alternative must match. | | `Webhook.lastFailureAt` | string OR null | No | At least one listed alternative must match. | | `Webhook.lastFailureReason` | string OR null | No | At least one listed alternative must match. | | `Webhook.createdAt` | string | No | | | `Webhook.updatedAt` | string | No | | **Nested field: `Webhook.events`** **Array item: `Webhook.events[]`** Type: `"render.completed"` / `"render.failed"`. **Nested field: `Webhook.lastSuccessAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Webhook.lastFailureAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Webhook.lastFailureReason`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/delete-project/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/delete-project.md # Delete project `DELETE /api/projects/{id}` Permanently delete a draft project. ## 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 <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 | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request DELETE 'https://api.zvid.io/api/projects/prj_PkHw73Ul4qBgr3HJRx9t' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Project deleted Content type: `application/json`. Required properties: `deleted`, `id`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.deleted` | boolean | Yes | | | `response.id` | string | Yes | | ### HTTP 401 Unauthorized 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 Project not found 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 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/delete-template/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/delete-template.md # Archive template `DELETE /api/templates/{id}` Archive the owned template. Existing outputs remain separate resources. ## 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 <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. This operation does not take a request body. ### Request example ```bash curl --request DELETE 'https://api.zvid.io/api/templates/YOUR_ID' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `archived`, `id`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.archived` | boolean | Yes | | | `response.id` | string | Yes | | ### 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 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/delete-upload/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/delete-upload.md # Delete upload `DELETE /api/uploads/{id}` ## 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 <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 | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request DELETE 'https://api.zvid.io/api/uploads/YOUR_ID' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 File deleted Content type: `application/json`. Required properties: `deleted`, `id`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.deleted` | boolean | Yes | | | `response.id` | string | Yes | | **Representative successful response response** ```json { "deleted": true, "id": "upl_example" } ``` ### 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 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/delete-webhook/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/delete-webhook.md # Delete webhook `DELETE /api/webhooks/{id}` ## 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 <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 | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request DELETE 'https://api.zvid.io/api/webhooks/YOUR_ID' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Endpoint deleted Content type: `application/json`. Required properties: `deleted`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.deleted` | boolean | Yes | | **Representative successful response response** ```json { "deleted": true } ``` ### 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 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/duplicate-template/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/duplicate-template.md # Duplicate template `POST /api/templates/{id}/duplicate` Create an independent copy of an owned template with an automatically derived name. No request body is needed and no render is submitted. ## 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 <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. This operation does not take a request body. ### Request example ```bash curl --request POST 'https://api.zvid.io/api/templates/YOUR_ID/duplicate' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 201 Copy created Content type: `application/json`. Required properties: `template`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.template` | [Template](#schema-template) | Yes | | ### 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 envelope **TemplateEnvelope** Required properties: `template`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateEnvelope.template` | [Template](#schema-template) | Yes | | ### schema template **Template** Required properties: `id`, `name`, `project`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Template.id` | string | Yes | Pattern: `^tpl_[A-Za-z0-9]{20}$`. | | `Template.userId` | integer | No | | | `Template.name` | string | Yes | | | `Template.description` | string OR null | No | At least one listed alternative must match. | | `Template.project` | [RenderPayload](#schema-render-payload) | Yes | | | `Template.type` | `"video"` / `"image"` | No | | | `Template.variablesSummary` | array of [TemplateVariableSummary](#schema-template-variable-summary) | No | | | `Template.version` | integer | No | | | `Template.status` | string | No | | | `Template.createdAt` | string | No | Format: `date-time`. | | `Template.updatedAt` | string | No | Format: `date-time`. | **Nested field: `Template.description`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Template.variablesSummary`** **Array item: `Template.variablesSummary[]`** Schema: [TemplateVariableSummary](#schema-template-variable-summary). ### 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 template variable summary **TemplateVariableSummary** One declared template variable and whether the project uses it. Required properties: `name`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateVariableSummary.name` | string | Yes | | | `TemplateVariableSummary.type` | string | No | | | `TemplateVariableSummary.used` | boolean | No | | | `TemplateVariableSummary.default` | any JSON value | No | Declared default value (any JSON type) | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-api-key-stats/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-api-key-stats.md # Get API key statistics `GET /api/api-keys/{id}/stats` Retrieve usage statistics for a specific API key, based on audit logs, for a given timeframe ## 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 <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 | integer | Yes | API key ID | | `timeframe` | query | `"24h"` / `"7d"` / `"30d"` | No | Timeframe for statistics. Supported values: 24h, 7d, 30d (default: 30d). Default: `"30d"`. | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/api-keys/789/stats' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 API key statistics retrieved successfully Content type: `application/json`. Unknown properties are rejected. Required properties: `timeframe`, `summary`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.timeframe` | `"24h"` / `"7d"` / `"30d"` | Yes | | | `response.summary` | object | Yes | Aggregated statistics for the selected timeframe Unknown properties are rejected. Required properties: `totalRequests`, `successfulRequests`, `failedRequests`. | | `response.dailyStats` | array of object | No | Daily request breakdown over the last 30 days | **Nested field: `response.summary`** Aggregated statistics for the selected timeframe Unknown properties are rejected. Required properties: `totalRequests`, `successfulRequests`, `failedRequests`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.summary.totalRequests` | integer | Yes | | | `response.summary.successfulRequests` | integer | Yes | | | `response.summary.failedRequests` | integer | Yes | | | `response.summary.avgResponseTime` | number | No | | **Nested field: `response.dailyStats`** Daily request breakdown over the last 30 days **Array item: `response.dailyStats[]`** Unknown properties are rejected. Required properties: `date`, `requests`, `successful`, `failed`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.dailyStats[].date` | string | Yes | Format: `date`. | | `response.dailyStats[].requests` | integer | Yes | | | `response.dailyStats[].successful` | integer | Yes | | | `response.dailyStats[].failed` | integer | Yes | | **Example response** ```json { "timeframe": "30d", "summary": { "totalRequests": 1500, "successfulRequests": 1400, "failedRequests": 100, "avgResponseTime": 120.5 }, "dailyStats": [ { "date": "2025-01-01", "requests": 50, "successful": 48, "failed": 2 }, { "date": "2025-01-02", "requests": 75, "successful": 70, "failed": 5 } ] } ``` ### HTTP 401 Unauthorized 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 500 Internal 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 api key stats **ApiKeyStats** Unknown properties are rejected. Required properties: `timeframe`, `summary`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ApiKeyStats.timeframe` | `"24h"` / `"7d"` / `"30d"` | Yes | | | `ApiKeyStats.summary` | object | Yes | Aggregated statistics for the selected timeframe Unknown properties are rejected. Required properties: `totalRequests`, `successfulRequests`, `failedRequests`. | | `ApiKeyStats.dailyStats` | array of object | No | Daily request breakdown over the last 30 days | **Nested field: `ApiKeyStats.summary`** Aggregated statistics for the selected timeframe Unknown properties are rejected. Required properties: `totalRequests`, `successfulRequests`, `failedRequests`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ApiKeyStats.summary.totalRequests` | integer | Yes | | | `ApiKeyStats.summary.successfulRequests` | integer | Yes | | | `ApiKeyStats.summary.failedRequests` | integer | Yes | | | `ApiKeyStats.summary.avgResponseTime` | number | No | | **Nested field: `ApiKeyStats.dailyStats`** Daily request breakdown over the last 30 days **Array item: `ApiKeyStats.dailyStats[]`** Unknown properties are rejected. Required properties: `date`, `requests`, `successful`, `failed`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ApiKeyStats.dailyStats[].date` | string | Yes | Format: `date`. | | `ApiKeyStats.dailyStats[].requests` | integer | Yes | | | `ApiKeyStats.dailyStats[].successful` | integer | Yes | | | `ApiKeyStats.dailyStats[].failed` | integer | 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-bulk-render/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-bulk-render.md # Get bulk render status `GET /api/render/bulk/{id}` Batch status with per-item job states and results. ## 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 <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 | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/render/bulk/YOUR_ID' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Batch and child job results Content type: `application/json`. Required properties: `bulk`, `jobs`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.bulk` | object | Yes | Required properties: `id`, `status`, `totalJobs`, `counts`. | | `response.jobs` | array of [BulkJobSummary](#schema-bulk-job-summary) | Yes | | **Nested field: `response.bulk`** Required properties: `id`, `status`, `totalJobs`, `counts`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.bulk.id` | string | Yes | | | `response.bulk.name` | string / null | No | | | `response.bulk.templateId` | string / null | No | | | `response.bulk.type` | `"video"` / `"image"` | No | | | `response.bulk.status` | string | Yes | | | `response.bulk.totalJobs` | integer | Yes | | | `response.bulk.creditsReserved` | number | No | | | `response.bulk.counts` | object | Yes | Required properties: `completed`, `failed`, `pending`. | | `response.bulk.createdAt` | string | No | Format: `date-time`. | **Nested field: `response.bulk.counts`** Required properties: `completed`, `failed`, `pending`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.bulk.counts.completed` | integer | Yes | | | `response.bulk.counts.failed` | integer | Yes | | | `response.bulk.counts.pending` | integer | Yes | | **Nested field: `response.jobs`** **Array item: `response.jobs[]`** Schema: [BulkJobSummary](#schema-bulk-job-summary). **Representative successful response response** ```json { "bulk": { "id": "blk_abcdefghijklmnopqrst", "name": "One image", "templateId": null, "type": "image", "status": "completed", "totalJobs": 1, "creditsReserved": 1, "counts": { "completed": 1, "failed": 0, "pending": 0 }, "createdAt": "2026-09-22T12:00:00Z" }, "jobs": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "name": "First image", "creditsEstimated": 0, "creditsConsumed": 0, "videoDuration": null, "videoResolution": "1200x630", "outputUrl": "https://example.com/image.png", "thumbnailUrl": null, "errorMessage": null, "createdAt": "2026-09-22T12:00:00Z", "completedAt": "2026-09-22T12:00:03Z" } ] } ``` ### HTTP 401 Unauthorized 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 Batch not found No response body is defined. ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema bulk render detail **BulkRenderDetail** Required properties: `bulk`, `jobs`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderDetail.bulk` | object | Yes | Required properties: `id`, `status`, `totalJobs`, `counts`. | | `BulkRenderDetail.jobs` | array of [BulkJobSummary](#schema-bulk-job-summary) | Yes | | **Nested field: `BulkRenderDetail.bulk`** Required properties: `id`, `status`, `totalJobs`, `counts`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderDetail.bulk.id` | string | Yes | | | `BulkRenderDetail.bulk.name` | string / null | No | | | `BulkRenderDetail.bulk.templateId` | string / null | No | | | `BulkRenderDetail.bulk.type` | `"video"` / `"image"` | No | | | `BulkRenderDetail.bulk.status` | string | Yes | | | `BulkRenderDetail.bulk.totalJobs` | integer | Yes | | | `BulkRenderDetail.bulk.creditsReserved` | number | No | | | `BulkRenderDetail.bulk.counts` | object | Yes | Required properties: `completed`, `failed`, `pending`. | | `BulkRenderDetail.bulk.createdAt` | string | No | Format: `date-time`. | **Nested field: `BulkRenderDetail.bulk.counts`** Required properties: `completed`, `failed`, `pending`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderDetail.bulk.counts.completed` | integer | Yes | | | `BulkRenderDetail.bulk.counts.failed` | integer | Yes | | | `BulkRenderDetail.bulk.counts.pending` | integer | Yes | | **Nested field: `BulkRenderDetail.jobs`** **Array item: `BulkRenderDetail.jobs[]`** Schema: [BulkJobSummary](#schema-bulk-job-summary). ### schema bulk job summary **BulkJobSummary** One render job as returned by the job list endpoint. Required properties: `id`, `status`, `createdAt`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkJobSummary.id` | string | Yes | | | `BulkJobSummary.status` | `"queued"` / `"processing"` / `"completed"` / `"failed"` | Yes | Job status | | `BulkJobSummary.name` | string / null | No | | | `BulkJobSummary.creditsEstimated` | number / null | No | | | `BulkJobSummary.creditsConsumed` | number / null | No | | | `BulkJobSummary.videoDuration` | number / null | No | Output duration in seconds (null for images) | | `BulkJobSummary.videoResolution` | string / null | No | | | `BulkJobSummary.outputUrl` | string / null | No | CDN URL of the finished output (null until completed) | | `BulkJobSummary.thumbnailUrl` | string / null | No | | | `BulkJobSummary.errorMessage` | string / null | No | | | `BulkJobSummary.templateId` | string / null | No | Source template, when the job was rendered from one | | `BulkJobSummary.createdAt` | string | Yes | Format: `date-time`. | | `BulkJobSummary.completedAt` | string / null | No | Format: `date-time`. | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-credit-balance/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-credit-balance.md # Get credit balance `GET /api/credits/balance` Retrieve the authenticated user's current credit balance ## 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 <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 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/credits/balance' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Credit balance retrieved successfully Content type: `application/json`. Unknown properties are rejected. Required properties: `balance`, `subscriptionCredits`, `addonCredits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.balance` | integer | Yes | Total available credits from subscription and add-on pools | | `response.subscriptionCredits` | integer | Yes | Credits remaining in the current subscription period | | `response.addonCredits` | object | Yes | Add-on credit pool and historical add-on totals Unknown properties are rejected. Required properties: `balance`, `totalEarned`, `totalSpent`. | **Nested field: `response.addonCredits`** Add-on credit pool and historical add-on totals Unknown properties are rejected. Required properties: `balance`, `totalEarned`, `totalSpent`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.addonCredits.balance` | integer | Yes | Available add-on credits | | `response.addonCredits.totalEarned` | integer | Yes | Total add-on credits earned over all time | | `response.addonCredits.totalSpent` | integer | Yes | Total add-on credits spent over all time | **Example response** ```json { "balance": 1800, "subscriptionCredits": 1200, "addonCredits": { "balance": 600, "totalEarned": 1000, "totalSpent": 400 } } ``` ### HTTP 401 Unauthorized 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 500 Internal 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 | **Example response** ```json { "error": "Failed to get credit balance" } ``` ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema credit balance **CreditBalance** Unknown properties are rejected. Required properties: `balance`, `subscriptionCredits`, `addonCredits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `CreditBalance.balance` | integer | Yes | Total available credits from subscription and add-on pools | | `CreditBalance.subscriptionCredits` | integer | Yes | Credits remaining in the current subscription period | | `CreditBalance.addonCredits` | object | Yes | Add-on credit pool and historical add-on totals Unknown properties are rejected. Required properties: `balance`, `totalEarned`, `totalSpent`. | **Nested field: `CreditBalance.addonCredits`** Add-on credit pool and historical add-on totals Unknown properties are rejected. Required properties: `balance`, `totalEarned`, `totalSpent`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `CreditBalance.addonCredits.balance` | integer | Yes | Available add-on credits | | `CreditBalance.addonCredits.totalEarned` | integer | Yes | Total add-on credits earned over all time | | `CreditBalance.addonCredits.totalSpent` | integer | Yes | Total add-on credits spent over all time | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-library-content/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-library-content.md # Get creative library content `GET /api/library/{kind}/{slug}/content` Returns a 302 redirect to the versioned CDN JSON. Free items are public. Premium items require authentication and a paid plan. Follow the redirect and adapt the returned content; do not guess CDN paths. ## Authentication This operation also permits an unauthenticated request. - Option 2: `x-api-key: YOUR_API_KEY` (header). API key for authentication. Create one in your dashboard. - Option 3: `Authorization: Bearer YOUR_ACCESS_TOKEN`. Dashboard JWT in Authorization: Bearer <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 | | --- | --- | --- | --- | --- | | `kind` | path | `"examples"` / `"design-templates"` / `"canvas-presets"` / `"shapes"` | Yes | | | `slug` | path | string | Yes | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/library/YOUR_KIND/YOUR_SLUG/content' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 302 Fetch content from the Location URL - Header `Location`: string No response body is defined. ### 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 403 A paid plan is required for this item 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 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 | ### HTTP 503 Content unavailable 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 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-library-item/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-library-item.md # Get creative library metadata `GET /api/library/{kind}/{slug}` Read metadata for an active library item. Fetch its content separately. ## Authentication No authentication is required by this operation. ## Parameters | Parameter | Location | Type | Required | Description and constraints | | --- | --- | --- | --- | --- | | `kind` | path | `"examples"` / `"design-templates"` / `"canvas-presets"` / `"shapes"` | Yes | | | `slug` | path | string | Yes | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/library/YOUR_KIND/YOUR_SLUG' ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `kind`, `slug`, `title`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.kind` | string | Yes | | | `response.slug` | string | Yes | | | `response.title` | string | Yes | | | `response.description` | string | No | | | `response.meta` | object OR null | No | At least one listed alternative must match. | | `response.version` | integer | No | | | `response.sortOrder` | integer | No | | | `response.status` | string | No | | | `response.contentUrl` | string | No | Omitted for premium items; use the content endpoint. | | `response.contentHash` | string | No | | | `response.createdAt` | string | No | | | `response.updatedAt` | string | No | | **Nested field: `response.meta`** At least one listed alternative must match. **anyOf alternative 1** Type: object. **anyOf alternative 2** Type: null. ### 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 library item **LibraryItem** Required properties: `kind`, `slug`, `title`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `LibraryItem.kind` | string | Yes | | | `LibraryItem.slug` | string | Yes | | | `LibraryItem.title` | string | Yes | | | `LibraryItem.description` | string | No | | | `LibraryItem.meta` | object OR null | No | At least one listed alternative must match. | | `LibraryItem.version` | integer | No | | | `LibraryItem.sortOrder` | integer | No | | | `LibraryItem.status` | string | No | | | `LibraryItem.contentUrl` | string | No | Omitted for premium items; use the content endpoint. | | `LibraryItem.contentHash` | string | No | | | `LibraryItem.createdAt` | string | No | | | `LibraryItem.updatedAt` | string | No | | **Nested field: `LibraryItem.meta`** At least one listed alternative must match. **anyOf alternative 1** Type: object. **anyOf alternative 2** Type: null. ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-mcp-preferences/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-mcp-preferences.md # Get MCP preferences `GET /api/mcp/preferences` Read account defaults used by the hosted MCP service. Change defaults in the dashboard; this REST operation is read-only. ## 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 <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 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/mcp/preferences' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `defaultProfile`, `defaultMaxRenderCredits`, `profiles`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.defaultProfile` | `"creator"` / `"readonly"` / `"automation"` / `"developer"` | Yes | | | `response.defaultMaxRenderCredits` | integer | Yes | Minimum: `1`. Maximum: `10000`. | | `response.profiles` | array of object | Yes | | **Nested field: `response.profiles`** **Array item: `response.profiles[]`** Required properties: `id`, `name`, `description`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.profiles[].id` | string | Yes | | | `response.profiles[].name` | string | Yes | | | `response.profiles[].description` | string | Yes | | ### 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 mcp preferences **McpPreferences** Required properties: `defaultProfile`, `defaultMaxRenderCredits`, `profiles`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `McpPreferences.defaultProfile` | `"creator"` / `"readonly"` / `"automation"` / `"developer"` | Yes | | | `McpPreferences.defaultMaxRenderCredits` | integer | Yes | Minimum: `1`. Maximum: `10000`. | | `McpPreferences.profiles` | array of object | Yes | | **Nested field: `McpPreferences.profiles`** **Array item: `McpPreferences.profiles[]`** Required properties: `id`, `name`, `description`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `McpPreferences.profiles[].id` | string | Yes | | | `McpPreferences.profiles[].name` | string | Yes | | | `McpPreferences.profiles[].description` | string | Yes | | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-project/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-project.md # Get project `GET /api/projects/{id}` Get a draft project by ID, including its full project JSON. ## 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 <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 | Project ID ("prj_" followed by 20 characters) | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/projects/prj_PkHw73Ul4qBgr3HJRx9t' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Project found Content type: `application/json`. Required properties: `project`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.project` | [Project](#schema-project) | Yes | | ### HTTP 400 Invalid project ID 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 401 Unauthorized 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 Project not found 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 project **Project** An editor draft project, including its full project JSON. Open it in the visual editor at https://editor.zvid.io?project=<id>. Required properties: `id`, `name`, `type`, `version`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Project.id` | string | Yes | | | `Project.userId` | integer | No | | | `Project.name` | string | Yes | | | `Project.payload` | [RenderPayload](#schema-render-payload) | No | | | `Project.type` | `"video"` / `"image"` | Yes | Derived from the stored payload's type | | `Project.version` | integer | Yes | Incremented on every update | | `Project.createdAt` | string | No | Format: `date-time`. | | `Project.updatedAt` | string | No | Format: `date-time`. | ### 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 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 | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-render-element/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-render-element.md # Get element reference `GET /api/render/elements/{type}/api-key` Free element fields, constraints and examples for the account. ## 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 | Parameter | Location | Type | Required | Description and constraints | | --- | --- | --- | --- | --- | | `type` | path | `"TEXT"` / `"IMAGE"` / `"VIDEO"` / `"GIF"` / `"SVG"` / `"AUDIO"` / `"SUBTITLE"` / `"SCENE"` | Yes | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/render/elements/YOUR_TYPE/api-key' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `element`, `planLimits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.schemaVersion` | string | No | | | `response.sourceOfTruth` | string | No | | | `response.planLimits` | object | Yes | | | `response.element` | object | Yes | | ### 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 element reference **ElementReference** Required properties: `element`, `planLimits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ElementReference.schemaVersion` | string | No | | | `ElementReference.sourceOfTruth` | string | No | | | `ElementReference.planLimits` | object | Yes | | | `ElementReference.element` | object | Yes | | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-render-example/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-render-example.md # Get authoring example `GET /api/render/examples/{name}/api-key` Free named starter payload or request. ## 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 | Parameter | Location | Type | Required | Description and constraints | | --- | --- | --- | --- | --- | | `name` | path | `"promo-video"` / `"template-render"` / `"still-image"` / `"subtitles"` / `"webhook-flow"` | Yes | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/render/examples/YOUR_NAME/api-key' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `example`, `planLimits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.schemaVersion` | string | No | | | `response.sourceOfTruth` | string | No | | | `response.planLimits` | object | Yes | | | `response.example` | [AuthoringExample](#schema-authoring-example) | Yes | | ### 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 example response **ExampleResponse** Required properties: `example`, `planLimits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ExampleResponse.schemaVersion` | string | No | | | `ExampleResponse.sourceOfTruth` | string | No | | | `ExampleResponse.planLimits` | object | Yes | | | `ExampleResponse.example` | [AuthoringExample](#schema-authoring-example) | Yes | | ### schema authoring example **AuthoringExample** Required properties: `name`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `AuthoringExample.name` | string | Yes | | | `AuthoringExample.title` | string | No | | | `AuthoringExample.description` | string | No | | | `AuthoringExample.payload` | [RenderPayload](#schema-render-payload) | No | | | `AuthoringExample.request` | object | No | | ### 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-render-job/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-render-job.md # Get render job status `GET /api/jobs/{id}` Retrieve the status, progress, and result of a render job by ID. ## 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 <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 | Render job ID | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/jobs/550e8400-e29b-41d4-a716-446655440000' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Render job found Content type: `application/json`. Unknown properties are rejected. Required properties: `id`, `state`, `progress`, `ts`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.id` | string | Yes | Job identifier | | `response.state` | string / null | Yes | Queue states include waiting, prioritized, active, delayed, completed and failed. Treat completed/failed as terminal; keep polling other states with bounded backoff. | | `response.progress` | number OR object | Yes | A percentage or phase object. The structured progress field is percentage, not percent. Exactly one of the listed alternatives must match. | | `response.result` | string OR object OR null | No | Usually an object with url while queue data is available. Persisted history can return the output URL string. Handle both shapes. Exactly one of the listed alternatives must match. | | `response.failedReason` | string / null | No | Failure reason if the job failed | | `response.ts` | [RenderJobTimestamps](#schema-render-job-timestamps) | Yes | | **Nested field: `response.progress`** A percentage or phase object. The structured progress field is percentage, not percent. Exactly one of the listed alternatives must match. **oneOf alternative 1** Minimum: `0`. Maximum: `100`. Type: number. **oneOf alternative 2** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.progress.phase` | string | No | | | `response.progress.percentage` | number | No | | | `response.progress.message` | string | No | | **Nested field: `response.result`** Usually an object with url while queue data is available. Persisted history can return the output URL string. Handle both shapes. Exactly one of the listed alternatives must match. **oneOf alternative 1** Type: string. **oneOf alternative 2** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.result.url` | string | No | | | `response.result.thumbnailUrl` | string OR null | No | At least one listed alternative must match. | | `response.result.type` | `"video"` / `"image"` | No | | | `response.result.duration` | number OR null | No | At least one listed alternative must match. | | `response.result.size` | number OR null | No | At least one listed alternative must match. | **Nested field: `response.result.thumbnailUrl`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `response.result.duration`** At least one listed alternative must match. **anyOf alternative 1** Type: number. **anyOf alternative 2** Type: null. **Nested field: `response.result.size`** At least one listed alternative must match. **anyOf alternative 1** Type: number. **anyOf alternative 2** Type: null. **oneOf alternative 3** Type: null. **Example response** ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "state": "completed", "progress": 100, "result": "https://cdn.zvid.io/videos/123/video.mp4", "failedReason": null, "ts": { "created": "2025-01-20T12:00:00.000Z", "updated": "2025-01-20T12:05:00.000Z", "finished": "2025-01-20T12:05:00.000Z" } } ``` ### HTTP 400 Invalid job ID 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 | **Example response** ```json { "error": "Invalid job ID", "message": "Job ID must be a valid UUID" } ``` ### HTTP 401 Authentication required 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 | **Example response** ```json { "error": "Authentication required", "message": "Please provide an API key in the X-API-Key header or Authorization header" } ``` ### HTTP 403 Access denied – job does not belong to the authenticated user 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 | **Example response** ```json { "error": "Access denied", "message": "Access denied" } ``` ### HTTP 404 Job not found 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 | **Example response** ```json { "error": "Job not found" } ``` ### HTTP 500 Internal 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 | **Example response** ```json { "error": "server_error" } ``` ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema render job status **RenderJobStatus** Unknown properties are rejected. Required properties: `id`, `state`, `progress`, `ts`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RenderJobStatus.id` | string | Yes | Job identifier | | `RenderJobStatus.state` | string / null | Yes | Queue states include waiting, prioritized, active, delayed, completed and failed. Treat completed/failed as terminal; keep polling other states with bounded backoff. | | `RenderJobStatus.progress` | number OR object | Yes | A percentage or phase object. The structured progress field is percentage, not percent. Exactly one of the listed alternatives must match. | | `RenderJobStatus.result` | string OR object OR null | No | Usually an object with url while queue data is available. Persisted history can return the output URL string. Handle both shapes. Exactly one of the listed alternatives must match. | | `RenderJobStatus.failedReason` | string / null | No | Failure reason if the job failed | | `RenderJobStatus.ts` | [RenderJobTimestamps](#schema-render-job-timestamps) | Yes | | **Nested field: `RenderJobStatus.progress`** A percentage or phase object. The structured progress field is percentage, not percent. Exactly one of the listed alternatives must match. **oneOf alternative 1** Minimum: `0`. Maximum: `100`. Type: number. **oneOf alternative 2** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RenderJobStatus.progress.phase` | string | No | | | `RenderJobStatus.progress.percentage` | number | No | | | `RenderJobStatus.progress.message` | string | No | | **Nested field: `RenderJobStatus.result`** Usually an object with url while queue data is available. Persisted history can return the output URL string. Handle both shapes. Exactly one of the listed alternatives must match. **oneOf alternative 1** Type: string. **oneOf alternative 2** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RenderJobStatus.result.url` | string | No | | | `RenderJobStatus.result.thumbnailUrl` | string OR null | No | At least one listed alternative must match. | | `RenderJobStatus.result.type` | `"video"` / `"image"` | No | | | `RenderJobStatus.result.duration` | number OR null | No | At least one listed alternative must match. | | `RenderJobStatus.result.size` | number OR null | No | At least one listed alternative must match. | **Nested field: `RenderJobStatus.result.thumbnailUrl`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `RenderJobStatus.result.duration`** At least one listed alternative must match. **anyOf alternative 1** Type: number. **anyOf alternative 2** Type: null. **Nested field: `RenderJobStatus.result.size`** At least one listed alternative must match. **anyOf alternative 1** Type: number. **anyOf alternative 2** Type: null. **oneOf alternative 3** Type: null. ### schema render job timestamps **RenderJobTimestamps** Unknown properties are rejected. Required properties: `created`, `updated`, `finished`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RenderJobTimestamps.created` | integer OR string OR null | Yes | Exactly one of the listed alternatives must match. | | `RenderJobTimestamps.updated` | integer OR string OR null | Yes | Exactly one of the listed alternatives must match. | | `RenderJobTimestamps.finished` | integer OR string OR null | Yes | Exactly one of the listed alternatives must match. | **Nested field: `RenderJobTimestamps.created`** Exactly one of the listed alternatives must match. **oneOf alternative 1** Unix milliseconds while job data is in the queue. Type: integer. **oneOf alternative 2** ISO date-time when served from persisted job history. Format: `date-time`. Type: string. **oneOf alternative 3** Type: null. **Nested field: `RenderJobTimestamps.updated`** Exactly one of the listed alternatives must match. **oneOf alternative 1** Unix milliseconds while job data is in the queue. Type: integer. **oneOf alternative 2** ISO date-time when served from persisted job history. Format: `date-time`. Type: string. **oneOf alternative 3** Type: null. **Nested field: `RenderJobTimestamps.finished`** Exactly one of the listed alternatives must match. **oneOf alternative 1** Unix milliseconds while job data is in the queue. Type: integer. **oneOf alternative 2** ISO date-time when served from persisted job history. Format: `date-time`. Type: string. **oneOf alternative 3** Type: null. ### 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 | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-render-schema/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-render-schema.md # Get live render schema `GET /api/render/schema/api-key` Free, account-aware resolved-project or render-request schema with validation notes and authoring guidance. Template input additionally supports variables, iterate and condition; use the authoring schema and validation endpoint for those fields. ## 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 | Parameter | Location | Type | Required | Description and constraints | | --- | --- | --- | --- | --- | | `target` | query | `"project"` / `"render-request"` | No | Default: `"project"`. | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/render/schema/api-key' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `schemaVersion`, `planLimits`, `target`, `jsonSchema`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.schemaVersion` | string | Yes | | | `response.sourceOfTruth` | string | No | | | `response.planLimits` | object | Yes | | | `response.target` | `"project"` / `"render-request"` | Yes | | | `response.jsonSchema` | object | Yes | | | `response.validationNotes` | array of string | No | | | `response.authoringGuidelines` | array of string | No | | | `response.authoringWorkflow` | array of string | No | | **Nested field: `response.validationNotes`** **Array item: `response.validationNotes[]`** Type: string. **Nested field: `response.authoringGuidelines`** **Array item: `response.authoringGuidelines[]`** Type: string. **Nested field: `response.authoringWorkflow`** **Array item: `response.authoringWorkflow[]`** Type: string. ### 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 authoring schema response **AuthoringSchemaResponse** Required properties: `schemaVersion`, `planLimits`, `target`, `jsonSchema`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `AuthoringSchemaResponse.schemaVersion` | string | Yes | | | `AuthoringSchemaResponse.sourceOfTruth` | string | No | | | `AuthoringSchemaResponse.planLimits` | object | Yes | | | `AuthoringSchemaResponse.target` | `"project"` / `"render-request"` | Yes | | | `AuthoringSchemaResponse.jsonSchema` | object | Yes | | | `AuthoringSchemaResponse.validationNotes` | array of string | No | | | `AuthoringSchemaResponse.authoringGuidelines` | array of string | No | | | `AuthoringSchemaResponse.authoringWorkflow` | array of string | No | | **Nested field: `AuthoringSchemaResponse.validationNotes`** **Array item: `AuthoringSchemaResponse.validationNotes[]`** Type: string. **Nested field: `AuthoringSchemaResponse.authoringGuidelines`** **Array item: `AuthoringSchemaResponse.authoringGuidelines[]`** Type: string. **Nested field: `AuthoringSchemaResponse.authoringWorkflow`** **Array item: `AuthoringSchemaResponse.authoringWorkflow[]`** Type: string. ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-template/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-template.md # Get template `GET /api/templates/{id}` Fetch a stored template, including its `variables` defaults. Templates are authored in the editor (Save → Save as template) or via the dashboard. ## 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 <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 | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/templates/YOUR_ID' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Stored template, including template.project Content type: `application/json`. Required properties: `template`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.template` | [Template](#schema-template) | Yes | | **Representative successful response response** ```json { "template": { "id": "tpl_abcdefghijklmnopqrst", "name": "Greeting", "project": { "variables": { "title": "Hello" }, "visuals": [ { "type": "TEXT", "text": "{{title}}" } ] } } } ``` ### HTTP 401 Unauthorized 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 Template not found No response body is defined. ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema template envelope **TemplateEnvelope** Required properties: `template`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateEnvelope.template` | [Template](#schema-template) | Yes | | ### schema template **Template** Required properties: `id`, `name`, `project`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Template.id` | string | Yes | Pattern: `^tpl_[A-Za-z0-9]{20}$`. | | `Template.userId` | integer | No | | | `Template.name` | string | Yes | | | `Template.description` | string OR null | No | At least one listed alternative must match. | | `Template.project` | [RenderPayload](#schema-render-payload) | Yes | | | `Template.type` | `"video"` / `"image"` | No | | | `Template.variablesSummary` | array of [TemplateVariableSummary](#schema-template-variable-summary) | No | | | `Template.version` | integer | No | | | `Template.status` | string | No | | | `Template.createdAt` | string | No | Format: `date-time`. | | `Template.updatedAt` | string | No | Format: `date-time`. | **Nested field: `Template.description`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Template.variablesSummary`** **Array item: `Template.variablesSummary[]`** Schema: [TemplateVariableSummary](#schema-template-variable-summary). ### 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 template variable summary **TemplateVariableSummary** One declared template variable and whether the project uses it. Required properties: `name`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateVariableSummary.name` | string | Yes | | | `TemplateVariableSummary.type` | string | No | | | `TemplateVariableSummary.used` | boolean | No | | | `TemplateVariableSummary.default` | any JSON value | No | Declared default value (any JSON type) | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-transactions/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-transactions.md # Get transaction history `GET /api/credits/transactions` Retrieve paginated list of credit transactions ## 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 <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 | | --- | --- | --- | --- | --- | | `page` | query | integer | No | Page number Default: `1`. Minimum: `1`. | | `limit` | query | integer | No | Number of items per page Default: `20`. Minimum: `1`. Maximum: `100`. | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/credits/transactions' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Transaction history retrieved successfully Content type: `application/json`. Unknown properties are rejected. Required properties: `transactions`, `pagination`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.transactions` | array of [Transaction](#schema-transaction) | Yes | | | `response.pagination` | object | Yes | Unknown properties are rejected. Required properties: `page`, `limit`, `total`, `totalPages`, `hasNext`, `hasPrev`. | **Nested field: `response.transactions`** **Array item: `response.transactions[]`** Schema: [Transaction](#schema-transaction). **Nested field: `response.pagination`** Unknown properties are rejected. Required properties: `page`, `limit`, `total`, `totalPages`, `hasNext`, `hasPrev`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.pagination.page` | integer | Yes | | | `response.pagination.limit` | integer | Yes | | | `response.pagination.total` | integer | Yes | | | `response.pagination.totalPages` | integer | Yes | | | `response.pagination.hasNext` | boolean | Yes | Whether there are more pages after the current one | | `response.pagination.hasPrev` | boolean | Yes | Whether there are pages before the current one | ### HTTP 400 Invalid query parameters 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 Unauthorized 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 500 Internal 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 transaction list **TransactionList** Unknown properties are rejected. Required properties: `transactions`, `pagination`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TransactionList.transactions` | array of [Transaction](#schema-transaction) | Yes | | | `TransactionList.pagination` | object | Yes | Unknown properties are rejected. Required properties: `page`, `limit`, `total`, `totalPages`, `hasNext`, `hasPrev`. | **Nested field: `TransactionList.transactions`** **Array item: `TransactionList.transactions[]`** Schema: [Transaction](#schema-transaction). **Nested field: `TransactionList.pagination`** Unknown properties are rejected. Required properties: `page`, `limit`, `total`, `totalPages`, `hasNext`, `hasPrev`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TransactionList.pagination.page` | integer | Yes | | | `TransactionList.pagination.limit` | integer | Yes | | | `TransactionList.pagination.total` | integer | Yes | | | `TransactionList.pagination.totalPages` | integer | Yes | | | `TransactionList.pagination.hasNext` | boolean | Yes | Whether there are more pages after the current one | | `TransactionList.pagination.hasPrev` | boolean | Yes | Whether there are pages before the current one | ### schema transaction **Transaction** Unknown properties are rejected. Required properties: `id`, `userId`, `amount`, `transactionType`, `createdAt`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Transaction.id` | integer | Yes | | | `Transaction.userId` | integer | Yes | | | `Transaction.amount` | integer | Yes | Transaction amount (positive for additions, negative for deductions) | | `Transaction.transactionType` | `"earned"` / `"spent"` / `"admin_adjustment"` / `"credit_pack"` / `"revoke"` / `"plan_change_adjustment"` | Yes | Type of transaction | | `Transaction.creditSource` | `"addon"` / `"subscription"` | No | Credit pool affected by this transaction | | `Transaction.subscriptionCreditPeriodId` | integer / null | No | Subscription credit period touched by this transaction, if applicable | | `Transaction.jobId` | string / null | No | Related job ID (if applicable) Format: `uuid`. | | `Transaction.description` | string | No | | | `Transaction.metadata` | object OR string OR null | No | Exactly one of the listed alternatives must match. | | `Transaction.createdAt` | string | Yes | Format: `date-time`. | **Nested field: `Transaction.metadata`** Exactly one of the listed alternatives must match. **oneOf alternative 1** Type: object. **oneOf alternative 2** Type: string. **oneOf alternative 3** Type: null. ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-usage-stats/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-usage-stats.md # Get usage statistics `GET /api/credits/usage-stats` Retrieve credit usage statistics for a specified timeframe ## 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 <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 | | --- | --- | --- | --- | --- | | `timeframe` | query | `"24h"` / `"7d"` / `"30d"` | No | Timeframe for statistics (24h, 7d, or 30d) Default: `"30d"`. | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/credits/usage-stats' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Usage statistics retrieved successfully Content type: `application/json`. Unknown properties are rejected. Required properties: `timeframe`, `totalSpent`, `totalEarned`, `totalRefunded`, `dailyUsage`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.timeframe` | string | Yes | The timeframe for the statistics | | `response.totalSpent` | integer | Yes | Total credits spent in the timeframe | | `response.totalEarned` | integer | Yes | Total credits earned in the timeframe | | `response.totalRefunded` | integer | Yes | Total credits refunded in the timeframe | | `response.dailyUsage` | array of object | Yes | Daily breakdown of credit usage | **Nested field: `response.dailyUsage`** Daily breakdown of credit usage **Array item: `response.dailyUsage[]`** Unknown properties are rejected. Required properties: `date`, `spent`, `earned`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.dailyUsage[].date` | string | Yes | Date of the usage Format: `date`. | | `response.dailyUsage[].spent` | integer | Yes | Credits spent on this day | | `response.dailyUsage[].earned` | integer | Yes | Credits earned on this day | **Example response** ```json { "timeframe": "30d", "totalSpent": 500, "totalEarned": 1000, "totalRefunded": 50, "dailyUsage": [ { "date": "2025-01-15", "spent": 100, "earned": 0 }, { "date": "2025-01-14", "spent": 75, "earned": 500 } ] } ``` ### HTTP 401 Unauthorized 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 500 Internal 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 usage stats **UsageStats** Unknown properties are rejected. Required properties: `timeframe`, `totalSpent`, `totalEarned`, `totalRefunded`, `dailyUsage`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `UsageStats.timeframe` | string | Yes | The timeframe for the statistics | | `UsageStats.totalSpent` | integer | Yes | Total credits spent in the timeframe | | `UsageStats.totalEarned` | integer | Yes | Total credits earned in the timeframe | | `UsageStats.totalRefunded` | integer | Yes | Total credits refunded in the timeframe | | `UsageStats.dailyUsage` | array of object | Yes | Daily breakdown of credit usage | **Nested field: `UsageStats.dailyUsage`** Daily breakdown of credit usage **Array item: `UsageStats.dailyUsage[]`** Unknown properties are rejected. Required properties: `date`, `spent`, `earned`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `UsageStats.dailyUsage[].date` | string | Yes | Date of the usage Format: `date`. | | `UsageStats.dailyUsage[].spent` | integer | Yes | Credits spent on this day | | `UsageStats.dailyUsage[].earned` | integer | Yes | Credits earned on this day | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-user-profile/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-user-profile.md # Get user profile `GET /api/user/profile` Retrieve the authenticated user's profile information and credit balance ## 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 <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 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/user/profile' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 User profile retrieved successfully Content type: `application/json`. Unknown properties are rejected. Required properties: `user`, `credits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.user` | object | Yes | Unknown properties are rejected. Required properties: `id`, `email`, `emailVerified`, `createdAt`. | | `response.credits` | [CreditBalance](#schema-credit-balance) | Yes | | **Nested field: `response.user`** Unknown properties are rejected. Required properties: `id`, `email`, `emailVerified`, `createdAt`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.user.id` | integer | Yes | | | `response.user.email` | string | Yes | Format: `email`. | | `response.user.firstName` | string | No | | | `response.user.lastName` | string | No | | | `response.user.emailVerified` | boolean | Yes | | | `response.user.profileCompleted` | boolean | No | | | `response.user.pendingRedirect` | string / null | No | | | `response.user.isAdmin` | boolean | No | | | `response.user.createdAt` | string | Yes | Format: `date-time`. | **Example response** ```json { "user": { "id": 123, "email": "user@example.com", "firstName": "John", "lastName": "Doe", "emailVerified": true, "profileCompleted": true, "pendingRedirect": null, "isAdmin": false, "createdAt": "2025-01-01T00:00:00.000Z" }, "credits": { "balance": 1800, "subscriptionCredits": 1200, "addonCredits": { "balance": 600, "totalEarned": 1000, "totalSpent": 400 } } } ``` ### HTTP 401 Unauthorized - Invalid or missing API key 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 | **Example response** ```json { "error": "Unauthorized", "message": "Invalid or missing API key" } ``` ### HTTP 404 User not found Content type: `application/json`. Unknown properties are rejected. Required properties: `error`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.error` | string | Yes | Error type | **Example response** ```json { "error": "User not found" } ``` ### HTTP 500 Internal 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 | **Example response** ```json { "error": "Failed to get user profile" } ``` ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema user profile **UserProfile** Unknown properties are rejected. Required properties: `user`, `credits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `UserProfile.user` | object | Yes | Unknown properties are rejected. Required properties: `id`, `email`, `emailVerified`, `createdAt`. | | `UserProfile.credits` | [CreditBalance](#schema-credit-balance) | Yes | | **Nested field: `UserProfile.user`** Unknown properties are rejected. Required properties: `id`, `email`, `emailVerified`, `createdAt`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `UserProfile.user.id` | integer | Yes | | | `UserProfile.user.email` | string | Yes | Format: `email`. | | `UserProfile.user.firstName` | string | No | | | `UserProfile.user.lastName` | string | No | | | `UserProfile.user.emailVerified` | boolean | Yes | | | `UserProfile.user.profileCompleted` | boolean | No | | | `UserProfile.user.pendingRedirect` | string / null | No | | | `UserProfile.user.isAdmin` | boolean | No | | | `UserProfile.user.createdAt` | string | Yes | Format: `date-time`. | ### schema credit balance **CreditBalance** Unknown properties are rejected. Required properties: `balance`, `subscriptionCredits`, `addonCredits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `CreditBalance.balance` | integer | Yes | Total available credits from subscription and add-on pools | | `CreditBalance.subscriptionCredits` | integer | Yes | Credits remaining in the current subscription period | | `CreditBalance.addonCredits` | object | Yes | Add-on credit pool and historical add-on totals Unknown properties are rejected. Required properties: `balance`, `totalEarned`, `totalSpent`. | **Nested field: `CreditBalance.addonCredits`** Add-on credit pool and historical add-on totals Unknown properties are rejected. Required properties: `balance`, `totalEarned`, `totalSpent`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `CreditBalance.addonCredits.balance` | integer | Yes | Available add-on credits | | `CreditBalance.addonCredits.totalEarned` | integer | Yes | Total add-on credits earned over all time | | `CreditBalance.addonCredits.totalSpent` | integer | Yes | Total add-on credits spent over all time | ### 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 user not found error **UserNotFoundError** Unknown properties are rejected. Required properties: `error`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `UserNotFoundError.error` | string | Yes | Error type | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/get-webhook/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/get-webhook.md # Get webhook `GET /api/webhooks/{id}` ## 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 <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 | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/webhooks/YOUR_ID' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Endpoint including signing secret Content type: `application/json`. Required properties: `webhook`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.webhook` | [Webhook](#schema-webhook) | Yes | | **Representative successful response response** ```json { "webhook": { "id": "whk_abcdefghijklmnopqrst", "url": "https://example.com/hooks/zvid", "events": [ "render.completed" ], "status": "active", "secret": "whsec_example_not_a_real_secret" } } ``` ### HTTP 404 Not found No response body is defined. ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema webhook **Webhook** Required properties: `id`, `url`, `events`, `status`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Webhook.id` | string | Yes | | | `Webhook.url` | string | Yes | | | `Webhook.description` | string | No | | | `Webhook.events` | array of `"render.completed"` / `"render.failed"` | Yes | | | `Webhook.status` | `"active"` / `"disabled"` | Yes | | | `Webhook.secret` | string | No | Returned by creation and single-webhook lookup; omitted from list/update responses. | | `Webhook.consecutiveFailures` | integer | No | | | `Webhook.lastSuccessAt` | string OR null | No | At least one listed alternative must match. | | `Webhook.lastFailureAt` | string OR null | No | At least one listed alternative must match. | | `Webhook.lastFailureReason` | string OR null | No | At least one listed alternative must match. | | `Webhook.createdAt` | string | No | | | `Webhook.updatedAt` | string | No | | **Nested field: `Webhook.events`** **Array item: `Webhook.events[]`** Type: `"render.completed"` / `"render.failed"`. **Nested field: `Webhook.lastSuccessAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Webhook.lastFailureAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Webhook.lastFailureReason`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-api-keys/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-api-keys.md # List API keys `GET /api/api-keys` Retrieve all active API keys for the authenticated user ## 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 <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 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/api-keys' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 API keys retrieved successfully Content type: `application/json`. Unknown properties are rejected. Required properties: `apiKeys`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.apiKeys` | array of [ApiKey](#schema-api-key) | Yes | | **Nested field: `response.apiKeys`** **Array item: `response.apiKeys[]`** Schema: [ApiKey](#schema-api-key). **Example response** ```json { "apiKeys": [ { "id": 789, "name": "Production API Key", "keyPrefix": "zvid_d3f4a1b2c3d", "createdAt": "2025-01-01T00:00:00.000Z", "lastUsedAt": "2025-01-15T10:30:00.000Z", "requestsCount": 150, "isActive": true } ] } ``` ### HTTP 401 Unauthorized 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 500 Internal 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 api key list **ApiKeyList** Unknown properties are rejected. Required properties: `apiKeys`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ApiKeyList.apiKeys` | array of [ApiKey](#schema-api-key) | Yes | | **Nested field: `ApiKeyList.apiKeys`** **Array item: `ApiKeyList.apiKeys[]`** Schema: [ApiKey](#schema-api-key). ### schema api key **ApiKey** Unknown properties are rejected. Required properties: `id`, `name`, `keyPrefix`, `createdAt`, `requestsCount`, `isActive`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ApiKey.id` | integer | Yes | | | `ApiKey.name` | string | Yes | | | `ApiKey.keyPrefix` | string | Yes | Stored prefix of the key (first characters, used for identification) | | `ApiKey.createdAt` | string | Yes | Format: `date-time`. | | `ApiKey.lastUsedAt` | string / null | No | Format: `date-time`. | | `ApiKey.requestsCount` | integer | Yes | Total number of requests made with this key | | `ApiKey.isActive` | boolean | Yes | Whether the key is active (this list only returns active keys) | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-bulk-renders/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-bulk-renders.md # List bulk renders `GET /api/render/bulk` List the account's bulk render batches with per-state job counts. ## 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 <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 | | --- | --- | --- | --- | --- | | `page` | query | integer | No | Default: `1`. Minimum: `1`. | | `limit` | query | integer | No | Default: `20`. Minimum: `1`. Maximum: `100`. | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/render/bulk' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Bulk render batches for the authenticated account Content type: `application/json`. Required properties: `bulks`, `pagination`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.bulks` | array of [BulkRenderSummary](#schema-bulk-render-summary) | Yes | | | `response.pagination` | [Pagination](#schema-pagination) | Yes | | **Nested field: `response.bulks`** **Array item: `response.bulks[]`** Schema: [BulkRenderSummary](#schema-bulk-render-summary). **Example response** ```json { "bulks": [ { "id": "blk_CL7FunS6EL0C7qeES6y4", "name": "spring-campaign", "templateId": "tpl_rAequJHzUETcLAMfrezk", "type": "video", "status": "completed", "totalJobs": 25, "creditsReserved": 150, "counts": { "completed": 24, "failed": 1, "pending": 0 }, "createdAt": "2026-07-05T10:39:27.000Z" } ], "pagination": { "page": 1, "limit": 20, "total": 5, "totalPages": 1, "hasNext": false, "hasPrev": false } } ``` ### HTTP 401 Unauthorized 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 | ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema bulk render list **BulkRenderList** Required properties: `bulks`, `pagination`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderList.bulks` | array of [BulkRenderSummary](#schema-bulk-render-summary) | Yes | | | `BulkRenderList.pagination` | [Pagination](#schema-pagination) | Yes | | **Nested field: `BulkRenderList.bulks`** **Array item: `BulkRenderList.bulks[]`** Schema: [BulkRenderSummary](#schema-bulk-render-summary). ### schema bulk render summary **BulkRenderSummary** One bulk render batch as returned by the bulk list endpoint. Required properties: `id`, `type`, `status`, `totalJobs`, `createdAt`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderSummary.id` | string | Yes | | | `BulkRenderSummary.name` | string / null | No | | | `BulkRenderSummary.templateId` | string / null | No | | | `BulkRenderSummary.type` | `"video"` / `"image"` | Yes | | | `BulkRenderSummary.status` | string | Yes | | | `BulkRenderSummary.totalJobs` | integer | Yes | | | `BulkRenderSummary.creditsReserved` | number | No | | | `BulkRenderSummary.counts` | object | No | Per-state job counts inside the batch | | `BulkRenderSummary.createdAt` | string | Yes | Format: `date-time`. | **Nested field: `BulkRenderSummary.counts`** Per-state job counts inside the batch | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderSummary.counts.completed` | integer | No | | | `BulkRenderSummary.counts.failed` | integer | No | | | `BulkRenderSummary.counts.pending` | integer | No | | ### schema pagination **Pagination** Standard pagination envelope for list endpoints. Required properties: `page`, `limit`, `total`, `totalPages`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Pagination.page` | integer | Yes | | | `Pagination.limit` | integer | Yes | | | `Pagination.total` | integer | Yes | | | `Pagination.totalPages` | integer | Yes | | | `Pagination.hasNext` | boolean | No | Whether there are more pages after the current one | | `Pagination.hasPrev` | boolean | No | Whether there are pages before the current one | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-library-items/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-library-items.md # List creative library items `GET /api/library/{kind}` Public curated assets. Supply limit for paginated search; offset requires limit. q filters the paginated request. Without pagination, the whole current list is returned. Premium metadata omits contentUrl. ## Authentication No authentication is required by this operation. ## Parameters | Parameter | Location | Type | Required | Description and constraints | | --- | --- | --- | --- | --- | | `kind` | path | `"examples"` / `"design-templates"` / `"canvas-presets"` / `"shapes"` | Yes | | | `limit` | query | integer | No | Minimum: `1`. Maximum: `100`. | | `offset` | query | integer | No | Default: `0`. Minimum: `0`. Maximum: `100000`. | | `q` | query | string | No | Maximum length: `80`. | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/library/YOUR_KIND' ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `kind`, `items`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.kind` | string | Yes | | | `response.items` | array of [LibraryItem](#schema-library-item) | Yes | | | `response.total` | integer | No | | | `response.limit` | integer | No | | | `response.offset` | integer | No | | | `response.hasMore` | boolean | No | | **Nested field: `response.items`** **Array item: `response.items[]`** Schema: [LibraryItem](#schema-library-item). ### 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 library list **LibraryList** Required properties: `kind`, `items`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `LibraryList.kind` | string | Yes | | | `LibraryList.items` | array of [LibraryItem](#schema-library-item) | Yes | | | `LibraryList.total` | integer | No | | | `LibraryList.limit` | integer | No | | | `LibraryList.offset` | integer | No | | | `LibraryList.hasMore` | boolean | No | | **Nested field: `LibraryList.items`** **Array item: `LibraryList.items[]`** Schema: [LibraryItem](#schema-library-item). ### schema library item **LibraryItem** Required properties: `kind`, `slug`, `title`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `LibraryItem.kind` | string | Yes | | | `LibraryItem.slug` | string | Yes | | | `LibraryItem.title` | string | Yes | | | `LibraryItem.description` | string | No | | | `LibraryItem.meta` | object OR null | No | At least one listed alternative must match. | | `LibraryItem.version` | integer | No | | | `LibraryItem.sortOrder` | integer | No | | | `LibraryItem.status` | string | No | | | `LibraryItem.contentUrl` | string | No | Omitted for premium items; use the content endpoint. | | `LibraryItem.contentHash` | string | No | | | `LibraryItem.createdAt` | string | No | | | `LibraryItem.updatedAt` | string | No | | **Nested field: `LibraryItem.meta`** At least one listed alternative must match. **anyOf alternative 1** Type: object. **anyOf alternative 2** Type: null. ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-projects/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-projects.md # List projects `GET /api/projects` List the account's editor draft projects. The full project JSON is not included — fetch a project by ID for that. ## 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 <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 | | --- | --- | --- | --- | --- | | `page` | query | integer | No | Default: `1`. Minimum: `1`. | | `limit` | query | integer | No | Default: `20`. Minimum: `1`. Maximum: `100`. | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/projects' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Projects for the authenticated account Content type: `application/json`. Required properties: `projects`, `pagination`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.projects` | array of [ProjectSummary](#schema-project-summary) | Yes | | | `response.pagination` | [Pagination](#schema-pagination) | Yes | | **Nested field: `response.projects`** **Array item: `response.projects[]`** Schema: [ProjectSummary](#schema-project-summary). **Example response** ```json { "projects": [ { "id": "prj_PkHw73Ul4qBgr3HJRx9t", "userId": 5, "name": "promo-draft", "type": "video", "version": 2, "createdAt": "2026-07-04T13:56:27.000Z", "updatedAt": "2026-07-04T14:23:33.000Z" } ], "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 } } ``` ### HTTP 401 Unauthorized 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 | ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema project list **ProjectList** Required properties: `projects`, `pagination`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ProjectList.projects` | array of [ProjectSummary](#schema-project-summary) | Yes | | | `ProjectList.pagination` | [Pagination](#schema-pagination) | Yes | | **Nested field: `ProjectList.projects`** **Array item: `ProjectList.projects[]`** Schema: [ProjectSummary](#schema-project-summary). ### schema project summary **ProjectSummary** One project as returned by the project list endpoint (no project JSON — fetch the project by ID for that). Required properties: `id`, `name`, `type`, `version`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ProjectSummary.id` | string | Yes | | | `ProjectSummary.userId` | integer | No | | | `ProjectSummary.name` | string | Yes | | | `ProjectSummary.type` | `"video"` / `"image"` | Yes | | | `ProjectSummary.version` | integer | Yes | | | `ProjectSummary.createdAt` | string | No | Format: `date-time`. | | `ProjectSummary.updatedAt` | string | No | Format: `date-time`. | ### schema pagination **Pagination** Standard pagination envelope for list endpoints. Required properties: `page`, `limit`, `total`, `totalPages`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Pagination.page` | integer | Yes | | | `Pagination.limit` | integer | Yes | | | `Pagination.total` | integer | Yes | | | `Pagination.totalPages` | integer | Yes | | | `Pagination.hasNext` | boolean | No | Whether there are more pages after the current one | | `Pagination.hasPrev` | boolean | No | Whether there are pages before the current one | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-render-elements/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-render-elements.md # List supported elements `GET /api/render/elements/api-key` Free element catalogue and layout guidance. ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/render/elements/api-key' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `elements`, `planLimits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.schemaVersion` | string | No | | | `response.sourceOfTruth` | string | No | | | `response.planLimits` | object | Yes | | | `response.elements` | array of object | Yes | | | `response.notes` | array of string | No | | | `response.authoringGuidelines` | array of string | No | | **Nested field: `response.elements`** **Array item: `response.elements[]`** Type: object. **Nested field: `response.notes`** **Array item: `response.notes[]`** Type: string. **Nested field: `response.authoringGuidelines`** **Array item: `response.authoringGuidelines[]`** Type: string. ### 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 element list **ElementList** Required properties: `elements`, `planLimits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ElementList.schemaVersion` | string | No | | | `ElementList.sourceOfTruth` | string | No | | | `ElementList.planLimits` | object | Yes | | | `ElementList.elements` | array of object | Yes | | | `ElementList.notes` | array of string | No | | | `ElementList.authoringGuidelines` | array of string | No | | **Nested field: `ElementList.elements`** **Array item: `ElementList.elements[]`** Type: object. **Nested field: `ElementList.notes`** **Array item: `ElementList.notes[]`** Type: string. **Nested field: `ElementList.authoringGuidelines`** **Array item: `ElementList.authoringGuidelines[]`** Type: string. ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-render-examples/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-render-examples.md # List authoring examples `GET /api/render/examples/api-key` Free generic starter requests. Use the creative library for designed examples, then adapt and validate. ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/render/examples/api-key' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `examples`, `planLimits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.schemaVersion` | string | No | | | `response.sourceOfTruth` | string | No | | | `response.planLimits` | object | Yes | | | `response.examples` | array of [AuthoringExample](#schema-authoring-example) | Yes | | | `response.authoringWorkflow` | array of string | No | | **Nested field: `response.examples`** **Array item: `response.examples[]`** Schema: [AuthoringExample](#schema-authoring-example). **Nested field: `response.authoringWorkflow`** **Array item: `response.authoringWorkflow[]`** Type: string. ### 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 example list **ExampleList** Required properties: `examples`, `planLimits`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `ExampleList.schemaVersion` | string | No | | | `ExampleList.sourceOfTruth` | string | No | | | `ExampleList.planLimits` | object | Yes | | | `ExampleList.examples` | array of [AuthoringExample](#schema-authoring-example) | Yes | | | `ExampleList.authoringWorkflow` | array of string | No | | **Nested field: `ExampleList.examples`** **Array item: `ExampleList.examples[]`** Schema: [AuthoringExample](#schema-authoring-example). **Nested field: `ExampleList.authoringWorkflow`** **Array item: `ExampleList.authoringWorkflow[]`** Type: string. ### schema authoring example **AuthoringExample** Required properties: `name`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `AuthoringExample.name` | string | Yes | | | `AuthoringExample.title` | string | No | | | `AuthoringExample.description` | string | No | | | `AuthoringExample.payload` | [RenderPayload](#schema-render-payload) | No | | | `AuthoringExample.request` | object | No | | ### 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-render-jobs/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-render-jobs.md # List render jobs `GET /api/jobs` List the account's render jobs, newest first. Filter by output type with `type`. ## 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 <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 | | --- | --- | --- | --- | --- | | `page` | query | integer | No | Page number (default 1) Default: `1`. Minimum: `1`. | | `limit` | query | integer | No | Items per page (default 20, max 100) Default: `20`. Minimum: `1`. Maximum: `100`. | | `type` | query | `"video"` / `"image"` | No | Only return renders of this type | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/jobs' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Render jobs for the authenticated account Content type: `application/json`. Required properties: `jobs`, `pagination`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.jobs` | array of [JobSummary](#schema-job-summary) | Yes | | | `response.pagination` | [Pagination](#schema-pagination) | Yes | | **Nested field: `response.jobs`** **Array item: `response.jobs[]`** Schema: [JobSummary](#schema-job-summary). **Example response** ```json { "jobs": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "jobType": "image", "name": "product-card", "creditsEstimated": 1, "creditsConsumed": 1, "videoDuration": null, "videoResolution": "1080x1080", "outputUrl": "https://cdn.zvid.io/images/5/product-card.png", "thumbnailUrl": "https://cdn.zvid.io/images/5/product-card.png", "errorMessage": null, "templateId": null, "createdAt": "2026-07-08T02:51:43.000Z", "completedAt": "2026-07-08T02:51:45.000Z" } ], "pagination": { "page": 1, "limit": 20, "total": 13, "totalPages": 1, "hasNext": false, "hasPrev": false } } ``` ### HTTP 401 Authentication required 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 500 Internal 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 job list **JobList** Required properties: `jobs`, `pagination`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `JobList.jobs` | array of [JobSummary](#schema-job-summary) | Yes | | | `JobList.pagination` | [Pagination](#schema-pagination) | Yes | | **Nested field: `JobList.jobs`** **Array item: `JobList.jobs[]`** Schema: [JobSummary](#schema-job-summary). ### schema job summary **JobSummary** One render job as returned by the job list endpoint. Required properties: `id`, `status`, `jobType`, `createdAt`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `JobSummary.id` | string | Yes | | | `JobSummary.status` | `"queued"` / `"processing"` / `"completed"` / `"failed"` | Yes | Job status | | `JobSummary.jobType` | `"video"` / `"image"` | Yes | | | `JobSummary.name` | string / null | No | | | `JobSummary.creditsEstimated` | number / null | No | | | `JobSummary.creditsConsumed` | number / null | No | | | `JobSummary.videoDuration` | number / null | No | Output duration in seconds (null for images) | | `JobSummary.videoResolution` | string / null | No | | | `JobSummary.outputUrl` | string / null | No | CDN URL of the finished output (null until completed) | | `JobSummary.thumbnailUrl` | string / null | No | | | `JobSummary.errorMessage` | string / null | No | | | `JobSummary.templateId` | string / null | No | Source template, when the job was rendered from one | | `JobSummary.createdAt` | string | Yes | Format: `date-time`. | | `JobSummary.completedAt` | string / null | No | Format: `date-time`. | ### schema pagination **Pagination** Standard pagination envelope for list endpoints. Required properties: `page`, `limit`, `total`, `totalPages`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Pagination.page` | integer | Yes | | | `Pagination.limit` | integer | Yes | | | `Pagination.total` | integer | Yes | | | `Pagination.totalPages` | integer | Yes | | | `Pagination.hasNext` | boolean | No | Whether there are more pages after the current one | | `Pagination.hasPrev` | boolean | No | Whether there are pages before the current one | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-templates/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-templates.md # List templates `GET /api/templates` List the account's templates with their declared variables (`variablesSummary`). The full project JSON is not included — fetch a template by ID for that. ## 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 <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 | | --- | --- | --- | --- | --- | | `page` | query | integer | No | Default: `1`. Minimum: `1`. | | `limit` | query | integer | No | Default: `20`. Minimum: `1`. Maximum: `100`. | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/templates' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Templates for the authenticated account Content type: `application/json`. Required properties: `templates`, `pagination`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.templates` | array of [TemplateSummary](#schema-template-summary) | Yes | | | `response.pagination` | [Pagination](#schema-pagination) | Yes | | **Nested field: `response.templates`** **Array item: `response.templates[]`** Schema: [TemplateSummary](#schema-template-summary). **Example response** ```json { "templates": [ { "id": "tpl_rAequJHzUETcLAMfrezk", "userId": 5, "name": "Product promo", "description": "16:9 promo with title/brand variables", "type": "video", "variablesSummary": [ { "name": "title", "type": "string", "used": true, "default": "Hello world" } ], "version": 3, "status": "active", "createdAt": "2026-07-01T09:00:00.000Z", "updatedAt": "2026-07-06T18:30:00.000Z" } ], "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 } } ``` ### HTTP 401 Unauthorized 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 | ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema template list **TemplateList** Required properties: `templates`, `pagination`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateList.templates` | array of [TemplateSummary](#schema-template-summary) | Yes | | | `TemplateList.pagination` | [Pagination](#schema-pagination) | Yes | | **Nested field: `TemplateList.templates`** **Array item: `TemplateList.templates[]`** Schema: [TemplateSummary](#schema-template-summary). ### schema template summary **TemplateSummary** One template as returned by the template list endpoint (no project JSON — fetch the template by ID for that). Required properties: `id`, `name`, `type`, `version`, `status`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateSummary.id` | string | Yes | | | `TemplateSummary.userId` | integer | No | | | `TemplateSummary.name` | string | Yes | | | `TemplateSummary.description` | string / null | No | | | `TemplateSummary.type` | `"video"` / `"image"` | Yes | | | `TemplateSummary.variablesSummary` | array of [TemplateVariableSummary](#schema-template-variable-summary) | No | | | `TemplateSummary.version` | integer | Yes | | | `TemplateSummary.status` | `"active"` / `"archived"` | Yes | | | `TemplateSummary.createdAt` | string | No | Format: `date-time`. | | `TemplateSummary.updatedAt` | string | No | Format: `date-time`. | **Nested field: `TemplateSummary.variablesSummary`** **Array item: `TemplateSummary.variablesSummary[]`** Schema: [TemplateVariableSummary](#schema-template-variable-summary). ### schema template variable summary **TemplateVariableSummary** One declared template variable and whether the project uses it. Required properties: `name`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateVariableSummary.name` | string | Yes | | | `TemplateVariableSummary.type` | string | No | | | `TemplateVariableSummary.used` | boolean | No | | | `TemplateVariableSummary.default` | any JSON value | No | Declared default value (any JSON type) | ### schema pagination **Pagination** Standard pagination envelope for list endpoints. Required properties: `page`, `limit`, `total`, `totalPages`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Pagination.page` | integer | Yes | | | `Pagination.limit` | integer | Yes | | | `Pagination.total` | integer | Yes | | | `Pagination.totalPages` | integer | Yes | | | `Pagination.hasNext` | boolean | No | Whether there are more pages after the current one | | `Pagination.hasPrev` | boolean | No | Whether there are pages before the current one | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-uploads/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-uploads.md # List uploads `GET /api/uploads` Your hosted media library — files usable directly as element `src` URLs. ## 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 <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 | | --- | --- | --- | --- | --- | | `type` | query | `"image"` / `"video"` / `"audio"` / `"gif"` | No | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/uploads' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Uploaded files and storage usage Content type: `application/json`. Required properties: `uploads`, `usage`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.uploads` | array of [Upload](#schema-upload) | Yes | | | `response.usage` | [UploadUsage](#schema-upload-usage) | Yes | | **Nested field: `response.uploads`** **Array item: `response.uploads[]`** Schema: [Upload](#schema-upload). **Representative successful response response** ```json { "uploads": [], "usage": { "files": 0, "usedBytes": 0, "maxTotalBytes": 2147483648 } } ``` ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema upload list **UploadList** Required properties: `uploads`, `usage`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `UploadList.uploads` | array of [Upload](#schema-upload) | Yes | | | `UploadList.usage` | [UploadUsage](#schema-upload-usage) | Yes | | **Nested field: `UploadList.uploads`** **Array item: `UploadList.uploads[]`** Schema: [Upload](#schema-upload). ### schema upload **Upload** Required properties: `id`, `kind`, `fileName`, `sizeBytes`, `url`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Upload.id` | string | Yes | | | `Upload.kind` | `"image"` / `"video"` / `"audio"` / `"gif"` | Yes | | | `Upload.fileName` | string | Yes | | | `Upload.mimeType` | string | No | | | `Upload.sizeBytes` | integer | Yes | | | `Upload.width` | number OR null | No | At least one listed alternative must match. | | `Upload.height` | number OR null | No | At least one listed alternative must match. | | `Upload.duration` | number OR null | No | At least one listed alternative must match. | | `Upload.url` | string | Yes | | | `Upload.createdAt` | string | No | | **Nested field: `Upload.width`** At least one listed alternative must match. **anyOf alternative 1** Type: number. **anyOf alternative 2** Type: null. **Nested field: `Upload.height`** At least one listed alternative must match. **anyOf alternative 1** Type: number. **anyOf alternative 2** Type: null. **Nested field: `Upload.duration`** At least one listed alternative must match. **anyOf alternative 1** Type: number. **anyOf alternative 2** Type: null. ### schema upload usage **UploadUsage** Required properties: `files`, `usedBytes`, `maxTotalBytes`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `UploadUsage.files` | integer | Yes | | | `UploadUsage.usedBytes` | integer | Yes | | | `UploadUsage.maxTotalBytes` | integer | Yes | | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-webhook-deliveries/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-webhook-deliveries.md # List webhook deliveries `GET /api/webhooks/{id}/deliveries` Delivery log with status, attempts, and response codes. ## 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 <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 | | | `page` | query | integer | No | | | `limit` | query | integer | No | Default: `20`. Minimum: `1`. Maximum: `100`. | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/webhooks/YOUR_ID/deliveries' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Delivery attempts Content type: `application/json`. Required properties: `deliveries`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.deliveries` | array of [WebhookDelivery](#schema-webhook-delivery) | Yes | | **Nested field: `response.deliveries`** **Array item: `response.deliveries[]`** Schema: [WebhookDelivery](#schema-webhook-delivery). **Representative successful response response** ```json { "deliveries": [ { "id": 123, "jobId": "550e8400-e29b-41d4-a716-446655440000", "event": "render.completed", "status": "success", "attempts": 1, "responseStatus": 200 } ] } ``` ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema webhook delivery **WebhookDelivery** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `WebhookDelivery.id` | integer | No | | | `WebhookDelivery.jobId` | string | No | | | `WebhookDelivery.event` | string | No | | | `WebhookDelivery.url` | string | No | | | `WebhookDelivery.status` | string | No | | | `WebhookDelivery.attempts` | integer | No | Delivery attempts made (max 5, exponential backoff from 30s). | | `WebhookDelivery.responseStatus` | integer OR null | No | At least one listed alternative must match. | | `WebhookDelivery.errorMessage` | string OR null | No | At least one listed alternative must match. | | `WebhookDelivery.createdAt` | string | No | | | `WebhookDelivery.deliveredAt` | string OR null | No | At least one listed alternative must match. | **Nested field: `WebhookDelivery.responseStatus`** At least one listed alternative must match. **anyOf alternative 1** Type: integer. **anyOf alternative 2** Type: null. **Nested field: `WebhookDelivery.errorMessage`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `WebhookDelivery.deliveredAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/list-webhooks/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/list-webhooks.md # List webhooks `GET /api/webhooks` ## 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 <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 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/webhooks' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Account endpoints and plan allowance Content type: `application/json`. Required properties: `webhooks`, `usage`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.webhooks` | array of [Webhook](#schema-webhook) | Yes | | | `response.usage` | [WebhookUsage](#schema-webhook-usage) | Yes | | **Nested field: `response.webhooks`** **Array item: `response.webhooks[]`** Schema: [Webhook](#schema-webhook). **Representative successful response response** ```json { "webhooks": [], "usage": { "used": 0, "limit": 1, "planName": "Free" } } ``` ### HTTP 401 Unauthorized 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 | ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema webhook list **WebhookList** Required properties: `webhooks`, `usage`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `WebhookList.webhooks` | array of [Webhook](#schema-webhook) | Yes | | | `WebhookList.usage` | [WebhookUsage](#schema-webhook-usage) | Yes | | **Nested field: `WebhookList.webhooks`** **Array item: `WebhookList.webhooks[]`** Schema: [Webhook](#schema-webhook). ### schema webhook **Webhook** Required properties: `id`, `url`, `events`, `status`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Webhook.id` | string | Yes | | | `Webhook.url` | string | Yes | | | `Webhook.description` | string | No | | | `Webhook.events` | array of `"render.completed"` / `"render.failed"` | Yes | | | `Webhook.status` | `"active"` / `"disabled"` | Yes | | | `Webhook.secret` | string | No | Returned by creation and single-webhook lookup; omitted from list/update responses. | | `Webhook.consecutiveFailures` | integer | No | | | `Webhook.lastSuccessAt` | string OR null | No | At least one listed alternative must match. | | `Webhook.lastFailureAt` | string OR null | No | At least one listed alternative must match. | | `Webhook.lastFailureReason` | string OR null | No | At least one listed alternative must match. | | `Webhook.createdAt` | string | No | | | `Webhook.updatedAt` | string | No | | **Nested field: `Webhook.events`** **Array item: `Webhook.events[]`** Type: `"render.completed"` / `"render.failed"`. **Nested field: `Webhook.lastSuccessAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Webhook.lastFailureAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Webhook.lastFailureReason`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. ### schema webhook usage **WebhookUsage** Required properties: `used`, `limit`, `planName`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `WebhookUsage.used` | integer | Yes | | | `WebhookUsage.limit` | integer | Yes | | | `WebhookUsage.planName` | 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/plan-creative-video/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/plan-creative-video.md # Plan a creative video `POST /api/render/creative-plan/api-key` Build a free, plan-aware art-direction plan before authoring project JSON. The response includes scene roles and timing, style/layout directions, creative-library and stock-media queries, variation seeds, recent-asset exclusions, and the fallback workflow used when no complete template fits. This endpoint does not enqueue a render or consume credits. ## 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 Unknown properties are rejected. Required properties: `brief`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.brief` | string | Yes | What the video should communicate, for whom, and the desired outcome. Minimum length: `3`. Maximum length: `4000`. | | `body.variationMode` | `"consistent"` / `"fresh"` / `"explore"` | No | Stable repeatable output, one new direction, or 2-5 materially different directions. Default: `"fresh"`. | | `body.variationSeed` | string OR integer | No | Optional reproducible creative seed. Exactly one of the listed alternatives must match. | | `body.exploreCount` | integer | No | Default: `3`. Minimum: `2`. Maximum: `5`. | | `body.aspectRatio` | `"16:9"` / `"9:16"` / `"1:1"` / `"4:5"` / `"custom"` | No | Default: `"16:9"`. | | `body.duration` | number | No | Desired final duration. The response caps it to the caller's plan. Default: `15`. Minimum: `0.1`. Maximum: `86400`. | | `body.style` | string | No | Built-in style-pack id or auto. Default: `"auto"`. Maximum length: `100`. | | `body.motionIntensity` | `"restrained"` / `"balanced"` / `"energetic"` | No | | | `body.preferredMedia` | `"image"` / `"video"` / `"mixed"` | No | Default: `"mixed"`. | | `body.recentAssetSlugs` | array of string | No | Recently used creative-library slugs to exclude from fresh or explore work. Maximum items: `20`. | | `body.brand` | [CreativeBrandKit](#schema-creative-brand-kit) | No | | **Nested field: `body.variationSeed`** Optional reproducible creative seed. Exactly one of the listed alternatives must match. **oneOf alternative 1** Minimum length: `1`. Maximum length: `128`. Type: string. **oneOf alternative 2** Type: integer. **Nested field: `body.recentAssetSlugs`** Recently used creative-library slugs to exclude from fresh or explore work. Maximum items: `20`. **Array item: `body.recentAssetSlugs[]`** Maximum length: `255`. Type: string. ### Request example: Example ```bash curl --request POST 'https://api.zvid.io/api/render/creative-plan/api-key' \ --header "x-api-key: $ZVID_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "brief": "Launch an AI analytics product for SaaS teams", "variationMode": "explore", "exploreCount": 3, "aspectRatio": "9:16", "duration": 20, "style": "modern-saas", "recentAssetSlugs": [ "saas-launch-one", "gradient-hero" ], "brand": { "name": "Acme", "primaryColor": "#6633FF", "headlineFont": "Sora" } }' ``` ## Responses ### HTTP 200 Creative plan generated Content type: `application/json`. Required properties: `creativePlanVersion`, `request`, `variation`, `directions`, `creativeWorkflow`, `nextActions`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.creativePlanVersion` | string | Yes | | | `response.schemaVersion` | string | No | | | `response.sourceOfTruth` | string | No | | | `response.planLimits` | object | No | | | `response.request` | object | Yes | | | `response.variation` | object | Yes | | | `response.searchQueries` | object | No | | | `response.exclusions` | array of string | No | | | `response.directions` | array of object | Yes | One direction for consistent/fresh or 2-5 materially different directions for explore. | | `response.creativeWorkflow` | object | Yes | Template selection, no-exact-match fallback, anti-repetition, build-order and quality-gate rules. | | `response.nextActions` | array of string | Yes | | | `response.warnings` | array of string | No | | **Nested field: `response.exclusions`** **Array item: `response.exclusions[]`** Type: string. **Nested field: `response.directions`** One direction for consistent/fresh or 2-5 materially different directions for explore. **Array item: `response.directions[]`** Type: object. **Nested field: `response.nextActions`** **Array item: `response.nextActions[]`** Type: string. **Nested field: `response.warnings`** **Array item: `response.warnings[]`** Type: string. ### HTTP 400 Invalid creative brief or options 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 401 Unauthorized 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 500 Internal 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 creative plan request **CreativePlanRequest** Unknown properties are rejected. Required properties: `brief`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `CreativePlanRequest.brief` | string | Yes | What the video should communicate, for whom, and the desired outcome. Minimum length: `3`. Maximum length: `4000`. | | `CreativePlanRequest.variationMode` | `"consistent"` / `"fresh"` / `"explore"` | No | Stable repeatable output, one new direction, or 2-5 materially different directions. Default: `"fresh"`. | | `CreativePlanRequest.variationSeed` | string OR integer | No | Optional reproducible creative seed. Exactly one of the listed alternatives must match. | | `CreativePlanRequest.exploreCount` | integer | No | Default: `3`. Minimum: `2`. Maximum: `5`. | | `CreativePlanRequest.aspectRatio` | `"16:9"` / `"9:16"` / `"1:1"` / `"4:5"` / `"custom"` | No | Default: `"16:9"`. | | `CreativePlanRequest.duration` | number | No | Desired final duration. The response caps it to the caller's plan. Default: `15`. Minimum: `0.1`. Maximum: `86400`. | | `CreativePlanRequest.style` | string | No | Built-in style-pack id or auto. Default: `"auto"`. Maximum length: `100`. | | `CreativePlanRequest.motionIntensity` | `"restrained"` / `"balanced"` / `"energetic"` | No | | | `CreativePlanRequest.preferredMedia` | `"image"` / `"video"` / `"mixed"` | No | Default: `"mixed"`. | | `CreativePlanRequest.recentAssetSlugs` | array of string | No | Recently used creative-library slugs to exclude from fresh or explore work. Maximum items: `20`. | | `CreativePlanRequest.brand` | [CreativeBrandKit](#schema-creative-brand-kit) | No | | **Nested field: `CreativePlanRequest.variationSeed`** Optional reproducible creative seed. Exactly one of the listed alternatives must match. **oneOf alternative 1** Minimum length: `1`. Maximum length: `128`. Type: string. **oneOf alternative 2** Type: integer. **Nested field: `CreativePlanRequest.recentAssetSlugs`** Recently used creative-library slugs to exclude from fresh or explore work. Maximum items: `20`. **Array item: `CreativePlanRequest.recentAssetSlugs[]`** Maximum length: `255`. Type: string. ### schema creative brand kit **CreativeBrandKit** Unknown properties are rejected. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `CreativeBrandKit.name` | string | No | Maximum length: `200`. | | `CreativeBrandKit.primaryColor` | string | No | Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{3})$`. | | `CreativeBrandKit.secondaryColor` | string | No | Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{3})$`. | | `CreativeBrandKit.accentColor` | string | No | Pattern: `^#([A-Fa-f0-9]{6}\|[A-Fa-f0-9]{3})$`. | | `CreativeBrandKit.headlineFont` | string | No | Maximum length: `100`. | | `CreativeBrandKit.bodyFont` | string | No | Maximum length: `100`. | | `CreativeBrandKit.logoUrl` | string | No | Maximum length: `2048`. Format: `uri`. | ### schema creative plan response **CreativePlanResponse** Required properties: `creativePlanVersion`, `request`, `variation`, `directions`, `creativeWorkflow`, `nextActions`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `CreativePlanResponse.creativePlanVersion` | string | Yes | | | `CreativePlanResponse.schemaVersion` | string | No | | | `CreativePlanResponse.sourceOfTruth` | string | No | | | `CreativePlanResponse.planLimits` | object | No | | | `CreativePlanResponse.request` | object | Yes | | | `CreativePlanResponse.variation` | object | Yes | | | `CreativePlanResponse.searchQueries` | object | No | | | `CreativePlanResponse.exclusions` | array of string | No | | | `CreativePlanResponse.directions` | array of object | Yes | One direction for consistent/fresh or 2-5 materially different directions for explore. | | `CreativePlanResponse.creativeWorkflow` | object | Yes | Template selection, no-exact-match fallback, anti-repetition, build-order and quality-gate rules. | | `CreativePlanResponse.nextActions` | array of string | Yes | | | `CreativePlanResponse.warnings` | array of string | No | | **Nested field: `CreativePlanResponse.exclusions`** **Array item: `CreativePlanResponse.exclusions[]`** Type: string. **Nested field: `CreativePlanResponse.directions`** One direction for consistent/fresh or 2-5 materially different directions for explore. **Array item: `CreativePlanResponse.directions[]`** Type: object. **Nested field: `CreativePlanResponse.nextActions`** **Array item: `CreativePlanResponse.nextActions[]`** Type: string. **Nested field: `CreativePlanResponse.warnings`** **Array item: `CreativePlanResponse.warnings[]`** Type: string. ### 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 | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/preview-template/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/preview-template.md # 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 <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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/repair-render-project/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/repair-render-project.md # Repair project JSON `POST /api/render/repair/api-key` Free conservative mechanical repairs. Inspect changes and remainingErrors, then run remote validation. No render or credit reservation. ## 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 Unknown properties are rejected. Required properties: `payload`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.payload` | object | Yes | | ### Request example: repair ```bash curl --request POST 'https://api.zvid.io/api/render/repair/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" } }' ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `repaired`, `changes`, `valid`, `remainingErrors`, `warnings`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.schemaVersion` | string | No | | | `response.sourceOfTruth` | string | No | | | `response.planLimits` | object | No | | | `response.repaired` | object | Yes | | | `response.changes` | array of object | Yes | | | `response.valid` | boolean | Yes | | | `response.remainingErrors` | array of object | Yes | | | `response.warnings` | array of object | Yes | | **Nested field: `response.changes`** **Array item: `response.changes[]`** Type: object. **Nested field: `response.remainingErrors`** **Array item: `response.remainingErrors[]`** Type: object. **Nested field: `response.warnings`** **Array item: `response.warnings[]`** Type: object. ### 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 repair response **RepairResponse** Required properties: `repaired`, `changes`, `valid`, `remainingErrors`, `warnings`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RepairResponse.schemaVersion` | string | No | | | `RepairResponse.sourceOfTruth` | string | No | | | `RepairResponse.planLimits` | object | No | | | `RepairResponse.repaired` | object | Yes | | | `RepairResponse.changes` | array of object | Yes | | | `RepairResponse.valid` | boolean | Yes | | | `RepairResponse.remainingErrors` | array of object | Yes | | | `RepairResponse.warnings` | array of object | Yes | | **Nested field: `RepairResponse.changes`** **Array item: `RepairResponse.changes[]`** Type: object. **Nested field: `RepairResponse.remainingErrors`** **Array item: `RepairResponse.remainingErrors[]`** Type: object. **Nested field: `RepairResponse.warnings`** **Array item: `RepairResponse.warnings[]`** Type: object. ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/revoke-api-key/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/revoke-api-key.md # Revoke API key `DELETE /api/api-keys/{id}` Revoke (delete) an API key belonging to the authenticated user ## 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 <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 | integer | Yes | API key ID | ## 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. This operation does not take a request body. ### Request example ```bash curl --request DELETE 'https://api.zvid.io/api/api-keys/789' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 API key revoked successfully Content type: `application/json`. Required properties: `success`, `message`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.success` | boolean | Yes | | | `response.message` | string | Yes | | ### HTTP 400 Invalid ID, API key not found, or operation failed 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 401 Unauthorized 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 | ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### 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 | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/search-stock-media/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/search-stock-media.md # Search stock media `GET /api/stock/search` Search Zvid's stock library. Anonymous requests use Free-plan renditions. Optional API-key authentication applies the account's media limits. Use returned URLs and metadata, inspect attribution requirements, and validate before rendering. ## Authentication This operation also permits an unauthenticated request. - Option 2: `x-api-key: YOUR_API_KEY` (header). API key for authentication. Create one in your dashboard. - Option 3: `Authorization: Bearer YOUR_ACCESS_TOKEN`. Dashboard JWT in Authorization: Bearer <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 | | --- | --- | --- | --- | --- | | `type` | query | `"image"` / `"video"` / `"gif"` / `"audio"` | Yes | | | `query` | query | string | No | Maximum length: `200`. | | `page` | query | integer | No | Default: `1`. Minimum: `1`. Maximum: `500`. | | `perPage` | query | integer | No | Default: `24`. Minimum: `1`. Maximum: `60`. | ## 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. This operation does not take a request body. ### Request example ```bash curl --request GET 'https://api.zvid.io/api/stock/search?type=YOUR_TYPE' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `items`, `page`, `perPage`, `hasMore`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.items` | array of object | Yes | | | `response.page` | integer | Yes | | | `response.perPage` | integer | Yes | | | `response.hasMore` | boolean | Yes | | | `response.excludedCount` | integer | No | | | `response.message` | string | No | | | `response.providerErrors` | object | No | | **Nested field: `response.items`** **Array item: `response.items[]`** Required properties: `id`, `kind`, `src`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.items[].id` | string | Yes | | | `response.items[].kind` | `"image"` / `"video"` / `"gif"` / `"audio"` | Yes | | | `response.items[].src` | string | Yes | Media URL to use in a project element. | | `response.items[].preview` | string | No | Picker thumbnail or preview URL. | | `response.items[].width` | number | No | | | `response.items[].height` | number | No | | | `response.items[].duration` | number | No | | | `response.items[].description` | string | No | | | `response.items[].credit` | object | No | | **Nested field: `response.items[].credit`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.items[].credit.name` | string | No | | | `response.items[].credit.link` | string | No | | ### 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 | ### HTTP 502 Stock library temporarily unavailable 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 503 Stock library unavailable 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 stock search result **StockSearchResult** Required properties: `items`, `page`, `perPage`, `hasMore`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `StockSearchResult.items` | array of object | Yes | | | `StockSearchResult.page` | integer | Yes | | | `StockSearchResult.perPage` | integer | Yes | | | `StockSearchResult.hasMore` | boolean | Yes | | | `StockSearchResult.excludedCount` | integer | No | | | `StockSearchResult.message` | string | No | | | `StockSearchResult.providerErrors` | object | No | | **Nested field: `StockSearchResult.items`** **Array item: `StockSearchResult.items[]`** Required properties: `id`, `kind`, `src`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `StockSearchResult.items[].id` | string | Yes | | | `StockSearchResult.items[].kind` | `"image"` / `"video"` / `"gif"` / `"audio"` | Yes | | | `StockSearchResult.items[].src` | string | Yes | Media URL to use in a project element. | | `StockSearchResult.items[].preview` | string | No | Picker thumbnail or preview URL. | | `StockSearchResult.items[].width` | number | No | | | `StockSearchResult.items[].height` | number | No | | | `StockSearchResult.items[].duration` | number | No | | | `StockSearchResult.items[].description` | string | No | | | `StockSearchResult.items[].credit` | object | No | | **Nested field: `StockSearchResult.items[].credit`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `StockSearchResult.items[].credit.name` | string | No | | | `StockSearchResult.items[].credit.link` | string | No | | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/submit-bulk-render/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/submit-bulk-render.md # Submit bulk render `POST /api/render/bulk/api-key` Submit one template (or inline payload) plus many variable sets. Each item becomes an individual render job with its own credits and webhook events. Validation is best-effort: valid items are queued, invalid items are returned in `errors` with their original request index (`item`, 0-based). Only a malformed envelope — or a batch where every item fails — is rejected outright. ## 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` or `template`, plus 1–500 items (plan-limited). Unknown properties are rejected. Required properties: `items`. 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 | Pattern: `^tpl_[A-Za-z0-9]{20}$`. | | `body.items` | array of object | Yes | Minimum items: `1`. Maximum items: `500`. | | `body.variables` | object | No | Batch-level values merged under every item's variables. | | `body.overrides` | [RenderOverrides](#schema-render-overrides) | No | | | `body.name` | string | No | Batch name shown in the dashboard. Maximum length: `255`. | | `body.webhookUrl` | string | No | One-off unsigned POST for each child job on completion or failure; not a single batch callback. The hosted API requires a public HTTPS address. Maximum length: `2048`. Format: `uri`. | **Nested field: `body.items`** Minimum items: `1`. Maximum items: `500`. **Array item: `body.items[]`** Unknown properties are rejected. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.items[].variables` | object | No | | | `body.items[].name` | string | No | Maximum length: `1000`. | **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: videoBatch ```bash curl --request POST 'https://api.zvid.io/api/render/bulk/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": "{{title}}", "position": "center-center", "style": { "fontSize": 64, "color": "#ffffff" } } ], "backgroundColor": "#14213d", "variables": { "title": "Hello, Zvid!" } }, "items": [ { "variables": { "title": "First video" } }, { "variables": { "title": "Second video" } } ] }' ``` ## Responses ### HTTP 202 Batch accepted; valid items queued Content type: `application/json`. Bulk submission result. `queued` is a boolean acknowledgement — the number of queued jobs is `totalJobs`. Items that failed validation are listed in `errors`; each entry carries the item's original request index as `item` (0-based). Required properties: `bulkId`, `queued`, `totalJobs`, `jobs`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.bulkId` | string | Yes | | | `response.queued` | boolean | Yes | | | `response.totalJobs` | integer | Yes | Number of jobs actually queued. | | `response.rejectedItems` | integer | No | Number of items that failed validation (`errors.length`). | | `response.errors` | array of object | No | Per-item validation failures (best-effort — the rest were queued). | | `response.failedToQueue` | integer | No | Valid items that could not be enqueued (queue failure, not validation). | | `response.creditsReserved` | integer | No | Total credits reserved for the batch. | | `response.clientRoom` | string | No | Socket.IO room emitting progress events for this batch. | | `response.queueAhead` | integer | No | Jobs ahead of this batch in the render queue. | | `response.jobs` | array of object | Yes | | **Nested field: `response.errors`** Per-item validation failures (best-effort — the rest were queued). **Array item: `response.errors[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.errors[].item` | integer | No | Original 0-based index of the item in the request's `items` array. | | `response.errors[].error` | string | No | | | `response.errors[].details` | array of object | No | | **Nested field: `response.errors[].details`** **Array item: `response.errors[].details[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.errors[].details[].field` | string | No | | | `response.errors[].details[].message` | string | No | | **Nested field: `response.jobs`** **Array item: `response.jobs[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.jobs[].jobId` | string | No | Format: `uuid`. | | `response.jobs[].index` | integer | No | The job's original 0-based position in the request's `items` array. | | `response.jobs[].name` | string / null | No | | | `response.jobs[].creditsReserved` | integer | No | | **Example response** ```json { "bulkId": "blk_CL7FunS6EL0C7qeES6y4", "queued": true, "totalJobs": 2, "rejectedItems": 1, "errors": [ { "item": 1, "error": "Validation failed", "details": [ { "field": "payload.duration", "message": "Duration must be at least 0.1 seconds" } ] } ], "failedToQueue": 0, "creditsReserved": 12, "clientRoom": "api:42:7", "queueAhead": 0, "jobs": [ { "jobId": "550e8400-e29b-41d4-a716-446655440000", "index": 0, "name": "spring-amira", "creditsReserved": 6 }, { "jobId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "index": 2, "name": null, "creditsReserved": 6 } ] } ``` ### HTTP 400 Malformed envelope or every item invalid 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 Unauthorized 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 402 Insufficient credits Content type: `application/json`. Unknown properties are rejected. Required properties: `error`, `message`, `creditsRequired`, `creditsAvailable`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.error` | string | Yes | | | `response.message` | string | Yes | | | `response.creditsRequired` | integer | Yes | | | `response.creditsAvailable` | integer | Yes | | ### 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 429 Plan concurrency or hourly render limit reached - Header `Retry-After`: Seconds to wait when supplied. integer Content type: `application/json`. Unknown properties are rejected. Required properties: `error`, `message`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.error` | string | Yes | | | `response.message` | string | Yes | | | `response.retryAfter` | integer OR null | No | At least one listed alternative must match. | | `response.current` | integer | No | | | `response.limit` | integer | No | | **Nested field: `response.retryAfter`** At least one listed alternative must match. **anyOf alternative 1** Type: integer. **anyOf alternative 2** Type: null. ### HTTP 503 Rendering temporarily unavailable for maintenance 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 bulk render request **BulkRenderRequest** Provide either `payload` or `template`, plus 1–500 items (plan-limited). Unknown properties are rejected. Required properties: `items`. Exactly one of the listed alternatives must match. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderRequest.payload` | [RenderPayload](#schema-render-payload) | No | | | `BulkRenderRequest.template` | string | No | Pattern: `^tpl_[A-Za-z0-9]{20}$`. | | `BulkRenderRequest.items` | array of object | Yes | Minimum items: `1`. Maximum items: `500`. | | `BulkRenderRequest.variables` | object | No | Batch-level values merged under every item's variables. | | `BulkRenderRequest.overrides` | [RenderOverrides](#schema-render-overrides) | No | | | `BulkRenderRequest.name` | string | No | Batch name shown in the dashboard. Maximum length: `255`. | | `BulkRenderRequest.webhookUrl` | string | No | One-off unsigned POST for each child job on completion or failure; not a single batch callback. The hosted API requires a public HTTPS address. Maximum length: `2048`. Format: `uri`. | **Nested field: `BulkRenderRequest.items`** Minimum items: `1`. Maximum items: `500`. **Array item: `BulkRenderRequest.items[]`** Unknown properties are rejected. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderRequest.items[].variables` | object | No | | | `BulkRenderRequest.items[].name` | string | No | Maximum length: `1000`. | **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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 bulk render accepted **BulkRenderAccepted** Bulk submission result. `queued` is a boolean acknowledgement — the number of queued jobs is `totalJobs`. Items that failed validation are listed in `errors`; each entry carries the item's original request index as `item` (0-based). Required properties: `bulkId`, `queued`, `totalJobs`, `jobs`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderAccepted.bulkId` | string | Yes | | | `BulkRenderAccepted.queued` | boolean | Yes | | | `BulkRenderAccepted.totalJobs` | integer | Yes | Number of jobs actually queued. | | `BulkRenderAccepted.rejectedItems` | integer | No | Number of items that failed validation (`errors.length`). | | `BulkRenderAccepted.errors` | array of object | No | Per-item validation failures (best-effort — the rest were queued). | | `BulkRenderAccepted.failedToQueue` | integer | No | Valid items that could not be enqueued (queue failure, not validation). | | `BulkRenderAccepted.creditsReserved` | integer | No | Total credits reserved for the batch. | | `BulkRenderAccepted.clientRoom` | string | No | Socket.IO room emitting progress events for this batch. | | `BulkRenderAccepted.queueAhead` | integer | No | Jobs ahead of this batch in the render queue. | | `BulkRenderAccepted.jobs` | array of object | Yes | | **Nested field: `BulkRenderAccepted.errors`** Per-item validation failures (best-effort — the rest were queued). **Array item: `BulkRenderAccepted.errors[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderAccepted.errors[].item` | integer | No | Original 0-based index of the item in the request's `items` array. | | `BulkRenderAccepted.errors[].error` | string | No | | | `BulkRenderAccepted.errors[].details` | array of object | No | | **Nested field: `BulkRenderAccepted.errors[].details`** **Array item: `BulkRenderAccepted.errors[].details[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderAccepted.errors[].details[].field` | string | No | | | `BulkRenderAccepted.errors[].details[].message` | string | No | | **Nested field: `BulkRenderAccepted.jobs`** **Array item: `BulkRenderAccepted.jobs[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderAccepted.jobs[].jobId` | string | No | Format: `uuid`. | | `BulkRenderAccepted.jobs[].index` | integer | No | The job's original 0-based position in the request's `items` array. | | `BulkRenderAccepted.jobs[].name` | string / null | No | | | `BulkRenderAccepted.jobs[].creditsReserved` | integer | No | | ### 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 insufficient credits error **InsufficientCreditsError** Unknown properties are rejected. Required properties: `error`, `message`, `creditsRequired`, `creditsAvailable`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `InsufficientCreditsError.error` | string | Yes | | | `InsufficientCreditsError.message` | string | Yes | | | `InsufficientCreditsError.creditsRequired` | integer | Yes | | | `InsufficientCreditsError.creditsAvailable` | integer | Yes | | ### 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 | ### schema rate limit error **RateLimitError** Unknown properties are rejected. Required properties: `error`, `message`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RateLimitError.error` | string | Yes | | | `RateLimitError.message` | string | Yes | | | `RateLimitError.retryAfter` | integer OR null | No | At least one listed alternative must match. | | `RateLimitError.current` | integer | No | | | `RateLimitError.limit` | integer | No | | **Nested field: `RateLimitError.retryAfter`** At least one listed alternative must match. **anyOf alternative 1** Type: integer. **anyOf alternative 2** Type: null. ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/submit-image-bulk-render/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/submit-image-bulk-render.md # Submit bulk image render `POST /api/render/image/bulk/api-key` Image alias of the bulk render endpoint: every item must resolve to an image project (inline payloads are forced to `type: "image"`; a referenced template must be an image template). Bulk images pool credits — 1 credit per image, reserved once per batch. ## 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` or `template`, plus 1–500 items (plan-limited). Unknown properties are rejected. Required properties: `items`. 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 | Pattern: `^tpl_[A-Za-z0-9]{20}$`. | | `body.items` | array of object | Yes | Minimum items: `1`. Maximum items: `500`. | | `body.variables` | object | No | Batch-level values merged under every item's variables. | | `body.overrides` | [RenderOverrides](#schema-render-overrides) | No | | | `body.name` | string | No | Batch name shown in the dashboard. Maximum length: `255`. | | `body.webhookUrl` | string | No | One-off unsigned POST for each child job on completion or failure; not a single batch callback. The hosted API requires a public HTTPS address. Maximum length: `2048`. Format: `uri`. | **Nested field: `body.items`** Minimum items: `1`. Maximum items: `500`. **Array item: `body.items[]`** Unknown properties are rejected. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.items[].variables` | object | No | | | `body.items[].name` | string | No | Maximum length: `1000`. | **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: imageBatch ```bash curl --request POST 'https://api.zvid.io/api/render/image/bulk/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": "{{title}}" } ], "variables": { "title": "Hello" } }, "items": [ { "variables": { "title": "First image" } }, { "variables": { "title": "Second image" } } ] }' ``` ## Responses ### HTTP 202 Batch accepted; valid items queued Content type: `application/json`. Bulk submission result. `queued` is a boolean acknowledgement — the number of queued jobs is `totalJobs`. Items that failed validation are listed in `errors`; each entry carries the item's original request index as `item` (0-based). Required properties: `bulkId`, `queued`, `totalJobs`, `jobs`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.bulkId` | string | Yes | | | `response.queued` | boolean | Yes | | | `response.totalJobs` | integer | Yes | Number of jobs actually queued. | | `response.rejectedItems` | integer | No | Number of items that failed validation (`errors.length`). | | `response.errors` | array of object | No | Per-item validation failures (best-effort — the rest were queued). | | `response.failedToQueue` | integer | No | Valid items that could not be enqueued (queue failure, not validation). | | `response.creditsReserved` | integer | No | Total credits reserved for the batch. | | `response.clientRoom` | string | No | Socket.IO room emitting progress events for this batch. | | `response.queueAhead` | integer | No | Jobs ahead of this batch in the render queue. | | `response.jobs` | array of object | Yes | | **Nested field: `response.errors`** Per-item validation failures (best-effort — the rest were queued). **Array item: `response.errors[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.errors[].item` | integer | No | Original 0-based index of the item in the request's `items` array. | | `response.errors[].error` | string | No | | | `response.errors[].details` | array of object | No | | **Nested field: `response.errors[].details`** **Array item: `response.errors[].details[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.errors[].details[].field` | string | No | | | `response.errors[].details[].message` | string | No | | **Nested field: `response.jobs`** **Array item: `response.jobs[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.jobs[].jobId` | string | No | Format: `uuid`. | | `response.jobs[].index` | integer | No | The job's original 0-based position in the request's `items` array. | | `response.jobs[].name` | string / null | No | | | `response.jobs[].creditsReserved` | integer | No | | **Example response** ```json { "bulkId": "blk_9tPq2ZxKvR4mW8sLdA1e", "queued": true, "totalJobs": 2, "rejectedItems": 0, "errors": [], "failedToQueue": 0, "creditsReserved": 2, "clientRoom": "api:42:7", "queueAhead": 0, "jobs": [ { "jobId": "550e8400-e29b-41d4-a716-446655440000", "index": 0, "name": "product-card-lamp", "creditsReserved": 0 }, { "jobId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "index": 1, "name": null, "creditsReserved": 0 } ] } ``` ### HTTP 400 Malformed envelope, non-image project, or every item invalid 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 Unauthorized 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 402 Insufficient credits Content type: `application/json`. Unknown properties are rejected. Required properties: `error`, `message`, `creditsRequired`, `creditsAvailable`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.error` | string | Yes | | | `response.message` | string | Yes | | | `response.creditsRequired` | integer | Yes | | | `response.creditsAvailable` | integer | Yes | | ### 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 429 Plan concurrency or hourly render limit reached - Header `Retry-After`: Seconds to wait when supplied. integer Content type: `application/json`. Unknown properties are rejected. Required properties: `error`, `message`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.error` | string | Yes | | | `response.message` | string | Yes | | | `response.retryAfter` | integer OR null | No | At least one listed alternative must match. | | `response.current` | integer | No | | | `response.limit` | integer | No | | **Nested field: `response.retryAfter`** At least one listed alternative must match. **anyOf alternative 1** Type: integer. **anyOf alternative 2** Type: null. ### HTTP 503 Rendering temporarily unavailable for maintenance 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 bulk render request **BulkRenderRequest** Provide either `payload` or `template`, plus 1–500 items (plan-limited). Unknown properties are rejected. Required properties: `items`. Exactly one of the listed alternatives must match. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderRequest.payload` | [RenderPayload](#schema-render-payload) | No | | | `BulkRenderRequest.template` | string | No | Pattern: `^tpl_[A-Za-z0-9]{20}$`. | | `BulkRenderRequest.items` | array of object | Yes | Minimum items: `1`. Maximum items: `500`. | | `BulkRenderRequest.variables` | object | No | Batch-level values merged under every item's variables. | | `BulkRenderRequest.overrides` | [RenderOverrides](#schema-render-overrides) | No | | | `BulkRenderRequest.name` | string | No | Batch name shown in the dashboard. Maximum length: `255`. | | `BulkRenderRequest.webhookUrl` | string | No | One-off unsigned POST for each child job on completion or failure; not a single batch callback. The hosted API requires a public HTTPS address. Maximum length: `2048`. Format: `uri`. | **Nested field: `BulkRenderRequest.items`** Minimum items: `1`. Maximum items: `500`. **Array item: `BulkRenderRequest.items[]`** Unknown properties are rejected. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderRequest.items[].variables` | object | No | | | `BulkRenderRequest.items[].name` | string | No | Maximum length: `1000`. | **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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 bulk render accepted **BulkRenderAccepted** Bulk submission result. `queued` is a boolean acknowledgement — the number of queued jobs is `totalJobs`. Items that failed validation are listed in `errors`; each entry carries the item's original request index as `item` (0-based). Required properties: `bulkId`, `queued`, `totalJobs`, `jobs`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderAccepted.bulkId` | string | Yes | | | `BulkRenderAccepted.queued` | boolean | Yes | | | `BulkRenderAccepted.totalJobs` | integer | Yes | Number of jobs actually queued. | | `BulkRenderAccepted.rejectedItems` | integer | No | Number of items that failed validation (`errors.length`). | | `BulkRenderAccepted.errors` | array of object | No | Per-item validation failures (best-effort — the rest were queued). | | `BulkRenderAccepted.failedToQueue` | integer | No | Valid items that could not be enqueued (queue failure, not validation). | | `BulkRenderAccepted.creditsReserved` | integer | No | Total credits reserved for the batch. | | `BulkRenderAccepted.clientRoom` | string | No | Socket.IO room emitting progress events for this batch. | | `BulkRenderAccepted.queueAhead` | integer | No | Jobs ahead of this batch in the render queue. | | `BulkRenderAccepted.jobs` | array of object | Yes | | **Nested field: `BulkRenderAccepted.errors`** Per-item validation failures (best-effort — the rest were queued). **Array item: `BulkRenderAccepted.errors[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderAccepted.errors[].item` | integer | No | Original 0-based index of the item in the request's `items` array. | | `BulkRenderAccepted.errors[].error` | string | No | | | `BulkRenderAccepted.errors[].details` | array of object | No | | **Nested field: `BulkRenderAccepted.errors[].details`** **Array item: `BulkRenderAccepted.errors[].details[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderAccepted.errors[].details[].field` | string | No | | | `BulkRenderAccepted.errors[].details[].message` | string | No | | **Nested field: `BulkRenderAccepted.jobs`** **Array item: `BulkRenderAccepted.jobs[]`** | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `BulkRenderAccepted.jobs[].jobId` | string | No | Format: `uuid`. | | `BulkRenderAccepted.jobs[].index` | integer | No | The job's original 0-based position in the request's `items` array. | | `BulkRenderAccepted.jobs[].name` | string / null | No | | | `BulkRenderAccepted.jobs[].creditsReserved` | integer | No | | ### 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 insufficient credits error **InsufficientCreditsError** Unknown properties are rejected. Required properties: `error`, `message`, `creditsRequired`, `creditsAvailable`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `InsufficientCreditsError.error` | string | Yes | | | `InsufficientCreditsError.message` | string | Yes | | | `InsufficientCreditsError.creditsRequired` | integer | Yes | | | `InsufficientCreditsError.creditsAvailable` | integer | Yes | | ### 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 | ### schema rate limit error **RateLimitError** Unknown properties are rejected. Required properties: `error`, `message`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RateLimitError.error` | string | Yes | | | `RateLimitError.message` | string | Yes | | | `RateLimitError.retryAfter` | integer OR null | No | At least one listed alternative must match. | | `RateLimitError.current` | integer | No | | | `RateLimitError.limit` | integer | No | | **Nested field: `RateLimitError.retryAfter`** At least one listed alternative must match. **anyOf alternative 1** Type: integer. **anyOf alternative 2** Type: null. ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/submit-image-render-job/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/submit-image-render-job.md # Submit image render job `POST /api/render/image/api-key` Render a still image (PNG, JPG, or WebP) from a Zvid payload. This endpoint forces `type: "image"` on inline payloads, which must satisfy image-mode constraints. A stored template must already be an image project. Image payloads may also be submitted to the regular render endpoint. Image credits: 1 credit per image, at any resolution. ## 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: image ```bash curl --request POST 'https://api.zvid.io/api/render/image/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" } } ] } }' ``` ## Responses ### HTTP 202 Image render job accepted and queued Content type: `application/json`. Unknown properties are rejected. Required properties: `jobId`, `status`, `queuePosition`, `creditsReserved`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.jobId` | string | Yes | Format: `uuid`. | | `response.status` | string | Yes | | | `response.queuePosition` | integer | Yes | Position in the render queue | | `response.creditsReserved` | integer | Yes | Credits reserved for this job | ### HTTP 400 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 Unauthorized 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 402 Insufficient credits Content type: `application/json`. Unknown properties are rejected. Required properties: `error`, `message`, `creditsRequired`, `creditsAvailable`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.error` | string | Yes | | | `response.message` | string | Yes | | | `response.creditsRequired` | integer | Yes | | | `response.creditsAvailable` | integer | Yes | | ### 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 429 Plan concurrency or hourly render limit reached - Header `Retry-After`: Seconds to wait when supplied. integer Content type: `application/json`. Unknown properties are rejected. Required properties: `error`, `message`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.error` | string | Yes | | | `response.message` | string | Yes | | | `response.retryAfter` | integer OR null | No | At least one listed alternative must match. | | `response.current` | integer | No | | | `response.limit` | integer | No | | **Nested field: `response.retryAfter`** At least one listed alternative must match. **anyOf alternative 1** Type: integer. **anyOf alternative 2** Type: null. ### HTTP 503 Rendering temporarily unavailable for maintenance 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 render job response **RenderJobResponse** Unknown properties are rejected. Required properties: `jobId`, `status`, `queuePosition`, `creditsReserved`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RenderJobResponse.jobId` | string | Yes | Format: `uuid`. | | `RenderJobResponse.status` | string | Yes | | | `RenderJobResponse.queuePosition` | integer | Yes | Position in the render queue | | `RenderJobResponse.creditsReserved` | integer | Yes | Credits reserved for this job | ### 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 insufficient credits error **InsufficientCreditsError** Unknown properties are rejected. Required properties: `error`, `message`, `creditsRequired`, `creditsAvailable`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `InsufficientCreditsError.error` | string | Yes | | | `InsufficientCreditsError.message` | string | Yes | | | `InsufficientCreditsError.creditsRequired` | integer | Yes | | | `InsufficientCreditsError.creditsAvailable` | integer | Yes | | ### 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 | ### schema rate limit error **RateLimitError** Unknown properties are rejected. Required properties: `error`, `message`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RateLimitError.error` | string | Yes | | | `RateLimitError.message` | string | Yes | | | `RateLimitError.retryAfter` | integer OR null | No | At least one listed alternative must match. | | `RateLimitError.current` | integer | No | | | `RateLimitError.limit` | integer | No | | **Nested field: `RateLimitError.retryAfter`** At least one listed alternative must match. **anyOf alternative 1** Type: integer. **anyOf alternative 2** Type: null. ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/submit-render-job/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/submit-render-job.md # Submit render job `POST /api/render/api-key` Submit a video or image asynchronously. Run the free validation endpoint first. Exactly one of payload or template is required. Inline and stored projects resolve variables. A successful submission returns 202 and reserves credits; poll the job until completed or failed. A network timeout does not prove submission failed: check the job before retrying. ## 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/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/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/api-key' \ --header "x-api-key: $ZVID_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "template": "tpl_abcdefghijklmnopqrst", "variables": { "title": "Summer sale" } }' ``` ## Responses ### HTTP 202 Render accepted; the example corresponds to the 5-second HD video request Content type: `application/json`. Unknown properties are rejected. Required properties: `jobId`, `status`, `queuePosition`, `creditsReserved`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.jobId` | string | Yes | Format: `uuid`. | | `response.status` | string | Yes | | | `response.queuePosition` | integer | Yes | Position in the render queue | | `response.creditsReserved` | integer | Yes | Credits reserved for this job | **Example response** ```json { "jobId": "550e8400-e29b-41d4-a716-446655440000", "status": "queued", "queuePosition": 5, "creditsReserved": 5 } ``` ### HTTP 400 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 Unauthorized 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 402 Insufficient credits Content type: `application/json`. Unknown properties are rejected. Required properties: `error`, `message`, `creditsRequired`, `creditsAvailable`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.error` | string | Yes | | | `response.message` | string | Yes | | | `response.creditsRequired` | integer | Yes | | | `response.creditsAvailable` | integer | Yes | | **Example response** ```json { "error": "Insufficient credits", "message": "This render requires 20 credits. Please check your balance.", "creditsRequired": 20, "creditsAvailable": 10 } ``` ### 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 429 Plan concurrency or hourly render limit reached - Header `Retry-After`: Seconds to wait when supplied. integer Content type: `application/json`. Unknown properties are rejected. Required properties: `error`, `message`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.error` | string | Yes | | | `response.message` | string | Yes | | | `response.retryAfter` | integer OR null | No | At least one listed alternative must match. | | `response.current` | integer | No | | | `response.limit` | integer | No | | **Nested field: `response.retryAfter`** At least one listed alternative must match. **anyOf alternative 1** Type: integer. **anyOf alternative 2** Type: null. ### HTTP 500 Internal 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 | ### HTTP 503 Rendering temporarily unavailable for maintenance 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 render job response **RenderJobResponse** Unknown properties are rejected. Required properties: `jobId`, `status`, `queuePosition`, `creditsReserved`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RenderJobResponse.jobId` | string | Yes | Format: `uuid`. | | `RenderJobResponse.status` | string | Yes | | | `RenderJobResponse.queuePosition` | integer | Yes | Position in the render queue | | `RenderJobResponse.creditsReserved` | integer | Yes | Credits reserved for this job | ### 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 insufficient credits error **InsufficientCreditsError** Unknown properties are rejected. Required properties: `error`, `message`, `creditsRequired`, `creditsAvailable`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `InsufficientCreditsError.error` | string | Yes | | | `InsufficientCreditsError.message` | string | Yes | | | `InsufficientCreditsError.creditsRequired` | integer | Yes | | | `InsufficientCreditsError.creditsAvailable` | integer | Yes | | ### 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 | ### schema rate limit error **RateLimitError** Unknown properties are rejected. Required properties: `error`, `message`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `RateLimitError.error` | string | Yes | | | `RateLimitError.message` | string | Yes | | | `RateLimitError.retryAfter` | integer OR null | No | At least one listed alternative must match. | | `RateLimitError.current` | integer | No | | | `RateLimitError.limit` | integer | No | | **Nested field: `RateLimitError.retryAfter`** At least one listed alternative must match. **anyOf alternative 1** Type: integer. **anyOf alternative 2** Type: null. ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/test-webhook/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/test-webhook.md # Send test delivery `POST /api/webhooks/{id}/test` ## 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 <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 | | ## 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. This operation does not take a request body. ### Request example ```bash curl --request POST 'https://api.zvid.io/api/webhooks/YOUR_ID/test' \ --header "x-api-key: $ZVID_API_KEY" ``` ## Responses ### HTTP 202 Test event queued Content type: `application/json`. Required properties: `queued`, `deliveryId`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.queued` | boolean | Yes | | | `response.deliveryId` | integer | Yes | | **Representative successful response response** ```json { "queued": true, "deliveryId": 123 } ``` ### 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 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/update-api-key/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/update-api-key.md # Update API key `PUT /api/api-keys/{id}` Rename an active API key belonging to the authenticated user ## 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 <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 | integer | Yes | API key ID | ## 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 Required properties: `name`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.name` | string | Yes | New name for the API key Minimum length: `1`. Maximum length: `100`. | ### Request example: Example ```bash curl --request PUT 'https://api.zvid.io/api/api-keys/789' \ --header "x-api-key: $ZVID_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "name": "Updated automation" }' ``` ## Responses ### HTTP 200 API key updated successfully Content type: `application/json`. Unknown properties are rejected. Required properties: `success`, `message`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.success` | boolean | Yes | | | `response.message` | string | Yes | | ### HTTP 400 Invalid ID, validation error, duplicate name, API key not found, or operation failed Content type: `application/json`. Exactly one of the listed alternatives must match. **oneOf alternative 1** Schema: [ValidationError](#schema-validation-error). **oneOf alternative 2** Schema: [Error](#schema-error). ### HTTP 401 Unauthorized 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 | ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### 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 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 | ### 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 | ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/update-project/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/update-project.md # Update project `PUT /api/projects/{id}` Rename a draft project and/or replace its project JSON. At least one of `name` or `payload` is required; each update increments `version`. ## 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 <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 | | ## 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 Minimum properties: `1`. Unknown properties are rejected. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.name` | string | No | Minimum length: `1`. Maximum length: `255`. | | `body.payload` | [RenderPayload](#schema-render-payload) | No | | | `body.project` | [RenderPayload](#schema-render-payload) | No | Compatibility alias for payload; never supply both. | Additional JSON Schema constraints: ```json { "not": { "required": [ "payload", "project" ] } } ``` ### Request example: Example ```bash curl --request PUT 'https://api.zvid.io/api/projects/prj_PkHw73Ul4qBgr3HJRx9t' \ --header "x-api-key: $ZVID_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "name": "promo-draft-v2" }' ``` ## Responses ### HTTP 200 Project updated Content type: `application/json`. Required properties: `project`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.project` | [Project](#schema-project) | Yes | | ### HTTP 400 Validation failed 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 Unauthorized 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 Project not found 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 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 project **Project** An editor draft project, including its full project JSON. Open it in the visual editor at https://editor.zvid.io?project=<id>. Required properties: `id`, `name`, `type`, `version`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Project.id` | string | Yes | | | `Project.userId` | integer | No | | | `Project.name` | string | Yes | | | `Project.payload` | [RenderPayload](#schema-render-payload) | No | | | `Project.type` | `"video"` / `"image"` | Yes | Derived from the stored payload's type | | `Project.version` | integer | Yes | Incremented on every update | | `Project.createdAt` | string | No | Format: `date-time`. | | `Project.updatedAt` | string | No | Format: `date-time`. | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/update-template/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/update-template.md # Update template `PUT /api/templates/{id}` Update any supplied fields. Read the existing template before changing it. payload and project are aliases; never supply both. ## 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 <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. A request body is required. ### application/json Minimum properties: `1`. Unknown properties are rejected. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.name` | string | No | Minimum length: `1`. Maximum length: `255`. | | `body.description` | string | No | Maximum length: `2000`. | | `body.payload` | [RenderPayload](#schema-render-payload) | No | | | `body.project` | [RenderPayload](#schema-render-payload) | No | Compatibility alias for payload. Supply exactly one. | Additional JSON Schema constraints: ```json { "not": { "required": [ "payload", "project" ] } } ``` ### Request example: rename ```bash curl --request PUT 'https://api.zvid.io/api/templates/YOUR_ID' \ --header "x-api-key: $ZVID_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "name": "Updated greeting" }' ``` ## Responses ### HTTP 200 Successful response Content type: `application/json`. Required properties: `template`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.template` | [Template](#schema-template) | Yes | | ### 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 update **TemplateUpdate** Minimum properties: `1`. Unknown properties are rejected. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateUpdate.name` | string | No | Minimum length: `1`. Maximum length: `255`. | | `TemplateUpdate.description` | string | No | Maximum length: `2000`. | | `TemplateUpdate.payload` | [RenderPayload](#schema-render-payload) | No | | | `TemplateUpdate.project` | [RenderPayload](#schema-render-payload) | No | Compatibility alias for payload. Supply exactly one. | Additional JSON Schema constraints: ```json { "not": { "required": [ "payload", "project" ] } } ``` ### 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 template envelope **TemplateEnvelope** Required properties: `template`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateEnvelope.template` | [Template](#schema-template) | Yes | | ### schema template **Template** Required properties: `id`, `name`, `project`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Template.id` | string | Yes | Pattern: `^tpl_[A-Za-z0-9]{20}$`. | | `Template.userId` | integer | No | | | `Template.name` | string | Yes | | | `Template.description` | string OR null | No | At least one listed alternative must match. | | `Template.project` | [RenderPayload](#schema-render-payload) | Yes | | | `Template.type` | `"video"` / `"image"` | No | | | `Template.variablesSummary` | array of [TemplateVariableSummary](#schema-template-variable-summary) | No | | | `Template.version` | integer | No | | | `Template.status` | string | No | | | `Template.createdAt` | string | No | Format: `date-time`. | | `Template.updatedAt` | string | No | Format: `date-time`. | **Nested field: `Template.description`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Template.variablesSummary`** **Array item: `Template.variablesSummary[]`** Schema: [TemplateVariableSummary](#schema-template-variable-summary). ### schema template variable summary **TemplateVariableSummary** One declared template variable and whether the project uses it. Required properties: `name`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `TemplateVariableSummary.name` | string | Yes | | | `TemplateVariableSummary.type` | string | No | | | `TemplateVariableSummary.used` | boolean | No | | | `TemplateVariableSummary.default` | any JSON value | No | Declared default value (any JSON type) | ### 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/update-webhook/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/update-webhook.md # Update webhook `PUT /api/webhooks/{id}` ## 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 <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 | | ## 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 Minimum properties: `1`. Unknown properties are rejected. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `body.url` | string | No | Maximum length: `2048`. Format: `uri`. | | `body.description` | string | No | Maximum length: `255`. | | `body.events` | array of `"render.completed"` / `"render.failed"` | No | Minimum items: `1`. Items must be unique. | | `body.status` | `"active"` / `"disabled"` | No | | **Nested field: `body.events`** Minimum items: `1`. Items must be unique. **Array item: `body.events[]`** Type: `"render.completed"` / `"render.failed"`. ### Request example: disable ```bash curl --request PUT 'https://api.zvid.io/api/webhooks/YOUR_ID' \ --header "x-api-key: $ZVID_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "status": "disabled" }' ``` ## Responses ### HTTP 200 Updated webhook Content type: `application/json`. Required properties: `id`, `url`, `events`, `status`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `response.id` | string | Yes | | | `response.url` | string | Yes | | | `response.description` | string | No | | | `response.events` | array of `"render.completed"` / `"render.failed"` | Yes | | | `response.status` | `"active"` / `"disabled"` | Yes | | | `response.secret` | string | No | Returned by creation and single-webhook lookup; omitted from list/update responses. | | `response.consecutiveFailures` | integer | No | | | `response.lastSuccessAt` | string OR null | No | At least one listed alternative must match. | | `response.lastFailureAt` | string OR null | No | At least one listed alternative must match. | | `response.lastFailureReason` | string OR null | No | At least one listed alternative must match. | | `response.createdAt` | string | No | | | `response.updatedAt` | string | No | | **Nested field: `response.events`** **Array item: `response.events[]`** Type: `"render.completed"` / `"render.failed"`. **Nested field: `response.lastSuccessAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `response.lastFailureAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `response.lastFailureReason`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Representative successful response response** ```json { "id": "whk_abcdefghijklmnopqrst", "url": "https://example.com/hooks/zvid", "events": [ "render.completed" ], "status": "disabled" } ``` ### HTTP 404 Not found No response body is defined. ## Schema definitions The following definitions describe the fields referenced above. Expand a definition to inspect its complete contract. ### schema webhook update **WebhookUpdate** Minimum properties: `1`. Unknown properties are rejected. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `WebhookUpdate.url` | string | No | Maximum length: `2048`. Format: `uri`. | | `WebhookUpdate.description` | string | No | Maximum length: `255`. | | `WebhookUpdate.events` | array of `"render.completed"` / `"render.failed"` | No | Minimum items: `1`. Items must be unique. | | `WebhookUpdate.status` | `"active"` / `"disabled"` | No | | **Nested field: `WebhookUpdate.events`** Minimum items: `1`. Items must be unique. **Array item: `WebhookUpdate.events[]`** Type: `"render.completed"` / `"render.failed"`. ### schema webhook **Webhook** Required properties: `id`, `url`, `events`, `status`. | Field | Type | Required | Description and constraints | | --- | --- | --- | --- | | `Webhook.id` | string | Yes | | | `Webhook.url` | string | Yes | | | `Webhook.description` | string | No | | | `Webhook.events` | array of `"render.completed"` / `"render.failed"` | Yes | | | `Webhook.status` | `"active"` / `"disabled"` | Yes | | | `Webhook.secret` | string | No | Returned by creation and single-webhook lookup; omitted from list/update responses. | | `Webhook.consecutiveFailures` | integer | No | | | `Webhook.lastSuccessAt` | string OR null | No | At least one listed alternative must match. | | `Webhook.lastFailureAt` | string OR null | No | At least one listed alternative must match. | | `Webhook.lastFailureReason` | string OR null | No | At least one listed alternative must match. | | `Webhook.createdAt` | string | No | | | `Webhook.updatedAt` | string | No | | **Nested field: `Webhook.events`** **Array item: `Webhook.events[]`** Type: `"render.completed"` / `"render.failed"`. **Nested field: `Webhook.lastSuccessAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Webhook.lastFailureAt`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. **Nested field: `Webhook.lastFailureReason`** At least one listed alternative must match. **anyOf alternative 1** Type: string. **anyOf alternative 2** Type: null. ## 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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/validate-render-job/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/validate-render-job.md # 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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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 { "depth": 1..10 }. 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 "<svg". Sanitized: no <script>/<foreignObject>/<iframe>/<object>/<embed>/<audio>/<video>, no on* event attributes, no external url()/href/src (only "#id" fragment refs), dimensions <= 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 < 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 (< 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 <script>/<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/) --- Canonical URL: https://docs.zvid.io/docs/endpoints/zvid-api/ Markdown URL: https://docs.zvid.io/markdown/docs/endpoints/zvid-api.md # Zvid API Create videos and still images, manage reusable templates and editor projects, and automate render delivery. REST base URL: https://api.zvid.io. Use x-api-key for server-side integrations. Read the schema and run POST /api/render/validate/api-key before rendering: validation is free and returns creditsRequired. A queued render reserves credits and completes asynchronously. Video pricing is ceil(duration seconds) up to 1920×1080 total pixels, or ceil(duration × 4) above that; images cost 1 credit each. Plan-specific limits are enforced by authenticated validation. Browser-safe setup, errors and full workflows: https://docs.zvid.io/docs/quick-start/. ## API server `https://api.zvid.io` Endpoint pages show the full path, including `/api`. Create API keys at [app.zvid.io/api-keys](https://app.zvid.io/api-keys). Each operation lists the authentication it accepts. ## Choose your next step - [Render your first video](https://docs.zvid.io/docs/quick-start/) - [Render a still image](https://docs.zvid.io/docs/rendering-images/) - [Understand project JSON](https://docs.zvid.io/docs/structure/) - [Read the OpenAPI specification](https://docs.zvid.io/openapi.yaml) - [Use documentation with an AI assistant](https://docs.zvid.io/docs/documentation-resources/) ## Operations - [Get user profile](https://docs.zvid.io/docs/endpoints/get-user-profile/): `GET /api/user/profile` - [Get credit balance](https://docs.zvid.io/docs/endpoints/get-credit-balance/): `GET /api/credits/balance` - [Get transaction history](https://docs.zvid.io/docs/endpoints/get-transactions/): `GET /api/credits/transactions` - [Get usage statistics](https://docs.zvid.io/docs/endpoints/get-usage-stats/): `GET /api/credits/usage-stats` - [List API keys](https://docs.zvid.io/docs/endpoints/list-api-keys/): `GET /api/api-keys` - [Create API key](https://docs.zvid.io/docs/endpoints/create-api-key/): `POST /api/api-keys` - [Update API key](https://docs.zvid.io/docs/endpoints/update-api-key/): `PUT /api/api-keys/{id}` - [Revoke API key](https://docs.zvid.io/docs/endpoints/revoke-api-key/): `DELETE /api/api-keys/{id}` - [Get API key statistics](https://docs.zvid.io/docs/endpoints/get-api-key-stats/): `GET /api/api-keys/{id}/stats` - [Plan a creative video](https://docs.zvid.io/docs/endpoints/plan-creative-video/): `POST /api/render/creative-plan/api-key` - [Submit render job](https://docs.zvid.io/docs/endpoints/submit-render-job/): `POST /api/render/api-key` - [List render jobs](https://docs.zvid.io/docs/endpoints/list-render-jobs/): `GET /api/jobs` - [Get render job status](https://docs.zvid.io/docs/endpoints/get-render-job/): `GET /api/jobs/{id}` - [Submit image render job](https://docs.zvid.io/docs/endpoints/submit-image-render-job/): `POST /api/render/image/api-key` - [Submit bulk render](https://docs.zvid.io/docs/endpoints/submit-bulk-render/): `POST /api/render/bulk/api-key` - [Submit bulk image render](https://docs.zvid.io/docs/endpoints/submit-image-bulk-render/): `POST /api/render/image/bulk/api-key` - [List bulk renders](https://docs.zvid.io/docs/endpoints/list-bulk-renders/): `GET /api/render/bulk` - [Get bulk render status](https://docs.zvid.io/docs/endpoints/get-bulk-render/): `GET /api/render/bulk/{id}` - [List templates](https://docs.zvid.io/docs/endpoints/list-templates/): `GET /api/templates` - [Create template](https://docs.zvid.io/docs/endpoints/create-template/): `POST /api/templates` - [Get template](https://docs.zvid.io/docs/endpoints/get-template/): `GET /api/templates/{id}` - [Update template](https://docs.zvid.io/docs/endpoints/update-template/): `PUT /api/templates/{id}` - [Archive template](https://docs.zvid.io/docs/endpoints/delete-template/): `DELETE /api/templates/{id}` - [Resolve and validate template](https://docs.zvid.io/docs/endpoints/preview-template/): `POST /api/templates/{id}/preview` - [List webhooks](https://docs.zvid.io/docs/endpoints/list-webhooks/): `GET /api/webhooks` - [Create webhook](https://docs.zvid.io/docs/endpoints/create-webhook/): `POST /api/webhooks` - [Get webhook](https://docs.zvid.io/docs/endpoints/get-webhook/): `GET /api/webhooks/{id}` - [Update webhook](https://docs.zvid.io/docs/endpoints/update-webhook/): `PUT /api/webhooks/{id}` - [Delete webhook](https://docs.zvid.io/docs/endpoints/delete-webhook/): `DELETE /api/webhooks/{id}` - [List webhook deliveries](https://docs.zvid.io/docs/endpoints/list-webhook-deliveries/): `GET /api/webhooks/{id}/deliveries` - [Send test delivery](https://docs.zvid.io/docs/endpoints/test-webhook/): `POST /api/webhooks/{id}/test` - [List uploads](https://docs.zvid.io/docs/endpoints/list-uploads/): `GET /api/uploads` - [Upload a file](https://docs.zvid.io/docs/endpoints/create-upload/): `POST /api/uploads` - [Delete upload](https://docs.zvid.io/docs/endpoints/delete-upload/): `DELETE /api/uploads/{id}` - [List projects](https://docs.zvid.io/docs/endpoints/list-projects/): `GET /api/projects` - [Create project](https://docs.zvid.io/docs/endpoints/create-project/): `POST /api/projects` - [Get project](https://docs.zvid.io/docs/endpoints/get-project/): `GET /api/projects/{id}` - [Update project](https://docs.zvid.io/docs/endpoints/update-project/): `PUT /api/projects/{id}` - [Delete project](https://docs.zvid.io/docs/endpoints/delete-project/): `DELETE /api/projects/{id}` - [Duplicate template](https://docs.zvid.io/docs/endpoints/duplicate-template/): `POST /api/templates/{id}/duplicate` - [Validate render request](https://docs.zvid.io/docs/endpoints/validate-render-job/): `POST /api/render/validate/api-key` - [Get live render schema](https://docs.zvid.io/docs/endpoints/get-render-schema/): `GET /api/render/schema/api-key` - [List supported elements](https://docs.zvid.io/docs/endpoints/list-render-elements/): `GET /api/render/elements/api-key` - [Get element reference](https://docs.zvid.io/docs/endpoints/get-render-element/): `GET /api/render/elements/{type}/api-key` - [List authoring examples](https://docs.zvid.io/docs/endpoints/list-render-examples/): `GET /api/render/examples/api-key` - [Get authoring example](https://docs.zvid.io/docs/endpoints/get-render-example/): `GET /api/render/examples/{name}/api-key` - [Repair project JSON](https://docs.zvid.io/docs/endpoints/repair-render-project/): `POST /api/render/repair/api-key` - [Get MCP preferences](https://docs.zvid.io/docs/endpoints/get-mcp-preferences/): `GET /api/mcp/preferences` - [Search stock media](https://docs.zvid.io/docs/endpoints/search-stock-media/): `GET /api/stock/search` - [List creative library items](https://docs.zvid.io/docs/endpoints/list-library-items/): `GET /api/library/{kind}` - [Get creative library metadata](https://docs.zvid.io/docs/endpoints/get-library-item/): `GET /api/library/{kind}/{slug}` - [Get creative library content](https://docs.zvid.io/docs/endpoints/get-library-content/): `GET /api/library/{kind}/{slug}/content` --- Canonical URL: https://docs.zvid.io/docs/examples/inspirational-video/ Markdown URL: https://docs.zvid.io/markdown/docs/examples/inspirational-video.md # Inspirational Video This example is a 28-second HD motivational video built from six `VIDEO` clips on one global timeline, two audio entries, and karaoke-style subtitles. These clips are not objects in the [`scenes` array](https://docs.zvid.io/docs/structure/scenes/). The complete JSON in this page is the runnable config for this example and has been checked against the current public render schema. ## Preview The full config below, rendered by Zvid [Watch rendered example](https://cdn.zvid.io/library/docs/example-inspirational.mp4) | Property | Value | | ------------- | ------------------- | | Resolution | `hd` (`1280 x 720`) | | Duration | `28` seconds | | Visual types | `VIDEO` | | Video clips | `6` | | Audio tracks | `2` | | Subtitle mode | `karaoke` | | Font | `Montserrat` | ## Config Highlights ```json { "name": "inspirational", "resolution": "hd", "duration": 28, "backgroundColor": "#000000" } ``` The clips are linked with `id` and `transitionId`. This is a visual element fragment from the complete project below: ```json { "type": "VIDEO", "id": "scene-1", "src": "https://www.pexels.com/download/video/4927963/", "width": 1280, "height": 720, "resize": "cover", "position": "center-center", "anchor": "center-center", "volume": 0, "videoBegin": 0, "videoEnd": 4.8, "enterBegin": 0.16, "exitEnd": 4.96, "transition": "fade", "transitionDuration": 0.6, "transitionId": "scene-2" } ``` The recorded fixture mixes the same audio source twice at different levels. Its captions are authored separately; this fixture does not demonstrate automatic transcription or separate narration and music sources: ```json [ { "src": "https://cdn.pixabay.com/audio/2026/02/24/audio_3f375fdf97.mp3", "volume": 1 }, { "src": "https://cdn.pixabay.com/audio/2026/02/24/audio_3f375fdf97.mp3", "volume": 0.2 } ] ``` For new projects, use the current flat subtitle style fields. This `subtitle` fragment is the equivalent styling for the recorded example: ```json { "font": { "color": "#ffffff", "bold": true, "size": 86, "family": "Montserrat" }, "position": "center-center", "stroke": { "width": 4, "color": "#000000" }, "animation": "karaoke", "activeWord": { "color": "#f3efa2" } } ``` ## How It Works Each clip is a `VIDEO` element positioned on the global timeline with `enterBegin` and `exitEnd`. The source clips are muted with `volume: 0`, and the fixture's audio entries are mixed through the root `audios` array. The first five clips declare transitions to the next clip: | Scene | Transition | Duration | | ---------------------- | ------------ | -------- | | `scene-1` to `scene-2` | `fade` | `0.6` | | `scene-2` to `scene-3` | `smoothleft` | `0.7` | | `scene-3` to `scene-4` | `fadeblack` | `0.6` | | `scene-4` to `scene-5` | `dissolve` | `0.6` | | `scene-5` to `scene-6` | `fadewhite` | `0.7` | ## Full Config The full JSON below is the recorded render's **project payload**, submitted under `payload`. Its source URLs and legacy `subtitle.styles` are retained to match the preview. For new projects, use the current subtitle fields above; do not combine them with `styles`. Validate a complete request with [Validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/) before spending credits. **Show full JSON config** ```json { "name": "inspirational", "resolution": "hd", "duration": 28, "backgroundColor": "#000000", "visuals": [ { "type": "VIDEO", "id": "scene-1", "src": "https://www.pexels.com/download/video/4927963/", "width": 1280, "height": 720, "resize": "cover", "position": "center-center", "anchor": "center-center", "volume": 0, "videoBegin": 0, "videoEnd": 4.8, "enterBegin": 0.16, "exitEnd": 4.96, "transition": "fade", "transitionDuration": 0.6, "transitionId": "scene-2" }, { "type": "VIDEO", "id": "scene-2", "src": "https://www.pexels.com/download/video/5417879/", "width": 1280, "height": 720, "resize": "cover", "position": "center-center", "anchor": "center-center", "volume": 0, "videoBegin": 0, "videoEnd": 4.64, "enterBegin": 4.96, "exitEnd": 9.6, "transition": "smoothleft", "transitionDuration": 0.7, "transitionId": "scene-3" }, { "type": "VIDEO", "id": "scene-3", "src": "https://www.pexels.com/download/video/5310849/", "width": 1280, "height": 720, "resize": "cover", "position": "center-center", "anchor": "center-center", "volume": 0, "videoBegin": 0, "videoEnd": 4.08, "enterBegin": 9.6, "exitEnd": 13.68, "transition": "fadeblack", "transitionDuration": 0.6, "transitionId": "scene-4" }, { "type": "VIDEO", "id": "scene-4", "src": "https://www.pexels.com/download/video/5946138/", "width": 1280, "height": 720, "resize": "cover", "position": "center-center", "anchor": "center-center", "volume": 0, "videoBegin": 0, "videoEnd": 4.8, "enterBegin": 13.68, "exitEnd": 18.48, "transition": "dissolve", "transitionDuration": 0.6, "transitionId": "scene-5" }, { "type": "VIDEO", "id": "scene-5", "src": "https://www.pexels.com/download/video/6607460/", "width": 1280, "height": 720, "resize": "cover", "position": "center-center", "anchor": "center-center", "volume": 0, "videoBegin": 0, "videoEnd": 5.52, "enterBegin": 18.48, "exitEnd": 24.0, "transition": "fadewhite", "transitionDuration": 0.7, "transitionId": "scene-6" }, { "type": "VIDEO", "id": "scene-6", "src": "https://www.pexels.com/download/video/2558580/", "width": 1280, "height": 720, "resize": "cover", "position": "center-center", "anchor": "center-center", "volume": 0, "videoBegin": 0, "videoEnd": 4.0, "enterBegin": 24.0, "exitEnd": 28.0 } ], "audios": [ { "src": "https://cdn.pixabay.com/audio/2026/02/24/audio_3f375fdf97.mp3", "volume": 1 }, { "src": "https://cdn.pixabay.com/audio/2026/02/24/audio_3f375fdf97.mp3", "volume": 0.2 } ], "subtitle": { "styles": { "color": "#ffffff", "isBold": true, "fontSize": 86, "fontFamily": "Montserrat", "position": "center-center", "outline": { "width": 4, "color": "#000000" }, "mode": "karaoke", "activeWord": { "color": "#f3efa2" } }, "captions": [ { "start": 0.16, "end": 0.92, "text": "You don't need to see", "words": [ { "text": "You", "start": 0.16, "end": 0.28 }, { "text": "don't", "start": 0.28, "end": 0.48 }, { "text": "need", "start": 0.48, "end": 0.6 }, { "text": "to", "start": 0.6, "end": 0.76 }, { "text": "see", "start": 0.76, "end": 0.92 } ] }, { "start": 0.92, "end": 1.68, "text": "the whole path.", "words": [ { "text": "the", "start": 0.92, "end": 1.04 }, { "text": "whole", "start": 1.04, "end": 1.28 }, { "text": "path.", "start": 1.28, "end": 1.68 } ] }, { "start": 2.0, "end": 3.16, "text": "You just need the courage", "words": [ { "text": "You", "start": 2.0, "end": 2.28 }, { "text": "just", "start": 2.28, "end": 2.48 }, { "text": "need", "start": 2.48, "end": 2.68 }, { "text": "the", "start": 2.68, "end": 2.84 }, { "text": "courage", "start": 2.84, "end": 3.16 } ] }, { "start": 3.16, "end": 4.4, "text": "to take the first step.", "words": [ { "text": "to", "start": 3.16, "end": 3.36 }, { "text": "take", "start": 3.36, "end": 3.6 }, { "text": "the", "start": 3.6, "end": 3.8 }, { "text": "first", "start": 3.8, "end": 4.04 }, { "text": "step.", "start": 4.04, "end": 4.4 } ] }, { "start": 4.96, "end": 6.48, "text": "Every great journey begins with", "words": [ { "text": "Every", "start": 4.96, "end": 5.28 }, { "text": "great", "start": 5.28, "end": 5.56 }, { "text": "journey", "start": 5.56, "end": 5.96 }, { "text": "begins", "start": 5.96, "end": 6.32 }, { "text": "with", "start": 6.32, "end": 6.48 } ] }, { "start": 6.48, "end": 7.28, "text": "uncertainty,", "words": [{ "text": "uncertainty,", "start": 6.48, "end": 7.28 }] }, { "start": 7.28, "end": 8.72, "text": "and that's where growth is", "words": [ { "text": "and", "start": 7.28, "end": 7.64 }, { "text": "that's", "start": 7.64, "end": 8.0 }, { "text": "where", "start": 8.0, "end": 8.24 }, { "text": "growth", "start": 8.24, "end": 8.48 }, { "text": "is", "start": 8.48, "end": 8.72 } ] }, { "start": 8.72, "end": 9.12, "text": "born.", "words": [{ "text": "born.", "start": 8.72, "end": 9.12 }] }, { "start": 9.6, "end": 11.04, "text": "The struggle you feel today", "words": [ { "text": "The", "start": 9.6, "end": 9.88 }, { "text": "struggle", "start": 9.88, "end": 10.24 }, { "text": "you", "start": 10.24, "end": 10.44 }, { "text": "feel", "start": 10.44, "end": 10.68 }, { "text": "today", "start": 10.68, "end": 11.04 } ] }, { "start": 11.12, "end": 12.6, "text": "is building the strength you'll", "words": [ { "text": "is", "start": 11.12, "end": 11.44 }, { "text": "building", "start": 11.44, "end": 11.72 }, { "text": "the", "start": 11.72, "end": 11.96 }, { "text": "strength", "start": 11.96, "end": 12.32 }, { "text": "you'll", "start": 12.32, "end": 12.6 } ] }, { "start": 12.6, "end": 13.36, "text": "need tomorrow.", "words": [ { "text": "need", "start": 12.6, "end": 12.8 }, { "text": "tomorrow.", "start": 12.8, "end": 13.36 } ] }, { "start": 13.68, "end": 14.8, "text": "Doubt will whisper,", "words": [ { "text": "Doubt", "start": 13.68, "end": 14.12 }, { "text": "will", "start": 14.12, "end": 14.32 }, { "text": "whisper,", "start": 14.32, "end": 14.8 } ] }, { "start": 14.96, "end": 15.92, "text": "fear will shout,", "words": [ { "text": "fear", "start": 14.96, "end": 15.32 }, { "text": "will", "start": 15.32, "end": 15.52 }, { "text": "shout,", "start": 15.52, "end": 15.92 } ] }, { "start": 16.16, "end": 17.56, "text": "but your dreams will call", "words": [ { "text": "but", "start": 16.16, "end": 16.48 }, { "text": "your", "start": 16.48, "end": 16.72 }, { "text": "dreams", "start": 16.72, "end": 17.08 }, { "text": "will", "start": 17.08, "end": 17.28 }, { "text": "call", "start": 17.28, "end": 17.56 } ] }, { "start": 17.56, "end": 18.16, "text": "louder.", "words": [{ "text": "louder.", "start": 17.56, "end": 18.16 }] }, { "start": 18.48, "end": 20.48, "text": "Success isn't about being perfect.", "words": [ { "text": "Success", "start": 18.48, "end": 18.88 }, { "text": "isn't", "start": 18.88, "end": 19.32 }, { "text": "about", "start": 19.32, "end": 19.56 }, { "text": "being", "start": 19.56, "end": 19.92 }, { "text": "perfect.", "start": 19.92, "end": 20.48 } ] }, { "start": 20.64, "end": 21.92, "text": "It's about showing up", "words": [ { "text": "It's", "start": 20.64, "end": 21.04 }, { "text": "about", "start": 21.04, "end": 21.24 }, { "text": "showing", "start": 21.24, "end": 21.64 }, { "text": "up", "start": 21.64, "end": 21.92 } ] }, { "start": 22.08, "end": 23.04, "text": "again and again.", "words": [ { "text": "again", "start": 22.08, "end": 22.44 }, { "text": "and", "start": 22.44, "end": 22.72 }, { "text": "again.", "start": 22.72, "end": 23.04 } ] }, { "start": 24.0, "end": 25.12, "text": "One day you'll look back", "words": [ { "text": "One", "start": 24.0, "end": 24.28 }, { "text": "day", "start": 24.28, "end": 24.48 }, { "text": "you'll", "start": 24.48, "end": 24.76 }, { "text": "look", "start": 24.76, "end": 24.92 }, { "text": "back", "start": 24.92, "end": 25.12 } ] }, { "start": 25.12, "end": 26.32, "text": "and realize this was the", "words": [ { "text": "and", "start": 25.12, "end": 25.36 }, { "text": "realize", "start": 25.36, "end": 25.76 }, { "text": "this", "start": 25.76, "end": 26.0 }, { "text": "was", "start": 26.0, "end": 26.2 }, { "text": "the", "start": 26.2, "end": 26.32 } ] }, { "start": 26.32, "end": 27.64, "text": "moment you decided not to", "words": [ { "text": "moment", "start": 26.32, "end": 26.6 }, { "text": "you", "start": 26.6, "end": 26.8 }, { "text": "decided", "start": 26.8, "end": 27.24 }, { "text": "not", "start": 27.24, "end": 27.48 }, { "text": "to", "start": 27.48, "end": 27.64 } ] }, { "start": 27.64, "end": 28.0, "text": "quit.", "words": [{ "text": "quit.", "start": 27.64, "end": 28.0 }] } ] } } ``` ## Key Takeaways - Use `id` and `transitionId` to link video clips on one timeline. - Set source video `volume` to `0` when narration should drive the audio. - Karaoke accepts text-only captions and distributes word timing automatically; explicit word-level `start`/`end` values provide transcript-accurate timing. - Keep background music low, often around `0.1` to `0.3`, so it does not compete with narration. --- Canonical URL: https://docs.zvid.io/docs/examples/pizza-margherita-reel/ Markdown URL: https://docs.zvid.io/markdown/docs/examples/pizza-margherita-reel.md # Pizza Margherita Reel This example is a 25.04-second portrait recipe reel built from eight `IMAGE` shots on one global timeline, one narration track, and karaoke-style subtitles. These shots are not objects in the [`scenes` array](https://docs.zvid.io/docs/structure/scenes/). The complete JSON in this page is the runnable config for this example and has been checked against the current public render schema. ## Preview The full config below, rendered by Zvid (1080×1920) [Watch rendered example](https://cdn.zvid.io/library/docs/example-pizza-reel.mp4) | Property | Value | | ------------- | -------------------------------- | | Resolution | `instagram-reel` (`1080 x 1920`) | | Duration | `25.04` seconds | | Frame rate | `30` fps | | Visual types | `IMAGE` | | Image shots | `8` | | Audio tracks | `1` | | Subtitle mode | `karaoke` | | Font | `Montserrat` | ## Config Highlights ```json { "name": "pizza-margherita-social", "resolution": "instagram-reel", "duration": 25.04, "frameRate": 30, "backgroundColor": "#000000" } ``` Each shot is a full portrait image. Most shots slide in from the right with `enterAnimation: "slideleft"`. This is a visual element fragment: ```json { "type": "IMAGE", "src": "https://cdn.zvid.io/assets/header/pizza-margherita/scene-02-ingredients.png", "width": 1080, "height": 1920, "position": "center-center", "resize": "cover", "enterBegin": 3.16, "enterEnd": 3.41, "exitEnd": 6.48, "enterAnimation": "slideleft" } ``` The narration is a single audio item: ```json [ { "src": "https://cdn.zvid.io/assets/header/pizza-margherita/voice-over.mp3", "volume": 1 } ] ``` For new projects, use this equivalent **`subtitle` style fragment** with the current flat fields, tuned for a tall mobile canvas. Add `captions` from the complete fixture below: ```json { "font": { "color": "#ffffff", "bold": true, "size": 98, "family": "Montserrat" }, "margin": { "y": 180, "x": 100 }, "stroke": { "width": 4, "color": "#000000" }, "animation": "karaoke", "activeWord": { "color": "#FFD700" } } ``` ## Shot timeline | Shot | Enters | Fully in | Ends | | ----------- | ------- | -------- | ------- | | Hook | `0` | `0` | `3.76` | | Ingredients | `3.16` | `3.41` | `6.48` | | Dough | `5.88` | `6.13` | `8.96` | | Sauce | `8.36` | `8.61` | `12.4` | | Mozzarella | `11.8` | `12.05` | `15.28` | | Bake | `14.68` | `14.93` | `25.04` | | Final touch | `17.96` | `18.21` | `25.04` | | Final shot | `21.16` | `21.41` | `25.04` | ## Full Config The full JSON below is the recorded render's **project payload**, submitted under `payload`. It retains legacy `subtitle.styles` to match the preview; use the current equivalent above for new work. Bake and Final touch omit `exitEnd`, so their layers last to the project end and are covered by later full-frame shots. `exitBegin` alone does not cut a layer out without an exit animation. For an explicit cutoff, set `exitEnd` in your own project. **Show full JSON config** ```json { "name": "pizza-margherita-social", "resolution": "instagram-reel", "duration": 25.04, "frameRate": 30, "backgroundColor": "#000000", "visuals": [ { "type": "IMAGE", "src": "https://cdn.zvid.io/assets/header/pizza-margherita/scene-01-hook.png", "width": 1080, "height": 1920, "position": "center-center", "resize": "cover", "enterBegin": 0, "exitEnd": 3.76 }, { "type": "IMAGE", "src": "https://cdn.zvid.io/assets/header/pizza-margherita/scene-02-ingredients.png", "width": 1080, "height": 1920, "position": "center-center", "resize": "cover", "enterBegin": 3.16, "enterEnd": 3.41, "exitEnd": 6.48, "enterAnimation": "slideleft" }, { "type": "IMAGE", "src": "https://cdn.zvid.io/assets/header/pizza-margherita/scene-03-dough.png", "width": 1080, "height": 1920, "position": "center-center", "resize": "cover", "enterBegin": 5.88, "enterEnd": 6.13, "exitEnd": 8.96, "enterAnimation": "slideleft" }, { "type": "IMAGE", "src": "https://cdn.zvid.io/assets/header/pizza-margherita/scene-04-sauce.png", "width": 1080, "height": 1920, "position": "center-center", "resize": "cover", "enterBegin": 8.36, "enterEnd": 8.61, "exitEnd": 12.4, "enterAnimation": "slideleft" }, { "type": "IMAGE", "src": "https://cdn.zvid.io/assets/header/pizza-margherita/scene-05-mozzarella.png", "width": 1080, "height": 1920, "position": "center-center", "resize": "cover", "enterBegin": 11.8, "enterEnd": 12.05, "exitEnd": 15.28, "enterAnimation": "slideleft" }, { "type": "IMAGE", "src": "https://cdn.zvid.io/assets/header/pizza-margherita/scene-06-bake.png", "width": 1080, "height": 1920, "position": "center-center", "resize": "cover", "enterBegin": 14.68, "enterEnd": 14.93, "exitBegin": 18.21, "enterAnimation": "slideleft" }, { "type": "IMAGE", "src": "https://cdn.zvid.io/assets/header/pizza-margherita/scene-07-final-touch.png", "width": 1080, "height": 1920, "position": "center-center", "resize": "cover", "enterBegin": 17.96, "enterEnd": 18.21, "exitBegin": 21.41, "enterAnimation": "slideleft" }, { "type": "IMAGE", "src": "https://cdn.zvid.io/assets/header/pizza-margherita/scene-08-final-shot.png", "width": 1080, "height": 1920, "position": "center-center", "resize": "cover", "enterBegin": 21.16, "enterEnd": 21.41, "exitEnd": 25.04, "enterAnimation": "slideleft" } ], "audios": [ { "src": "https://cdn.zvid.io/assets/header/pizza-margherita/voice-over.mp3", "volume": 1 } ], "subtitle": { "styles": { "color": "#ffffff", "isBold": true, "fontSize": 98, "fontFamily": "Montserrat", "marginV": 180, "marginH": 100, "outline": { "width": 4, "color": "#000000" }, "mode": "karaoke", "activeWord": { "color": "#FFD700" } }, "captions": [ { "start": 0.24, "end": 2.96, "text": "Want to make authentic", "words": [ { "start": 0.24, "end": 0.36, "text": "Want" }, { "start": 0.36, "end": 0.52, "text": "to" }, { "start": 0.52, "end": 0.72, "text": "make" }, { "start": 0.72, "end": 1.28, "text": "authentic" } ] }, { "start": 1.28, "end": 3.44, "text": "Italian pizza margherita at home,", "words": [ { "start": 1.28, "end": 1.84, "text": "Italian" }, { "start": 1.84, "end": 2.28, "text": "pizza" }, { "start": 2.28, "end": 2.96, "text": "margherita" }, { "start": 2.96, "end": 3.44, "text": "at home," } ] }, { "start": 3.76, "end": 6.16, "text": "You only need six", "words": [ { "start": 3.76, "end": 4.12, "text": "You" }, { "start": 4.12, "end": 4.44, "text": "only" }, { "start": 4.44, "end": 4.8, "text": "need" }, { "start": 4.8, "end": 5.16, "text": "six" } ] }, { "start": 5.16, "end": 6.16, "text": "simple ingredients.", "words": [ { "start": 5.16, "end": 5.6, "text": "simple" }, { "start": 5.6, "end": 6.16, "text": "ingredients." } ] }, { "start": 6.48, "end": 7.72, "text": "Stretch your dough gently.", "words": [ { "start": 6.48, "end": 6.92, "text": "Stretch" }, { "start": 6.92, "end": 7.08, "text": "your" }, { "start": 7.08, "end": 7.36, "text": "dough" }, { "start": 7.36, "end": 7.72, "text": "gently." } ] }, { "start": 7.72, "end": 8.8, "text": "Using your hands,", "words": [ { "start": 7.72, "end": 8.0, "text": "Using" }, { "start": 8.0, "end": 8.32, "text": "your" }, { "start": 8.32, "end": 8.8, "text": "hands," } ] }, { "start": 8.96, "end": 11.04, "text": "Spread a thin layer", "words": [ { "start": 8.96, "end": 9.4, "text": "Spread" }, { "start": 9.4, "end": 9.52, "text": "a" }, { "start": 9.52, "end": 9.72, "text": "thin" }, { "start": 9.72, "end": 10.04, "text": "layer" } ] }, { "start": 10.04, "end": 11.04, "text": "of tomato sauce.", "words": [ { "start": 10.04, "end": 10.2, "text": "of" }, { "start": 10.2, "end": 10.68, "text": "tomato" }, { "start": 10.68, "end": 11.04, "text": "sauce." } ] }, { "start": 11.2, "end": 12.08, "text": "Less is more.", "words": [ { "start": 11.2, "end": 11.56, "text": "Less" }, { "start": 11.56, "end": 11.8, "text": "is" }, { "start": 11.8, "end": 12.08, "text": "more." } ] }, { "start": 12.4, "end": 15.04, "text": "Tear fresh mozzarella and", "words": [ { "start": 12.4, "end": 12.8, "text": "Tear" }, { "start": 12.8, "end": 13.12, "text": "fresh" }, { "start": 13.12, "end": 13.88, "text": "mozzarella" }, { "start": 13.88, "end": 14.08, "text": "and" } ] }, { "start": 14.08, "end": 15.04, "text": "place it evenly.", "words": [ { "start": 14.08, "end": 14.28, "text": "place" }, { "start": 14.28, "end": 14.44, "text": "it" }, { "start": 14.44, "end": 15.04, "text": "evenly." } ] }, { "start": 15.28, "end": 16.88, "text": "Bake at the highest", "words": [ { "start": 15.28, "end": 15.68, "text": "Bake" }, { "start": 15.68, "end": 15.959, "text": "at" }, { "start": 15.959, "end": 16.16, "text": "the" }, { "start": 16.16, "end": 16.48, "text": "highest" } ] }, { "start": 16.48, "end": 18.4, "text": "temperature for 7-10 minutes.", "words": [ { "start": 16.48, "end": 16.88, "text": "temperature" }, { "start": 16.88, "end": 17.2, "text": "for" }, { "start": 17.2, "end": 17.92, "text": "7-10" }, { "start": 17.92, "end": 18.4, "text": "minutes." } ] }, { "start": 18.56, "end": 20.36, "text": "Finish with fresh basil", "words": [ { "start": 18.56, "end": 19.0, "text": "Finish" }, { "start": 19.0, "end": 19.2, "text": "with" }, { "start": 19.2, "end": 19.56, "text": "fresh" }, { "start": 19.56, "end": 20.04, "text": "basil" } ] }, { "start": 20.04, "end": 21.44, "text": "and a drizzle of olive oil.", "words": [ { "start": 20.04, "end": 20.2, "text": "and" }, { "start": 20.2, "end": 20.36, "text": "a" }, { "start": 20.36, "end": 20.76, "text": "drizzle" }, { "start": 20.76, "end": 20.88, "text": "of" }, { "start": 20.88, "end": 21.16, "text": "olive" }, { "start": 21.16, "end": 21.44, "text": "oil." } ] }, { "start": 21.76, "end": 22.56, "text": "And that's it.", "words": [ { "start": 21.76, "end": 22.04, "text": "And" }, { "start": 22.04, "end": 22.28, "text": "that's" }, { "start": 22.28, "end": 22.56, "text": "it." } ] }, { "start": 22.72, "end": 25.04, "text": "Simple, fresh, delicious.", "words": [ { "start": 22.72, "end": 23.36, "text": "Simple," }, { "start": 23.52, "end": 24.0, "text": "fresh," }, { "start": 24.32, "end": 25.04, "text": "delicious." } ] } ] } } ``` ## Key Takeaways - `instagram-reel` sets the canvas to `1080 x 1920`. - Consistent short slide animations create a recipe-step rhythm. - `margin.y` and `margin.x` keep subtitles readable on portrait videos (legacy equivalents: `styles.marginV` and `styles.marginH`). - Match caption timing to the narration; the JSON includes authored timings, and submitting an audio track does not automatically transcribe it. --- Canonical URL: https://docs.zvid.io/docs/examples/zvid-ad/ Markdown URL: https://docs.zvid.io/markdown/docs/examples/zvid-ad.md # Zvid Ad This example is a 14-second HD promotional ad built entirely with HTML `TEXT` elements for styled text plus a few `SVG` elements for vector decoration. It demonstrates a fully programmatic video with no external image or video footage. The complete JSON in this page is the runnable config for this example. > **Author this with scenes** This ad uses one global timeline with conceptual sections ("intro", "flow", "CTA"). For multi-part videos like this, the [`scenes`](https://docs.zvid.io/docs/structure/scenes/) API lets each section keep its own local timeline and add clean cross-section transitions — usually the better structure for new projects. ## Preview The full config below, rendered by Zvid — no external footage [Watch rendered example](https://cdn.zvid.io/library/docs/example-zvid-ad.mp4) | Property | Value | | ------------ | --------------------- | | Resolution | `hd` (`1280 x 720`) | | Duration | `14` seconds | | Frame rate | `30` fps | | Visual types | `TEXT` (HTML) + `SVG` | | Elements | `13` | | Audio | none | | Sections | intro, flow, CTA | ## Config Highlights ```json { "name": "zvid-ad", "resolution": "hd", "duration": 14, "frameRate": 30, "backgroundColor": "#0b0614" } ``` The first element is a persistent background, authored as an [SVG element](https://docs.zvid.io/docs/structure/svg-elements/). New HTML layouts can also use the [safe inline SVG subset](https://docs.zvid.io/docs/structure/text-elements/#inline-svg-in-html). This abbreviated element fragment illustrates the type; the complete markup is in the recorded payload below: ```json { "type": "SVG", "width": 1280, "height": 720, "svg": "..." } ``` The remaining elements are HTML elements with inline styles: ```json { "type": "TEXT", "x": 640, "y": 238, "width": 980, "anchor": "center-center", "track": 10, "enterBegin": 0.5, "enterEnd": 1.2, "enterAnimation": "fade", "exitBegin": 4.2, "exitEnd": 4.8, "exitAnimation": "fade", "html": "
Your data. Your brand.
Instant videos.
" } ``` Track values layer the scene: | Track | Purpose | | ---------- | ---------------------------------- | | `1` | Decorative background details | | `3` to `5` | Mid-section cards and flow diagram | | `8` | Feature tags and CTA cards | | `10` | Main text | ## How It Works The ad is organized into three timed sections: | Section | Time range | Content | | ------- | --------------- | ----------------------------------------------------------- | | Intro | `0` to `4.8` | Headline and supporting copy fade in and out. | | Flow | `4.6` to `10.8` | Data, JSON, and video cards show the product concept. | | CTA | `10.6` to `14` | Final call-to-action text and feature cards remain visible. | The example uses staggered `enterBegin` values to create rhythm without video footage. Most elements also have `exitBegin` and `exitEnd` values so each section clears cleanly before the next one settles. ## Full Config The full JSON below is the recorded render's **project payload**. Submit it under `payload`, as shown in [Quick Start](https://docs.zvid.io/docs/quick-start/), and run [free validation](https://docs.zvid.io/docs/validate-and-estimate/) before rendering. **Show full JSON config** ```json { "name": "zvid-ad", "resolution": "hd", "duration": 14, "frameRate": 30, "backgroundColor": "#0b0614", "visuals": [ { "type": "SVG", "width": 1280, "height": 720, "svg": "" }, { "type": "SVG", "x": 1020, "y": 120, "width": 240, "height": 240, "anchor": "center-center", "opacity": 0.65, "track": 1, "enterBegin": 0, "enterEnd": 1, "enterAnimation": "fade", "exitBegin": 4.2, "exitEnd": 4.8, "exitAnimation": "fade", "svg": "" }, { "type": "TEXT", "x": 640, "y": 238, "width": 980, "anchor": "center-center", "track": 10, "enterBegin": 0.5, "enterEnd": 1.2, "enterAnimation": "fade", "exitBegin": 4.2, "exitEnd": 4.8, "exitAnimation": "fade", "html": "
Your data. Your brand.
Instant videos.
" }, { "type": "TEXT", "x": 640, "y": 352, "width": 980, "anchor": "center-center", "track": 10, "enterBegin": 0.9, "enterEnd": 1.6, "enterAnimation": "fade", "exitBegin": 4.1, "exitEnd": 4.8, "exitAnimation": "fade", "html": "
Create product promos, offers, and announcements
automatically for every campaign and every channel.
" }, { "type": "TEXT", "x": 640, "y": 462, "width": 900, "anchor": "center-center", "track": 10, "enterBegin": 1.3, "enterEnd": 2, "enterAnimation": "fade", "exitBegin": 4, "exitEnd": 4.7, "exitAnimation": "fade", "html": "
From one creative system to thousands of polished brand-safe videos.
" }, { "type": "TEXT", "x": 640, "y": 112, "width": 1080, "anchor": "center-center", "track": 10, "enterBegin": 4.6, "enterEnd": 5.2, "enterAnimation": "fade", "exitBegin": 10.2, "exitEnd": 10.8, "exitAnimation": "fade", "html": "
Turn structured content into
high-performing video creative
" }, { "type": "SVG", "x": 305, "y": 390, "width": 370, "height": 300, "anchor": "center-center", "track": 3, "enterBegin": 5, "enterEnd": 5.6, "enterAnimation": "smoothleft", "exitBegin": 10.1, "exitEnd": 10.7, "exitAnimation": "fade", "svg": "Catalog Data" }, { "type": "SVG", "x": 640, "y": 392, "width": 220, "height": 180, "anchor": "center-center", "track": 5, "enterBegin": 5.3, "enterEnd": 5.9, "enterAnimation": "fade", "exitBegin": 10, "exitEnd": 10.6, "exitAnimation": "fade", "svg": "DATAJSONVIDEO" }, { "type": "SVG", "x": 972, "y": 390, "width": 410, "height": 316, "anchor": "center-center", "track": 4, "enterBegin": 5.2, "enterEnd": 5.8, "enterAnimation": "smoothright", "exitBegin": 10.1, "exitEnd": 10.7, "exitAnimation": "fade", "svg": "On-brandFast editsScale" }, { "type": "TEXT", "x": 640, "y": 604, "width": 860, "anchor": "center-center", "track": 8, "enterBegin": 5.7, "enterEnd": 6.3, "enterAnimation": "fade", "exitBegin": 10, "exitEnd": 10.6, "exitAnimation": "fade", "html": "
OffersBrand stylingLocalizationBulk output
" }, { "type": "TEXT", "x": 640, "y": 228, "width": 1000, "anchor": "center-center", "track": 10, "enterBegin": 10.6, "enterEnd": 11.2, "enterAnimation": "fade", "html": "
Launch more campaigns.
Keep every video on-brand.
" }, { "type": "TEXT", "x": 640, "y": 350, "width": 980, "anchor": "center-center", "track": 10, "enterBegin": 10.9, "enterEnd": 11.5, "enterAnimation": "fade", "html": "
Zvid helps SaaS teams, marketplaces, and modern brands turn structured content into compelling video at scale.
" }, { "type": "TEXT", "x": 640, "y": 520, "anchor": "center-center", "track": 8, "enterBegin": 11.2, "enterEnd": 11.8, "enterAnimation": "fade", "html": "
Faster production
Consistent output
Built for scale
" } ] } ``` ## Key Takeaways - Styled `TEXT` elements and vector `SVG` layers can produce a programmatic ad without external footage; this recorded example uses both types. - A persistent full-frame background element is efficient for branded layouts. - Use `track` intentionally so text stays above decorative layers. - Staggered enter timings make static sections feel more dynamic. - For multi-part videos, consider the [`scenes`](https://docs.zvid.io/docs/structure/scenes/) API to keep each section on its own local timeline. --- Canonical URL: https://docs.zvid.io/docs/faq/ Markdown URL: https://docs.zvid.io/markdown/docs/faq.md # Frequently Asked Questions Short answers with links to the canonical pages. ## Project Configuration ### What video dimensions should I use? Use a [resolution preset](https://docs.zvid.io/docs/structure/properties/resolution-presets/) for standard social/HD formats (e.g. `instagram-post`, `youtube-short`, `full-hd`), or `resolution: "custom"` with explicit `width`/`height` for anything else. Output dimensions are limited by your plan. ### What frame rate should I use? `30` for most content, `60` for smoother motion (larger files), `24` for a cinematic look, `25` for PAL. The API accepts integers from `1` to `60`. ### Which output format should I choose? `mp4` (recommended), `webm`, `mov`, or `avi`. For still images, see [Rendering Images](https://docs.zvid.io/docs/rendering-images/). ## Elements ### How do I center elements? Set `position: "center-center"` — see [Common Element Properties](https://docs.zvid.io/docs/structure/common-properties/). ### How do I size media? Provide `width`/`height` and optionally [`resize`](https://docs.zvid.io/docs/structure/properties/resize/): `contain` keeps the full asset visible, `cover` fills the box and may crop. ### How do timing properties work? `enterBegin → enterEnd → exitBegin → exitEnd`, in seconds. See [Common Element Properties](https://docs.zvid.io/docs/structure/common-properties/#timing). ### Why are animations not showing? The animation window has zero length, the effect name isn't a supported [xfade name](https://docs.zvid.io/docs/structure/properties/xfade-effects/), or the timing is outside the project duration. See [Animations](https://docs.zvid.io/docs/structure/animations/). ## Media Files ### What media formats are supported? Input assets are remote HTTP/HTTPS URLs; FFmpeg-compatible media generally works (JPEG/PNG/WebP images, MP4/MOV/WebM/MKV video, MP3/WAV/AAC/OGG/M4A audio), subject to plan limits. In [the editor](https://docs.zvid.io/docs/editor/media/) you can also upload files to your media library. ### Are there hard limits? Yes — plan-dependent limits on duration, output/input resolution, media size, and element counts. Validation errors include your active limits. See [Credits, Plans & Limits](https://docs.zvid.io/docs/credits-and-plans/). ## Audio ### What volume values are allowed? `0` to `2`, on both [audio tracks](https://docs.zvid.io/docs/structure/audio-elements/) and video elements. `0` mutes, `1` preserves the source level, and values above `1` amplify it; loud sources can clip. ## Text and Fonts ### Why is my custom font not displaying? Use an exact Google Fonts family name (`Poppins`, `Montserrat`, `Roboto`, …) in `style.fontFamily`. See [Text & HTML Elements](https://docs.zvid.io/docs/structure/text-elements/#font-handling). ### Why is my HTML text rejected? `TEXT.html` accepts a safe HTML subset (`div`, `span`, `p`, `strong`, `em`, lists, …) — no script/style tags, event handlers, or external CSS URLs. Rich styling belongs in [`customCode`](https://docs.zvid.io/docs/structure/text-elements/#native-css--javascript-customcode). ## Templates & Automation ### How do I render the same video with different data? Author a [template with variables](https://docs.zvid.io/docs/templates/template-basics/) and render it with per-request values — or thousands at once with [bulk rendering](https://docs.zvid.io/docs/automation/bulk-rendering/). ### How do I get notified when a render finishes? Register a [webhook](https://docs.zvid.io/docs/automation/webhooks/) or pass a per-request `webhookUrl`. ## Editor ### Is editor JSON compatible with the API? The editor exports Zvid project JSON. Before submitting it, run [free hosted validation](https://docs.zvid.io/docs/validate-and-estimate/) with your API key: the hosted API checks your plan and applies stricter limits than some editor controls. See [Rendering & export](https://docs.zvid.io/docs/editor/export/). ## Getting Help - [JSON Structure Overview](https://docs.zvid.io/docs/structure/) - [Quick Start](https://docs.zvid.io/docs/quick-start/) - [Zvid community on Discord](https://discord.gg/MZyWKqHDj3) — ask questions, discuss workflows, and share your work. - [Contact support](https://zvid.io/contact) --- Canonical URL: https://docs.zvid.io/docs/integrations/ Markdown URL: https://docs.zvid.io/markdown/docs/integrations.md # Choose an SDK or automation integration All integrations reach the Zvid API, but installation, available actions and approval behavior differ. For the exact current request contract, use the [API reference](https://docs.zvid.io/docs/endpoints/zvid-api/) and [free validation](https://docs.zvid.io/docs/validate-and-estimate/). ## Select the interface | Interface | Best starting point | Authentication | | --- | --- | --- | | Direct REST | Any server language or an HTTP automation step | `X-Api-Key` header from [API keys](https://app.zvid.io/api-keys) | | JavaScript / TypeScript | A Node.js application using the published `@zvid/sdk` | API key kept on your server | | Hosted MCP | ChatGPT, Codex and other MCP clients | Account OAuth; API-key header for clients that support it | | n8n | Visual automation and MCP-powered agents | Zvid credentials for action nodes; MCP OAuth2 API or Header Auth for MCP | | Make | The Zvid custom app and available modules | Zvid connection/API key | | Python, LangChain, Vercel AI tools | Source integrations under development | API key; verify local build and dependency instructions | ## JavaScript and TypeScript Install the published package: ```bash npm install @zvid/sdk ``` The package name is **`@zvid/sdk`**. Keep `ZVID_API_KEY` in your server environment. Review the SDK's retry settings before submitting paid jobs; automatic retries on an uncertain submission can create duplicate work. Use the [safe standalone REST example](https://docs.zvid.io/examples/render-once.mjs) if you need validation followed by exactly one submission. Package availability was checked on 22 September 2026: [`@zvid/sdk`](https://www.npmjs.com/package/@zvid/sdk) version 0.2.0, [`@zvid/mcp`](https://www.npmjs.com/package/@zvid/mcp) version 0.1.0, and [`@zvid/n8n-nodes-zvid`](https://www.npmjs.com/package/@zvid/n8n-nodes-zvid) version 0.1.8 were published. These are verification snapshots, not a promise that they remain the latest versions. Check release notes before upgrading. ## ChatGPT and other assistants Use [the assistant setup guide](https://docs.zvid.io/docs/ai-assistants/) for the hosted endpoint `https://mcp.zvid.io/mcp`, profiles, account connections, credit ceilings and the draft/quote/approval flow. Reading the documentation alone does not grant an assistant access to your Zvid account. ## n8n For direct Zvid action and trigger nodes, install **`@zvid/n8n-nodes-zvid`** from **Settings → Community nodes** before configuring Zvid credentials. A workspace owner or administrator may need to install it. Use the supported Zvid node operation when one is available. For a chat agent, use n8n's built-in **MCP Client Tool** with `https://mcp.zvid.io/mcp`. Use an **MCP OAuth2 API** credential for account sign-in or Header Auth with `x-api-key`. The removed `zvidAgentTools` node is not an installation option. MCP profile and `maxRenderCredits` settings are URL query parameters; see [assistant profiles](https://docs.zvid.io/docs/ai-assistants/). Rendering in an automation spends credits. Validate input, preserve returned job IDs, and gate any watch/download step on completed media. An MCP agent's approval workflow and a direct node's render action have different controls. ## Make and source previews The Make integration is a custom app; use its supplied installation and connection instructions rather than assuming every workspace can find it in a public marketplace. Availability and module sets should be verified in your workspace. The Python `zvid`, `langchain-zvid` and `zvid-ai-tools` packages were **not available from their public registries** when checked on 22 September 2026. Their source code is not evidence that a registry installation will work. Use the corresponding source README for local development, or call REST from your language. Public installation commands should be added only after a verified release. ## Production checklist Validate and estimate before rendering, handle both numeric and object job progress, normalize the final output URL, and use [signed webhooks](https://docs.zvid.io/docs/automation/webhooks/) or [bounded polling](https://docs.zvid.io/docs/operations/render-lifecycle/). Follow [error and retry guidance](https://docs.zvid.io/docs/operations/errors-and-retries/) and verify which operations your installed integration actually exposes. --- Canonical URL: https://docs.zvid.io/docs/intro/ Markdown URL: https://docs.zvid.io/markdown/docs/intro.md # What is Zvid? Zvid is a platform for **creating videos and images programmatically**. You describe a video as structured JSON — clips, images, text, audio, subtitles, scenes — submit it to the API, and Zvid renders it in the cloud and hands back a CDN URL. The same JSON powers a full **visual editor**, so you can design visually and render via API, or go JSON-first the whole way. ## How it works 1. **Describe** — build a JSON `payload`: elements, scenes, subtitles, timing. 2. **Submit** — `POST /api/render/api-key` with your API key. Zvid validates the payload, checks credits and plan limits, and queues the job. 3. **Render** — Zvid resolves your remote assets and renders the video (or image) in the cloud. 4. **Deliver** — poll `GET /api/jobs/{id}` (or receive a [webhook](https://docs.zvid.io/docs/automation/webhooks/)) and get the final CDN URL. ## What you can build - **Rich compositions** — text, HTML with native CSS & JavaScript, images, GIFs, video clips, audio tracks, and word-timed subtitles with [word animation modes](https://docs.zvid.io/docs/structure/subtitle/). - **Multi-part videos** — [scenes](https://docs.zvid.io/docs/structure/scenes/) with local timelines and cross-scene transitions. - **Data-driven videos** — [templates with variables](https://docs.zvid.io/docs/templates/template-basics/), rendered in bulk with per-item data. - **Still images** — the same payload can render a [PNG, JPG, or WebP](https://docs.zvid.io/docs/rendering-images/). - **Automated pipelines** — [webhooks](https://docs.zvid.io/docs/automation/webhooks/) and [bulk rendering](https://docs.zvid.io/docs/automation/bulk-rendering/) for hands-off workflows. ## Choose how to work | | | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **The API** | Server-to-server JSON rendering with API keys. Start with the [Quick Start](https://docs.zvid.io/docs/quick-start/). | | **The Editor** | A browser-based visual editor at [editor.zvid.io](https://editor.zvid.io) — design on a canvas and timeline, then render directly or export the JSON. Start with the [Editor overview](https://docs.zvid.io/docs/editor/overview/). | | **AI assistants** | Connect an assistant through Zvid's MCP endpoint to discover capabilities, validate a project, and render with your approval. See [AI assistants](https://docs.zvid.io/docs/ai-assistants/). | | **Integrations** | Use supported automation tools or SDKs around the same API. See [Integrations](https://docs.zvid.io/docs/integrations/). | The editor imports and exports Zvid project JSON. Run [free hosted validation](https://docs.zvid.io/docs/validate-and-estimate/) before rendering an export: account limits and hosted constraints apply regardless of how you authored the project. ## Next steps - Follow the [Quick Start](https://docs.zvid.io/docs/quick-start/) — first render in under five minutes. - Read the [Authentication guide](https://docs.zvid.io/docs/authentication/). - Explore the [JSON structure](https://docs.zvid.io/docs/structure/) or the [Editor](https://docs.zvid.io/docs/editor/overview/). Join the [Zvid community on Discord](https://discord.gg/MZyWKqHDj3) to ask questions, exchange workflow ideas, and share what you make. For private account or billing questions, contact us at [https://zvid.io/contact](https://zvid.io/contact). --- Canonical URL: https://docs.zvid.io/docs/operations/errors-and-retries/ Markdown URL: https://docs.zvid.io/markdown/docs/operations/errors-and-retries.md # Handle errors, timeouts and retries Read the HTTP status and structured error body together. Preserve the operation, job ID when available, `error`, `message` and validation details. Keep API keys, webhook secrets and sensitive variable data out of logs. ## Choose the correct recovery | Status or symptom | Typical cause | Recovery | | --- | --- | --- | | 400 | Invalid fields, mutually exclusive inputs, unresolved variables, plan limits or unsupported options | Correct the request using `details`; validate it again | | 401 | Missing, invalid or revoked credentials | Replace or correct authentication; do not repeatedly retry the same key | | 402 | Insufficient available render credits | Inspect required/available credits and account balance before resubmitting | | 403 | Account permission or access restriction | Verify the account and capability allowed for that route | | 404 | Missing resource or resource not owned by this account | Verify ID and account; do not assume a job belongs to another user | | 409 | A state conflict, such as deleting an active job | Re-read the resource and follow the endpoint's lifecycle rules | | 413 | Request or upload exceeds a size limit | Reduce or split the input within documented limits | | 429 | Hourly rate or concurrent-job capacity, including capacity needed by a bulk request | Read `current`, `limit`, `retryAfter` where supplied and honor `Retry-After` | | 502, 503, 504 or network timeout | Temporary service/network problem, maintenance, or an uncertain response | Retry safe reads with bounded backoff; reconcile state-changing requests first | | `state: "failed"` | An accepted render failed | Inspect `failedReason`, correct the cause and deliberately submit a new job if needed | Exceeding the bulk request's item-count cap is a `400` validation error; it is different from a `429` capacity limit. Not every endpoint returns every status or field. The endpoint reference documents its contract. Errors can occur before the controller, so a failed response is not always shaped like a successful response with one extra property. ## Avoid duplicate paid renders A connection can fail after a render was accepted but before your client received its job ID. Blindly retrying the POST can create another paid render. The presence of a client-supplied job identifier does not establish a general idempotency guarantee for all routes. Store successful submission responses immediately. If the outcome is uncertain, inspect recent jobs and the dashboard, correlate with your business record and any returned job identifier, then decide whether to submit again. Do not turn a polling timeout into a new render submission. SDKs and HTTP libraries can retry automatically, including POST requests. Review those defaults and disable request retries for paid submissions when your application cannot reconcile duplicates. The [Node.js example](https://docs.zvid.io/examples/render-once.mjs) submits once and retries only status reads. ## Bounded retries for reads Use exponential backoff with jitter for temporary read failures. Respect a valid `Retry-After` value, whether it is a number of seconds or an HTTP date. Bound both total waiting time and per-request timeout, and stop when the caller cancels. For `429` caused by a concurrent-job limit, allowing current jobs to finish may be necessary; rapid retries will not create capacity. For a validation error, retries with identical input will not fix the request. ## Diagnose media and layout separately A structurally valid project can still encounter an inaccessible source URL, corrupt media or rendering failure. Check [media access](https://docs.zvid.io/docs/concepts/media-assets/), then isolate the failing element. Incorrect placement, clipping and timing often need a payload correction rather than a retry; use the [layout](https://docs.zvid.io/docs/concepts/layout/) and [timing](https://docs.zvid.io/docs/concepts/timing/) guides. When asking for help at [https://zvid.io/contact](https://zvid.io/contact), include the job ID, approximate UTC time, route, HTTP status and a redacted minimal request. Never include the API key or webhook signing secret. --- Canonical URL: https://docs.zvid.io/docs/operations/render-lifecycle/ Markdown URL: https://docs.zvid.io/markdown/docs/operations/render-lifecycle.md # Submit, track and retrieve a render A render submission creates an asynchronous job. Acceptance means the job was submitted, not that a video or image is ready. ## The complete flow 1. Build a project or select a stored template and variable values. 2. [Validate the exact request and inspect its cost](https://docs.zvid.io/docs/validate-and-estimate/). 3. Submit to the video or image endpoint. Credits are reserved during submission. 4. Store the returned `jobId` with your own business record before doing other work. 5. Poll `GET /api/jobs/{jobId}` or receive a [webhook](https://docs.zvid.io/docs/automation/webhooks/). 6. On completion, read the output URL and store it where your application needs it. On failure, record `failedReason` and decide whether the cause can be corrected. The [downloadable Node.js example](https://docs.zvid.io/examples/render-once.mjs) implements validation, a single submission and bounded polling. It validates only unless you explicitly pass `--render`. ## Job state and progress The queue may report intermediate states such as `waiting`, `delayed`, `prioritized`, `active` or `waiting-children`. Keep waiting within your deadline unless `state` is `completed` or `failed`. An unexpected state is useful diagnostic information; do not invent a successful result from it. `progress` can be a number or an object with `percentage` and other stage information. It is a progress hint, not a completion test. A value of 100 does not replace checking `state` and `result`. Job data can come from the live queue or stored history. Queue timestamps use epoch milliseconds; stored timestamp fields can be date strings. Normalize them before doing arithmetic. Do not assume the response has one timestamp representation forever. ## Extract the output URL For a completed job, `result` can be an object with `url` or a URL string from stored history: ```js const url = typeof job.result === "string" ? job.result : job.result?.url; if (job.state === "completed" && !url) { throw new Error("Completed job has no output URL; retain the job ID for investigation."); } ``` Only use the final media when the job reports completion and a usable URL. A thumbnail or preview is not the finished output. Keep your job ID alongside the output URL for support and reconciliation. ## Polling and webhooks For polling, use a delay, an overall deadline and a timeout on each network request. Retry safe status reads after temporary failures; honor `Retry-After` when present. A polling timeout means the client stopped waiting, not that rendering failed. Resume tracking the **same** job. For production automation, signed account webhooks avoid constant polling. Verify the original request bytes and timestamp, acknowledge promptly, and handle repeated deliveries idempotently. A per-request `webhookUrl` is unsigned; see [webhook security and payloads](https://docs.zvid.io/docs/automation/webhooks/). ## Failures, cancellation and retention Submission failures and render failures are different. A rejected submission may have no job ID. An accepted job can later fail while fetching media or rendering. See [errors and retries](https://docs.zvid.io/docs/operations/errors-and-retries/) before resubmitting. Job deletion requires a dashboard session/JWT; an API key can read job status but cannot call the delete endpoint. Deleting a queued job can cancel work and trigger the applicable refund behavior. An active job cannot simply be deleted; the API returns a conflict. Deleting a completed render can also remove its stored output. Treat deletion as a lifecycle action, not as a way to hide a dashboard row. Do not assume CDN output is a permanent archive. If your product requires long-term retention, copy completed media to storage you control under your own retention policy. See [media and URLs](https://docs.zvid.io/docs/concepts/media-assets/). --- Canonical URL: https://docs.zvid.io/docs/quick-start/ Markdown URL: https://docs.zvid.io/markdown/docs/quick-start.md # Quick Start Render your first video by verifying your API key, checking credits, validating a JSON payload, submitting it, and polling until the video URL is ready. For an AI assistant connection, see [AI assistants](https://docs.zvid.io/docs/ai-assistants/). ## Prerequisites - A Zvid account - An [API key](https://docs.zvid.io/docs/authentication/) from the [dashboard](https://app.zvid.io/api-keys) - Sufficient credits for the render ## Step 1: Verify Your API Key ```bash curl -X GET https://api.zvid.io/api/user/profile \ -H "x-api-key: YOUR_API_KEY" ``` Example response: ```json { "user": { "id": 123, "email": "user@example.com", "firstName": "John", "lastName": "Doe", "createdAt": "2025-09-01T00:00:00.000Z" }, "credits": { "balance": 1164, "subscriptionCredits": 1164, "addonCredits": { "balance": 0, "totalEarned": 0, "totalSpent": 0 } } } ``` ## Step 2: Check Your Credit Balance ```bash curl -X GET https://api.zvid.io/api/credits/balance \ -H "x-api-key: YOUR_API_KEY" ``` ```json { "balance": 1164, "subscriptionCredits": 1164, "addonCredits": { "balance": 0, "totalEarned": 0, "totalSpent": 0 } } ``` ## Step 3: Validate and submit your first render The complete request below renders a 10-second Full HD video for **10 credits**. First send this same body to `POST /api/render/validate/api-key` to check it for free. A successful validation returns `valid: true` and `creditsRequired`; it does not create a job. Then send it to the render URL shown below when you are ready to spend credits. See [Validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/) for the full preflight response. ```bash curl -X POST https://api.zvid.io/api/render/api-key \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payload": { "name": "hello-zvid", "width": 1920, "height": 1080, "duration": 10, "frameRate": 30, "backgroundColor": "#000000", "visuals": [ { "type": "TEXT", "text": "Hello, Zvid!", "x": 960, "y": 540, "anchor": "center-center", "style": { "fontSize": 72, "color": "#ffffff", "fontFamily": "Arial" } } ] } }' ``` Example response: ```json { "jobId": "550e8400-e29b-41d4-a716-446655440000", "status": "queued", "queuePosition": 2, "creditsReserved": 10 } ``` > **Save the `jobId`** It is the `{id}` used with the jobs endpoint in the next step. ## Step 4: Poll the Render Job ```bash curl -X GET https://api.zvid.io/api/jobs/{id} \ -H "x-api-key: YOUR_API_KEY" ``` ### Processing ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "state": "active", "progress": { "phase": "rendering", "percentage": 65, "message": "Rendering frames..." }, "result": null, "failedReason": null, "ts": { "created": 1774290612835, "updated": 1774290615000, "finished": null } } ``` ### Completed ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "state": "completed", "progress": { "phase": "uploading", "percentage": 100, "message": "Uploading to B2: 100%" }, "result": { "ok": true, "renderDuration": 16, "totalDuration": 17, "finishedAt": "2026-03-23T18:30:29.888Z", "url": "https://cdn.zvid.io/videos/4/hello-zvid.mp4", "thumbnailUrl": "https://cdn.zvid.io/images/4/hello-zvid_thumbnail.jpg", "size": 3848989, "fileName": "hello-zvid.mp4", "duration": 10 }, "failedReason": null, "ts": { "created": 1774290612835, "updated": 1774290612835, "finished": 1774290629888 } } ``` ### Failed ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "state": "failed", "progress": { "phase": "rendering", "percentage": 40, "message": "Processing failed" }, "result": null, "failedReason": "Rendering failed due to invalid asset", "ts": { "created": 1774290612835, "updated": 1774290617000, "finished": 1774290617000 } } ``` Poll every few seconds with a bounded timeout. When `state` is `completed`, use `result.url`; when it is `failed`, inspect `failedReason` and stop polling. A submission `jobId` identifies the job; the lookup response uses `id` and `state`. Do not submit the render again merely because a poll failed. See [Render lifecycle](https://docs.zvid.io/docs/operations/render-lifecycle/) and [Errors and retries](https://docs.zvid.io/docs/operations/errors-and-retries/) for recovery. This recorded preview shows the composition submitted in step 3. The response URLs above are examples; use the `result.url` returned by your own job: hello-zvid.mp4 — 1920×1080, 10 s [Watch rendered example](https://cdn.zvid.io/library/docs/hello-zvid.mp4) ### Response fields - `id`: Render job ID. - `state`: Job state — `waiting`, `active`, `completed`, or `failed`. - `progress`: Render/upload progress: a number or an object with `phase`, `percentage`, and `message`. - `result`: Final render output when completed. - `failedReason`: Error message when the job fails. - `ts`: Created, updated, and finished Unix timestamps in milliseconds; unfinished timestamps can be `null`. > **Prefer push over polling?** Register a [webhook](https://docs.zvid.io/docs/automation/webhooks/) or pass a per-request `webhookUrl` and Zvid calls you when the job finishes. ## Error Handling Validation errors return `400` with field-level details: ```json { "error": "Validation failed", "message": "Please check your input and try again", "details": [ { "field": "payload.duration", "message": "Duration must be at least 0.1 seconds" } ] } ``` Fix the listed fields and submit the request again. When a payload exceeds your plan's limits, the error message includes the active limits. ## API base URL ```text https://api.zvid.io ``` REST paths begin with `/api`, for example `/api/jobs/{id}`. Do not add a second `/api` when using an SDK that already prefixes its routes. ## Next Steps - Explore the [JSON Structure Overview](https://docs.zvid.io/docs/structure/). - Design visually in [the Editor](https://docs.zvid.io/docs/editor/overview/). - Automate with [webhooks](https://docs.zvid.io/docs/automation/webhooks/) and [bulk rendering](https://docs.zvid.io/docs/automation/bulk-rendering/). - Browse the [Examples](https://docs.zvid.io/docs/examples/inspirational-video/). - Share your first render or ask a question in the [Zvid Discord community](https://discord.gg/MZyWKqHDj3). --- Canonical URL: https://docs.zvid.io/docs/recipes/ Markdown URL: https://docs.zvid.io/markdown/docs/recipes.md # Build a complete video or image workflow Choose a recipe, start from its complete project example, then adapt one concern at a time. Examples show the project inside the REST `payload` envelope where labelled. Use an API key and [free validation](https://docs.zvid.io/docs/validate-and-estimate/) before submitting. Rendering spends credits; validation and template dry-runs do not. ## Add captions to a clip Start with the [subtitle examples](https://docs.zvid.io/docs/structure/subtitle/) and the [complete inspirational video](https://docs.zvid.io/docs/examples/inspirational-video/). 1. Set the video source to your reachable media URL and bound its source/output timing. 2. Add caption text and start/end times under the root `subtitle` property. Use optional word timing when you need precise alignment; words are automatically timed from the caption text when explicit words are absent. 3. Choose a caption box position, typography and readable background. Rounded backgrounds belong to the documented subtitle background style fields. 4. Validate, render and inspect the first/last caption and scene boundaries. Zvid's renderer consumes supplied captions; do not assume a render automatically transcribes your source audio. Caption and word timestamps both use absolute seconds on the project timeline. Follow the exact timing rules in [Caption](https://docs.zvid.io/docs/structure/properties/caption/) and [Word](https://docs.zvid.io/docs/structure/properties/word/). ## Create a branded still image Use the complete request in [Rendering images](https://docs.zvid.io/docs/rendering-images/). 1. Choose `type: "image"`, an output size and a supported still-image format. 2. Replace copy, colors and image sources. Fit variable text deliberately and check its longest likely value. 3. Remove unsupported video elements and timeline fields; a video payload cannot be converted by changing `type` alone. 4. Validate, submit to the image endpoint, then retrieve the completed image URL. Use PNG or WebP when transparency is required. The [layout guide](https://docs.zvid.io/docs/concepts/layout/) explains how anchors and position presets affect placement. ## Adapt a product or promotional video Start from the [complete Zvid ad](https://docs.zvid.io/docs/examples/zvid-ad/) or [pizza reel](https://docs.zvid.io/docs/examples/pizza-margherita-reel/). Keep the displayed fixture and rendered preview paired while studying them; your own edits will create a different result. 1. Preserve the timeline and layer structure while replacing copy and source assets. 2. Check source aspect ratios, text lengths and available audio/video durations. 3. Review the timing after trims, speed changes and transition overlaps. 4. Validate the whole composition, render it, and inspect the output before sending it to a customer. For an assistant, the [Creator workflow](https://docs.zvid.io/docs/ai-assistants/) can discover an appropriate library example and prepare a draft/quote for review. ## Turn a list into a slideshow Use [Scenes](https://docs.zvid.io/docs/structure/scenes/) for a complete scene project, then [Dynamic content](https://docs.zvid.io/docs/templates/dynamic-content/) for `iterate` and conditions. Each array item in a scene's `iterate` creates one scene inside **one output**. Use `condition` to show or hide scenes and elements. A bulk request creates **multiple outputs**. For a reusable stored video template, give each scene a positive explicit duration. Validate both an empty/short dataset and the largest intended dataset so resolved scene counts and duration stay within account limits. ## Combine narration and background music Use the audio item fragments in [Audio elements](https://docs.zvid.io/docs/structure/audio-elements/), adding them to the project's or scene's `audios` array. Keep narration and music as separate audio items. Set volume, source trims and timeline bounds explicitly; `matchDuration` follows the containing timeline and loops short source segments to fill it. Narration should supply a bounded duration when it determines scene length. Background music should not accidentally determine the length of an Auto-mode project. Check [timing](https://docs.zvid.io/docs/concepts/timing/) and listen to the rendered output; passing structural validation does not prove an audible mix is balanced. ## Render one result per customer or product Follow [Template basics](https://docs.zvid.io/docs/templates/template-basics/), then [Bulk rendering](https://docs.zvid.io/docs/automation/bulk-rendering/). Save a template, preview representative variable sets for free, validate costs and limits, then submit the bulk request. Store the batch ID and each item result. Handle partial acceptance and failures per item; do not resubmit successful items as part of an indiscriminate retry. The REST hard cap is 500 items with possible lower account limits; MCP and installed automation integrations can impose lower limits. Use signed [webhooks](https://docs.zvid.io/docs/automation/webhooks/) or [bounded polling](https://docs.zvid.io/docs/operations/render-lifecycle/) to deliver only completed media. Keep template design changes separate from variable-data changes so failures can be traced to the correct input. --- Canonical URL: https://docs.zvid.io/docs/rendering-images/ Markdown URL: https://docs.zvid.io/markdown/docs/rendering-images.md # Rendering Images Image projects use Zvid's composition model to render a **PNG, JPG, or WebP**. Set `type: "image"`, use static `IMAGE`, `TEXT`/HTML, or `SVG` visuals, and omit video-only timing, audio, and scene fields. Positioning, text styling, filters, and safe `customCode` remain available. ## First image Create an [API key](https://docs.zvid.io/docs/authentication/) and use this complete request. It costs **1 credit**. To validate it for free first, send the same body to `POST /api/render/validate/api-key` instead; see [Validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/). ```bash curl -X POST https://api.zvid.io/api/render/image/api-key \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payload": { "type": "image", "name": "hello-image", "width": 1200, "height": 675, "outputFormat": "png", "backgroundColor": "#ffffff", "visuals": [{ "type": "TEXT", "text": "Hello, Zvid!", "position": "center-center", "style": { "fontFamily": "Poppins", "fontSize": 72, "color": "#111111" } }] } }' ``` You can submit an image payload to the regular `/api/render/api-key` endpoint too. The `/api/render/image/*` endpoints set `type: "image"` on an inline payload; when using a stored template, that template must already be an image project. `overrides` cannot change a template's type. ## Image-Specific Fields | Field | Type | Default | Notes | | -------------- | ---------------------------------- | --------------- | ---------------------------------------------------------------------------- | | `type` | `"image"` | `"video"` | Switches the render to image output. | | `outputFormat` | `png` \| `jpg` \| `jpeg` \| `webp` | `png` | Video formats are rejected for image renders. | | `transparent` | `boolean` | `false` | Transparent background. PNG/WebP only — rejected with `jpg`. | | `quality` | `1`–`100` | encoder default | JPG/WebP compression quality — rejected with `png`. | | `snapshotTime` | `number` (seconds, 0–3600) | start | Which moment of animated content (e.g. a `customCode` animation) to capture. | ## Converting a video composition 1. Set `type: "image"` and choose an image `outputFormat`. 2. Remove root `duration`, `durationMode`, `frameRate`, `audios`, `scenes`, `subtitle`, and `thumbnail`. Move the desired static scene visuals into the root `visuals` array if necessary. 3. Replace `VIDEO` and `GIF` elements with static assets; these types are rejected in image projects. 4. Remove element `enterBegin`, `enterEnd`, `exitBegin`, `exitEnd`, `videoBegin`, `videoEnd`, `videoDuration`, `transition`, `transitionId`, and `transitionDuration`. Image elements are always visible. 5. Run [hosted validation](https://docs.zvid.io/docs/validate-and-estimate/) before rendering. `snapshotTime` chooses a moment in time-based HTML/custom-code content. It does not make video/GIF elements valid or provide video-frame extraction. Use PNG or WebP for transparency; JPEG rejects `transparent: true`. ## Examples ### A social graphic ```json { "name": "docs-img-render-basic", "type": "image", "width": 1200, "height": 675, "outputFormat": "png", "backgroundColor": "#140b2e", "visuals": [ { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2024/10/02/18/24/leaf-9091894_1280.jpg", "width": 1200, "height": 675, "position": "center-center", "resize": "cover", "filter": { "brightness": -20 } }, { "type": "TEXT", "html": "
“Ship visuals from an API.”
", "position": "center-center", "customCode": { "css": ".q { color: #ffffff; font-family: Montserrat; font-size: 64px; font-weight: 800; text-shadow: 0 4px 24px rgba(0,0,0,0.6); }" } } ] } ``` 1200×675 PNG rendered by Zvid [View image](https://cdn.zvid.io/library/docs/img-render-basic.png) ### Transparent sticker (PNG with alpha) ```json { "name": "docs-img-render-transparent", "type": "image", "width": 800, "height": 600, "outputFormat": "png", "transparent": true, "visuals": [ { "type": "TEXT", "html": "
NEW DROP
", "position": "center-center", "customCode": { "css": ".sticker { color: #ffffff; font-family: Poppins; font-size: 72px; font-weight: 800; padding: 28px 56px; background: linear-gradient(135deg, #7c3aed, #d946ef); border-radius: 999px; transform: rotate(-6deg); box-shadow: 0 12px 40px rgba(124,58,237,0.5); }" } } ] } ``` transparent: true — drop it on any background [View image](https://cdn.zvid.io/library/docs/img-render-transparent.png) > **This site's social card is a Zvid render** The Open Graph image for docs.zvid.io is generated by this exact feature — a 1200×630 `type: "image"` render. ## Credits Image renders cost **1 credit per image**, including bulk renders — resolution-independent. A single image render costs 1 credit; a [bulk render](https://docs.zvid.io/docs/automation/bulk-rendering/) of 25 images costs 25 credits. ## Response The submit/poll flow is identical to video — see the [Quick Start](https://docs.zvid.io/docs/quick-start/). A completed job's output points at the image file; there is no separate thumbnail. Read `result.url` when `result` is an object, or use `result` directly when stored history returns a URL string. See [Render lifecycle](https://docs.zvid.io/docs/operations/render-lifecycle/) for both response shapes. ## In the Editor The editor has a first-class image mode: **New → Image** hides the time-domain tools (timeline, audio, scenes, subtitles) and the render dialog offers format, quality, and transparency options. See [Rendering & export](https://docs.zvid.io/docs/editor/export/). ## Related - [Quick Start](https://docs.zvid.io/docs/quick-start/) - [Bulk rendering](https://docs.zvid.io/docs/automation/bulk-rendering/) - [Templates](https://docs.zvid.io/docs/templates/template-basics/) — data-driven image batches --- Canonical URL: https://docs.zvid.io/docs/structure/ Markdown URL: https://docs.zvid.io/markdown/docs/structure.md # JSON Structure Overview A Zvid project is a plain JSON object submitted as `payload` to `POST /api/render/api-key`. The outer **request envelope** contains `payload` or a stored `template` ID, plus optional request variables and output overrides. The **project** is the object inside `payload`; an **element** belongs in its `visuals` array (or a scene's `visuals`). Audio belongs in `audios`. Do not nest a second `payload` inside a project exported by the editor. ```json { "payload": { "name": "my-video", "duration": 30, "visuals": [], "audios": [] } } ``` The Zvid API validates the payload, checks account limits, queues the render, resolves remote assets, and produces the final video. ## Project Object The interface below describes resolved video projects. Image projects share layout fields but have a different set of allowed fields; see [Rendering images](https://docs.zvid.io/docs/rendering-images/). Authoring-time `variables`, `condition`, and scene `iterate` are resolved before this shape is validated; see [Templates](https://docs.zvid.io/docs/templates/template-basics/). For video projects, `durationMode: "auto"` derives length from the scene sequence (minus transition overlaps), timed global elements, audio and captions. In this mode `duration` is an optional minimum. Without timed content or a minimum, the fallback is 10 seconds. Omit `durationMode` or use `"fixed"` to retain legacy timing. A scene project always includes its full scene sequence, including when a shorter root duration is supplied. For API submission in Auto mode, videos need `exitEnd` or `videoEnd`, and audio needs `exit`, `audioEnd`, or `matchDuration: true`. This lets the API validate the complete length against your plan before reserving credits. The editor resolves media lengths before submitting a render. The standalone renderer can also probe source lengths. Both timing fields are video-only. ```typescript interface Project { name?: string; width?: number; height?: number; resolution?: ResolutionPreset; duration?: number; durationMode?: "auto" | "fixed"; frameRate?: number; backgroundColor?: string; outputFormat?: "mp4" | "mov" | "avi" | "webm"; visuals?: Item[]; audios?: AudioItem[]; scenes?: Scene[]; thumbnail?: string; subtitle?: Subtitle; } ``` | Property | Type | Required | Default | Notes | | ----------------- | ------------------------------------------------------------------ | -------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `name` | `string` | No | `"unnamed"` | Output filename without extension. Letters, numbers, spaces, `_`, and `-` are accepted by API validation. | | `width` | `number` | No | `1280` | Used for custom resolution. Limited by the user's plan. | | `height` | `number` | No | `720` | Used for custom resolution. Limited by the user's plan. | | `resolution` | [`ResolutionPreset`](https://docs.zvid.io/docs/structure/properties/resolution-presets/) | No | custom dimensions | Preset dimensions override `width` and `height` when not `custom`. | | `duration` | `number` | No | `10` | Seconds. Minimum `0.1`; maximum is plan-dependent. | | `durationMode` | `"auto" \| "fixed"` | No | fixed behavior when omitted | Video only. Auto derives output length; `duration` becomes a minimum. See [Timing](https://docs.zvid.io/docs/concepts/timing/). | | `frameRate` | `number` | No | `30` | Integer from `1` to `60`. | | `backgroundColor` | `string` | No | `#ffffff` | Hex color, such as `#000000`. | | `outputFormat` | `string` | No | `mp4` | API accepts only `mp4`, `mov`, `avi`, and `webm`. | | `visuals` | `Item[]` | No | `[]` | Text/HTML, image, video, GIF, and (deprecated) SVG elements. When `scenes` is set, these act as a global overlay layer. | | `audios` | `AudioItem[]` | No | `[]` | External audio tracks. | | `scenes` | [`Scene[]`](https://docs.zvid.io/docs/structure/scenes/) | No | none | Sequential, self-contained segments. When present, drives the timeline; `visuals`/`audios` overlay all scenes. | | `thumbnail` | `string` | No | generated when absent | Optional remote image URL. | | `subtitle` | `Subtitle` | No | none | Caption and subtitle configuration. | `type` selects `"video"` (default) or `"image"`. Image-only fields are `snapshotTime`, `quality`, and `transparent`; image formats and forbidden video fields are documented in [Rendering images](https://docs.zvid.io/docs/rendering-images/). Project and scene `variables` hold authoring defaults, not rendered content. ## Resolution Presets See the [`ResolutionPreset`](https://docs.zvid.io/docs/structure/properties/resolution-presets/) source reference for supported preset names, dimensions, and usage notes. ## Property Reference All visual elements share timeline, transform, and layering fields — see [Common Element Properties](https://docs.zvid.io/docs/structure/common-properties/) for the canonical tables and the media-support matrix. The pages below are the source references for reusable typed properties: - [`PositionPreset`](https://docs.zvid.io/docs/structure/properties/position/) - [`Anchor`](https://docs.zvid.io/docs/structure/properties/anchor/) - [`ResizeMode`](https://docs.zvid.io/docs/structure/properties/resize/) - [`zoom`](https://docs.zvid.io/docs/structure/properties/zoom/) - [`FilterOptions`](https://docs.zvid.io/docs/structure/properties/filter-options/) - [`CropParams`](https://docs.zvid.io/docs/structure/properties/crop-params/) - [`ChromaKey`](https://docs.zvid.io/docs/structure/properties/chroma-key/) - [`BorderRadius`](https://docs.zvid.io/docs/structure/properties/border-radius/) - [`XFadeEffect`](https://docs.zvid.io/docs/structure/properties/xfade-effects/) - [`Caption`](https://docs.zvid.io/docs/structure/properties/caption/) - [`Word`](https://docs.zvid.io/docs/structure/properties/word/) - [`SubtitleStyles`](https://docs.zvid.io/docs/structure/properties/subtitle-styles/) ## Element Types - [Text & HTML Elements](https://docs.zvid.io/docs/structure/text-elements/): plain text or HTML with native CSS and JavaScript. - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/): remote image sources, filters, crop, radius, chroma key, resize, and zoom. - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/): remote video clips, trim timing, audio, playback speed, transitions, resize, and zoom. - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/): animated GIFs with timing, resize, zoom, crop, filters, and chroma key. - [SVG Elements](https://docs.zvid.io/docs/structure/svg-elements/): **deprecated** — use [HTML elements](https://docs.zvid.io/docs/structure/text-elements/) instead. - [Audio Elements](https://docs.zvid.io/docs/structure/audio-elements/): background music, narration, and sound effects. - [Subtitle](https://docs.zvid.io/docs/structure/subtitle/): word-timed captions and subtitle styling. - [Animation Effects](https://docs.zvid.io/docs/structure/animations/): enter and exit animations for visual elements. - [Video Transitions](https://docs.zvid.io/docs/structure/transitions/): video-to-video xfade transitions. ## Scenes For multi-part videos, use [Scenes](https://docs.zvid.io/docs/structure/scenes/). The `scenes` array splits a project into sequential, self-contained segments, each with its own local timeline and optional cross-scene transition. Project-level `visuals` and `audios` then render as a global overlay spanning every scene. ## Defaults ### Project Defaults | Property | Default | | ----------------- | --------- | | `width` | `1280` | | `height` | `720` | | `duration` | `10` | | `frameRate` | `30` | | `backgroundColor` | `#ffffff` | | `outputFormat` | `mp4` | | `name` | `unnamed` | | `visuals` | `[]` | | `audios` | `[]` | ### Visual Defaults Shared visual defaults (position, timing, opacity, track, animations) are listed in [Common Element Properties](https://docs.zvid.io/docs/structure/common-properties/). ### Video Defaults | Property | Default | | ---------------------------- | -------------------------------------------------- | | `videoBegin` | `0` | | `videoEnd` | project duration or source duration when available | | `videoDuration` | project duration or source duration when available | | `volume` | `1` | | `speed` | `1` | | `transition`, `transitionId` | `null` | ### Audio Defaults | Property | Default | | --------------- | -------------------------------------------------- | | `enter` | `0` | | `exit` | project duration | | `audioBegin` | `0` | | `audioEnd` | project duration or source duration when available | | `audioDuration` | project duration or source duration when available | | `volume` | `1` | | `speed` | `1` | ## Supported Formats - Input media: remote HTTP/HTTPS assets accepted by API validation and checked during rendering. - Output video: `mp4`, `mov`, `avi`, or `webm`. - HTML: HTML markup with native CSS and JavaScript via `customCode`, described in [Text & HTML Elements](https://docs.zvid.io/docs/structure/text-elements/). - SVG: the legacy `SVG` element accepts safe inline markup. For new HTML layouts, use the restricted geometry-only SVG subset described in [Text & HTML Elements](https://docs.zvid.io/docs/structure/text-elements/#inline-svg-in-html). ## Resource Limits Limits are plan-dependent and enforced before or during rendering. They include output resolution, project duration, input media resolution, media size, and element counts by type. Validation errors include the active plan limits when the payload exceeds them. ## Quick Examples These are **element fragments**, not complete render requests. Put visual fragments in `payload.visuals` and audio fragments in `payload.audios`. Use [Quick Start](https://docs.zvid.io/docs/quick-start/) for a complete request, and run [free validation](https://docs.zvid.io/docs/validate-and-estimate/) before rendering. ### Basic Text Element ```json { "type": "TEXT", "text": "Hello World", "x": 640, "y": 360, "anchor": "center-center", "style": { "fontSize": 48, "color": "#000000", "textAlign": "center" } } ``` ### Basic Image Element ```json { "type": "IMAGE", "src": "https://images.pexels.com/photos/32972375/pexels-photo-32972375.jpeg", "x": 100, "y": 100, "width": 607, "height": 910 } ``` ### Basic Video Element ```json { "type": "VIDEO", "src": "https://videos.pexels.com/video-files/1409899/1409899-sd_640_360_25fps.mp4", "videoEnd": 10, "volume": 0 } ``` ### Basic Audio Element ```json { "src": "https://cdn.pixabay.com/audio/2025/04/21/audio_ed6f0ed574.mp3", "volume": 0.5 } ``` ## Next Steps - [Quick Start](https://docs.zvid.io/docs/quick-start/) - [Common Element Properties](https://docs.zvid.io/docs/structure/common-properties/) - [Examples](https://docs.zvid.io/docs/examples/inspirational-video/) - [FAQ](https://docs.zvid.io/docs/faq/) - [Timing](https://docs.zvid.io/docs/concepts/timing/) — projects, scenes, source trims, and automatic length - [Layout](https://docs.zvid.io/docs/concepts/layout/) — pixels, boxes, positioning, and anchors - [Documentation resources](https://docs.zvid.io/docs/documentation-resources/) — schemas and machine-readable references --- Canonical URL: https://docs.zvid.io/docs/structure/animations/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/animations.md # Animation Effects Animations are enter and exit effects for visual elements. They use FFmpeg xfade effects against transparent backgrounds. ## Properties Set `enterAnimation` and/or `exitAnimation` to a supported xfade effect name. The animation plays inside the element's enter/exit timing windows (`enterBegin → enterEnd` and `exitBegin → exitEnd`) — see [Common Element Properties](https://docs.zvid.io/docs/structure/common-properties/#timing) for the timing model. An animation only plays when its window's end time is greater than its start time. ## Supported Effects The API accepts the [`XFadeEffect`](https://docs.zvid.io/docs/structure/properties/xfade-effects/) source list for `enterAnimation`, `exitAnimation`, and video `transition`. ## Examples These are **visual element fragments** for a video project's `payload.visuals`. Enter/exit times use the containing project's or scene's timeline, in seconds. For complete duration rules, see [Timing](https://docs.zvid.io/docs/concepts/timing/). ### Fade In And Out ```json { "type": "TEXT", "text": "Hello World", "position": "center-center", "enterBegin": 0, "enterEnd": 1, "exitBegin": 9, "exitEnd": 10, "enterAnimation": "fade", "exitAnimation": "fade" } ``` ```json { "name": "docs-anim-fade", "width": 960, "height": 540, "duration": 5, "backgroundColor": "#140b2e", "visuals": [ { "type": "TEXT", "text": "Hello World", "position": "center-center", "enterBegin": 0, "enterEnd": 1, "exitBegin": 4, "exitEnd": 5, "enterAnimation": "fade", "exitAnimation": "fade", "style": { "fontSize": 64, "color": "#ffffff", "fontFamily": "Poppins" } } ] } ``` Recorded fixture: fade in and fade out [Watch rendered example](https://cdn.zvid.io/library/docs/anim-fade.mp4) ### Quick HTML Element Animation ```json { "type": "TEXT", "html": "
", "x": 500, "y": 200, "enterBegin": 5, "enterEnd": 5.5, "exitBegin": 7, "exitEnd": 7.3, "enterAnimation": "dissolve", "exitAnimation": "dissolve", "customCode": { "css": ".card { width: 200px; height: 150px; background: #ff6b35; border-radius: 12px; }" } } ``` The `enterAnimation`/`exitAnimation` xfade effects above and the [`customCode`](https://docs.zvid.io/docs/structure/text-elements/#native-css--javascript-customcode) CSS animation loop can be combined freely. ## Troubleshooting - Animation not visible: ensure the duration is not zero. - Validation error: check the effect name against the supported list. - Choppy output: reduce simultaneous animated elements or use simpler effects. ## Related Pages - [Text Elements](https://docs.zvid.io/docs/structure/text-elements/) - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Transitions](https://docs.zvid.io/docs/structure/transitions/) - [XFadeEffect](https://docs.zvid.io/docs/structure/properties/xfade-effects/) --- Canonical URL: https://docs.zvid.io/docs/structure/audio-elements/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/audio-elements.md # Audio Elements Set `matchDuration: true` to follow the containing scene or project. The audio then ends with that timeline (overriding `exit`) and does not determine its automatic duration. Short source segments loop to fill the window. `audios` adds background music, narration, and sound effects to the project. Audio items belong in `audios`, not `visuals`, and do not require a `type` field. ## Interface ```typescript interface AudioItem { src: string; matchDuration?: boolean; enter?: number; exit?: number; volume?: number; speed?: number; audioBegin?: number; audioEnd?: number; audioDuration?: number; } ``` ## Required Fields | Property | Type | Notes | | -------- | -------- | ----------------------------------- | | `src` | `string` | Remote `http` or `https` audio URL. | ## Properties | Property | Default | Range/notes | | --------------- | -------------------------- | ------------------------------------------------------------------------------------------------------- | | `enter` | `0` | Start time on the project timeline. | | `exit` | `project.duration` | End time on the project timeline. | | `matchDuration` | `false` | Follow the containing project/scene end, overriding `exit`; excluded from automatic-length calculation. | | `audioBegin` | `0` | Start point in source audio. | | `audioEnd` | project or source duration | End point in source audio. | | `audioDuration` | project or source duration | Source duration hint. | | `volume` | `1` | `0` to `2`; `1` is the original level. | | `speed` | `1` | `0.1` to `10`. | ## Examples All timing fields are in **seconds**. `enter`/`exit` use the containing project or scene timeline; `audioBegin`/`audioEnd` use source-media time. The examples below are **audio item fragments** for `payload.audios` or `payload.scenes[n].audios`, not full render requests. See [Timing](https://docs.zvid.io/docs/concepts/timing/) for trimming, speed, and automatic duration. ### Background Music ```json { "src": "https://cdn.pixabay.com/audio/2025/03/19/audio_56ae1dae5f.mp3", "volume": 0.3, "enter": 0, "exit": 10 } ``` ### Sound Effect ```json { "src": "https://cdn.pixabay.com/audio/2025/01/13/audio_c2af364af2.mp3", "volume": 0.8, "enter": 2, "exit": 4 } ``` ### Source Segment ```json { "src": "https://cdn.pixabay.com/audio/2025/04/21/audio_ed6f0ed574.mp3", "audioBegin": 10, "audioEnd": 20, "volume": 0.5, "enter": 0, "exit": 10 } ``` ## Related Pages - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [Animation Effects](https://docs.zvid.io/docs/structure/animations/) - [Transitions](https://docs.zvid.io/docs/structure/transitions/) --- Canonical URL: https://docs.zvid.io/docs/structure/common-properties/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/common-properties.md # Common Element Properties Every visual element (`TEXT`, `IMAGE`, `VIDEO`, `GIF`, and the deprecated `SVG`) shares the same placement, timing, and layering fields. This page is the canonical reference for those shared properties — element pages document only what is specific to their type and link back here. Visual elements belong in `payload.visuals` or `payload.scenes[n].visuals`. Timing and animation fields below are for video projects; image projects use always-visible static elements and [reject timing fields](https://docs.zvid.io/docs/rendering-images/#converting-a-video-composition). ## Placement & Transform | Property | Type | Default | Notes | | ----------------- | ---------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `x`, `y` | `number` | `0` | Pixel position. Ignored when a `position` preset is used. | | `width`, `height` | `number` | auto (source / computed) | Element size in pixels. Limited by plan input resolution for media. | | `position` | `PositionPreset` | `custom` | 9-grid preset placement such as `center-center`. See [`PositionPreset`](https://docs.zvid.io/docs/structure/properties/position/). | | `anchor` | `Anchor` | derived from `position` when preset is used | Transform origin for placement and rotation. See [`Anchor`](https://docs.zvid.io/docs/structure/properties/anchor/). | | `opacity` | `number` | `1` | `0` (invisible) to `1` (opaque). | | `angle` | `number` | `0` | Rotation in degrees, `-360` to `360`, around the `anchor`. | | `flipV`, `flipH` | `boolean` | `false` | Vertical / horizontal mirroring. | With custom coordinates, omitted `anchor` uses `top-left`. All sizes and positions are pixels on the output canvas, not CSS percentages. A preset overrides explicit `x`/`y`; for an inset or margin, use custom coordinates. See [Layout](https://docs.zvid.io/docs/concepts/layout/) for worked positioning examples. ## Timing All times are seconds on the element's timeline — the project timeline, or the **scene-local** timeline when the element lives inside a [scene](https://docs.zvid.io/docs/structure/scenes/). Inside a scene, duration-based defaults refer to that scene's resolved length. For source trimming and automatic duration, see [Timing](https://docs.zvid.io/docs/concepts/timing/). | Property | Default | Notes | | ------------ | ------------------ | ---------------------------- | | `enterBegin` | `0` | Element starts appearing. | | `enterEnd` | `0` | Element is fully visible. | | `exitBegin` | `project.duration` | Element starts disappearing. | | `exitEnd` | `project.duration` | Element is fully gone. | The element is invisible before `enterBegin`, animates in until `enterEnd`, stays visible until `exitBegin`, and animates out until `exitEnd`. The enter/exit _windows_ only have an effect when paired with an [`enterAnimation` / `exitAnimation`](https://docs.zvid.io/docs/structure/animations/); with no animation the element simply cuts in at `enterBegin` and out at `exitEnd`. ## Layering | Property | Default | Notes | | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `track` | `0` | Z-order: higher tracks render above lower tracks. Inside scenes, scene elements always render below project-level (global overlay) visuals. | ## Animations | Property | Default | Notes | | ---------------- | ------- | -------------------------------------------------------------------------- | | `enterAnimation` | `null` | Xfade effect name for the enter window. See [Animations](https://docs.zvid.io/docs/structure/animations/). | | `exitAnimation` | `null` | Xfade effect name for the exit window. | Supported effect names are listed in [`XFadeEffect`](https://docs.zvid.io/docs/structure/properties/xfade-effects/). ## Media-Only Properties Media elements add resizing and effect options. Support by type: | Property | IMAGE | VIDEO | GIF | TEXT | SVG (deprecated) | | ------------------------------------------------------ | :---: | :---: | :-: | :--: | :--------------: | | [`resize`](https://docs.zvid.io/docs/structure/properties/resize/) (`contain`/`cover`) | ✅ | ✅ | ✅ | — | — | | [`zoom`](https://docs.zvid.io/docs/structure/properties/zoom/) (Ken Burns) | ✅ | ✅ | ✅ | — | — | | [`filter`](https://docs.zvid.io/docs/structure/properties/filter-options/) | ✅ | ✅ | ✅ | — | ✅ | | [`cropParams`](https://docs.zvid.io/docs/structure/properties/crop-params/) | ✅ | ✅ | ✅ | — | — | | [`chromaKey`](https://docs.zvid.io/docs/structure/properties/chroma-key/) | ✅ | ✅ | ✅ | — | ✅ | | [`radius`](https://docs.zvid.io/docs/structure/properties/border-radius/) | ✅ | ✅ | ✅ | — | — | `TEXT` elements style themselves with `style` and [`customCode`](https://docs.zvid.io/docs/structure/text-elements/#native-css--javascript-customcode) instead — rounded corners, shadows, and filters are all regular CSS there. ## Audio Timing Is Different [Audio items](https://docs.zvid.io/docs/structure/audio-elements/) are not visual elements: they use `enter` / `exit` for timeline placement and `audioBegin` / `audioEnd` for source trimming, and have no transform, layering, or animation fields. ## Related Pages - [JSON Structure Overview](https://docs.zvid.io/docs/structure/) - [Animations](https://docs.zvid.io/docs/structure/animations/) - [Scenes](https://docs.zvid.io/docs/structure/scenes/) - [Property Reference](https://docs.zvid.io/docs/structure/properties/position/) --- Canonical URL: https://docs.zvid.io/docs/structure/gif-elements/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/gif-elements.md # GIF Elements `GIF` elements add animated GIF assets to a project. The hosted API accepts GIFs as media-like visual elements, including timing, transform, resize, zoom, crop, filters, and chroma key options. ## Interface ```typescript interface GIFItem { type: "GIF"; src: string; x?: number; y?: number; width?: number; height?: number; anchor?: Anchor; position?: PositionPreset; resize?: "contain" | "cover"; enterBegin?: number; enterEnd?: number; exitBegin?: number; exitEnd?: number; track?: number; opacity?: number; angle?: number; flipV?: boolean; flipH?: boolean; enterAnimation?: XFadeEffect | null; exitAnimation?: XFadeEffect | null; filter?: FilterOptions; cropParams?: CropParams; chromaKey?: ChromaKey; zoom?: boolean | { depth?: number }; radius?: BorderRadius; } ``` ## Required Fields | Property | Type | Notes | | -------- | -------- | ----------------------------------- | | `type` | `"GIF"` | Case-insensitive in API validation. | | `src` | `string` | Remote `http` or `https` URL. | ## Properties `GIF` elements behave like [images](https://docs.zvid.io/docs/structure/image-elements/): they share the [common element properties](https://docs.zvid.io/docs/structure/common-properties/) and support every media option in the [support matrix](https://docs.zvid.io/docs/structure/common-properties/#media-only-properties) — [`resize`](https://docs.zvid.io/docs/structure/properties/resize/), [`zoom`](https://docs.zvid.io/docs/structure/properties/zoom/), [`filter`](https://docs.zvid.io/docs/structure/properties/filter-options/), [`cropParams`](https://docs.zvid.io/docs/structure/properties/crop-params/), [`chromaKey`](https://docs.zvid.io/docs/structure/properties/chroma-key/), and [`radius`](https://docs.zvid.io/docs/structure/properties/border-radius/). The GIF's own animation loops for the element's visible duration. ## Examples These are **visual element fragments** for a video project's `payload.visuals`. `GIF` elements are rejected in image projects; use a static image instead. The recorded preview includes a background and its own GIF placement; use its full payload for that exact composition. ### Simple GIF ```json { "type": "GIF", "src": "https://media.giphy.com/media/3oEjI6SIIHBdRxXI40/giphy.gif", "position": "center-center", "track": 10 } ``` ```json { "name": "docs-gif-overlay", "width": 960, "height": 540, "duration": 5, "backgroundColor": "#000000", "visuals": [ { "type": "VIDEO", "src": "https://cdn.pixabay.com/video/2025/06/09/284566_large.mp4", "resize": "cover", "width": 960, "height": 540, "volume": 0 }, { "type": "GIF", "src": "https://media.giphy.com/media/3oEjI6SIIHBdRxXI40/giphy.gif", "width": 260, "height": 260, "resize": "contain", "position": "bottom-left", "track": 10 } ] } ``` Recorded fixture: a GIF over a video background [Watch rendered example](https://cdn.zvid.io/library/docs/gif-overlay.mp4) ### Resized GIF ```json { "type": "GIF", "src": "https://media.giphy.com/media/3oEjI6SIIHBdRxXI40/giphy.gif", "width": 300, "height": 300, "resize": "contain", "position": "bottom-right", "opacity": 0.8 } ``` ### Cropped GIF ```json { "type": "GIF", "src": "https://media.giphy.com/media/3oEjI6SIIHBdRxXI40/giphy.gif", "width": 300, "height": 200, "cropParams": { "x": 50, "y": 25, "width": 400, "height": 300 } } ``` ## Related Pages - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [Animation Effects](https://docs.zvid.io/docs/structure/animations/) - [Property Reference](https://docs.zvid.io/docs/structure/properties/position/) --- Canonical URL: https://docs.zvid.io/docs/structure/image-elements/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/image-elements.md # Image Elements `IMAGE` elements add remote images to the composition. The API validates the URL and visual options, then Zvid downloads and checks the asset before rendering. Use a public direct media URL; see [Media assets](https://docs.zvid.io/docs/concepts/media-assets/) for uploads, supported sources, and download failures. ## Interface ```typescript interface ImageItem { type: "IMAGE"; src: string; x?: number; y?: number; width?: number; height?: number; anchor?: Anchor; position?: PositionPreset; resize?: "contain" | "cover"; enterBegin?: number; enterEnd?: number; exitBegin?: number; exitEnd?: number; track?: number; opacity?: number; angle?: number; flipV?: boolean; flipH?: boolean; enterAnimation?: XFadeEffect | null; exitAnimation?: XFadeEffect | null; filter?: FilterOptions; cropParams?: CropParams; chromaKey?: ChromaKey; radius?: BorderRadius; zoom?: boolean | { depth?: number }; } ``` ## Required Fields | Property | Type | Notes | | -------- | --------- | ----------------------------------- | | `type` | `"IMAGE"` | Case-insensitive in API validation. | | `src` | `string` | Remote `http` or `https` URL. | ## Properties Placement, timing, layering, and animation fields are shared by all visual elements — see [Common Element Properties](https://docs.zvid.io/docs/structure/common-properties/). Images support every media option in the [support matrix](https://docs.zvid.io/docs/structure/common-properties/#media-only-properties): [`resize`](https://docs.zvid.io/docs/structure/properties/resize/), [`zoom`](https://docs.zvid.io/docs/structure/properties/zoom/), [`filter`](https://docs.zvid.io/docs/structure/properties/filter-options/), [`cropParams`](https://docs.zvid.io/docs/structure/properties/crop-params/), [`chromaKey`](https://docs.zvid.io/docs/structure/properties/chroma-key/), and [`radius`](https://docs.zvid.io/docs/structure/properties/border-radius/). ## Examples The JSON blocks are **visual element fragments** for `payload.visuals` or a scene's `visuals`. The recorded previews demonstrate the named property with their own full project fixtures, available alongside each preview. Their canvas sizes and surrounding composition can differ from the short fragments. ### Simple Image ```json { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2022/08/16/05/50/straw-bales-7389396_1280.jpg", "x": 100, "y": 100, "width": 400, "height": 300 } ``` ### Centered Cover Image ```json { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2024/10/02/18/24/leaf-9091894_1280.jpg", "width": 1280, "height": 720, "position": "center-center", "resize": "cover" } ``` ```json { "name": "docs-img-cover", "width": 960, "height": 540, "duration": 4, "backgroundColor": "#000000", "visuals": [ { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2024/10/02/18/24/leaf-9091894_1280.jpg", "width": 960, "height": 540, "position": "center-center", "resize": "cover" } ] } ``` Recorded fixture: resize cover on a 960×540 canvas [Watch rendered example](https://cdn.zvid.io/library/docs/img-cover.mp4) ### Cropped Image ```json { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2023/06/04/20/21/cat-8040862_1280.jpg", "x": 300, "y": 200, "width": 400, "height": 300, "cropParams": { "x": 100, "y": 50, "width": 800, "height": 600 } } ``` ```json { "name": "docs-img-crop", "width": 960, "height": 540, "duration": 4, "backgroundColor": "#1e1b31", "visuals": [ { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2023/06/04/20/21/cat-8040862_1280.jpg", "x": 280, "y": 120, "width": 400, "height": 300, "cropParams": { "x": 100, "y": 50, "width": 800, "height": 600 } } ] } ``` Recorded fixture: a cropped source image [Watch rendered example](https://cdn.zvid.io/library/docs/img-crop.mp4) ### Rounded Corners And Filters ```json { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2022/11/05/22/11/channel-7572879_1280.jpg", "width": 300, "height": 200, "position": "bottom-right", "radius": { "tl": 10, "tr": 10, "bl": 10, "br": 10 }, "filter": { "brightness": -10, "hue-rotate": "30deg", "blur": 20 } } ``` ```json { "name": "docs-img-radius", "width": 960, "height": 540, "duration": 4, "backgroundColor": "#1e1b31", "visuals": [ { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2022/11/05/22/11/channel-7572879_1280.jpg", "width": 480, "height": 320, "position": "center-center", "radius": { "tl": 32, "tr": 32, "bl": 32, "br": 32 } } ] } ``` Recorded fixture: 32-pixel rounded corners, without the fragment's filters [Watch rendered example](https://cdn.zvid.io/library/docs/img-radius.mp4) ## Related Pages - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/) - [Animation Effects](https://docs.zvid.io/docs/structure/animations/) - [FilterOptions](https://docs.zvid.io/docs/structure/properties/filter-options/) - [CropParams](https://docs.zvid.io/docs/structure/properties/crop-params/) - [ChromaKey](https://docs.zvid.io/docs/structure/properties/chroma-key/) - [BorderRadius](https://docs.zvid.io/docs/structure/properties/border-radius/) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/anchor/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/anchor.md # Anchor `anchor` controls the transform origin used for placement, rotation, and scaling. ```typescript type Anchor = | "top-left" | "top-center" | "top-right" | "center-left" | "center-center" | "center-right" | "bottom-left" | "bottom-center" | "bottom-right"; ``` Use `anchor: "center-center"` when `x` and `y` should represent the center point of an element. With custom coordinates, an omitted anchor is `top-left`. With a non-custom `position` preset, an omitted anchor takes that preset's value. `position` still determines placement when supplied; use custom positioning for explicit coordinates and margins. See [Layout](https://docs.zvid.io/docs/concepts/layout/). ## Example Visual element fragment for `payload.visuals`. Replace the illustrative asset URL with your public image URL before rendering. ```json { "type": "IMAGE", "src": "https://cdn.example.com/photo.jpg", "x": 640, "y": 360, "width": 400, "height": 300, "anchor": "center-center" } ``` ## Used By - [Text & HTML Elements](https://docs.zvid.io/docs/structure/text-elements/) - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/) - [SVG Elements (deprecated)](https://docs.zvid.io/docs/structure/svg-elements/) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/border-radius/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/border-radius.md # BorderRadius `radius` rounds the element's corners (CSS `border-radius` semantics). ```typescript interface BorderRadius { tl?: number; tr?: number; bl?: number; br?: number; } ``` | Property | Corner | | -------- | ------------ | | `tl` | Top-left | | `tr` | Top-right | | `bl` | Bottom-left | | `br` | Bottom-right | Values are nonnegative pixels, limited by your plan's input-resolution bound. Omitted corners default to square (`0`). This object belongs on an `IMAGE`, `VIDEO`, or `GIF` visual; for HTML use CSS `border-radius`, and for captions use [`subtitle.background.radius`](https://docs.zvid.io/docs/structure/subtitle/). ## Example Property fragment for a visual element. The separate recorded preview uses 32-pixel corners; its payload is available below. ```json { "radius": { "tl": 10, "tr": 10, "bl": 10, "br": 10 } } ``` ## Used By - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/) ```json { "name": "docs-img-radius", "width": 960, "height": 540, "duration": 4, "backgroundColor": "#1e1b31", "visuals": [ { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2022/11/05/22/11/channel-7572879_1280.jpg", "width": 480, "height": 320, "position": "center-center", "radius": { "tl": 32, "tr": 32, "bl": 32, "br": 32 } } ] } ``` Recorded fixture: radius 32 pixels on every corner [Watch rendered example](https://cdn.zvid.io/library/docs/img-radius.mp4) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/caption/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/caption.md # Caption `Caption` defines one subtitle segment on the video timeline. Location: `payload.subtitle.captions[n]`. Timings are absolute project seconds, even when visuals use scene-local timelines. Keep `start < end`; avoid overlapping caption intervals unless simultaneous captions are intended. The examples below are caption fragments, not complete project payloads. ```typescript interface Caption { start: number; end: number; text?: string; words?: Word[]; } ``` | Property | Required | Notes | | -------- | -------------------- | --------------------------------------------------------------------------- | | `start` | Yes | Caption start time in seconds. | | `end` | Yes | Caption end time in seconds. | | `text` | Unless `words` given | Full caption text. Word timings are auto-generated when `words` is omitted. | | `words` | Unless `text` given | Exact word-level timings (e.g. from a transcription model). | Each caption needs `text` and/or `words`. When only `text` is provided, word timings are distributed across the caption window proportionally to word length — every word-timed animation (karaoke, highlight, fill, …) works without hand-authored timings. ## Example — text only (auto-timed) ```json { "start": 0.5, "end": 2, "text": "Welcome to our video" } ``` ## Example — exact word timings ```json { "start": 0.5, "end": 2, "text": "Welcome to our video", "words": [ { "start": 0.5, "end": 0.9, "text": "Welcome" }, { "start": 0.9, "end": 1.1, "text": "to" }, { "start": 1.1, "end": 1.4, "text": "our" }, { "start": 1.4, "end": 2, "text": "video" } ] } ``` ## Used By - [Subtitle](https://docs.zvid.io/docs/structure/subtitle/) - [Word](https://docs.zvid.io/docs/structure/properties/word/) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/chroma-key/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/chroma-key.md # ChromaKey `chromaKey` removes pixels that match a target color, which is useful for green-screen style effects. ```typescript interface ChromaKey { color: string; similarity?: number; blend?: number; } ``` | Property | Required | Range/format | Notes | | ------------ | -------- | ------------ | -------------------------------- | | `color` | Yes | hex color | Example: `"#00ff00"`. | | `similarity` | No | `0` to `100` | Higher values match more colors. | | `blend` | No | `0` to `100` | Softens the keyed edge. | ## Example Property fragment for a media visual. `similarity` and `blend` are unitless amounts on a 0–100 scale, not 0–1 fractions; supply them explicitly for a repeatable look. The preview demonstrates this feature with its recorded fixture. ```json { "chromaKey": { "color": "#00ff00", "similarity": 18, "blend": 8 } } ``` ## Used By - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/) - [SVG Elements (deprecated)](https://docs.zvid.io/docs/structure/svg-elements/) For [HTML elements](https://docs.zvid.io/docs/structure/text-elements/), use CSS (`background`, `mix-blend-mode`) through [`customCode`](https://docs.zvid.io/docs/structure/text-elements/#native-css--javascript-customcode) instead of `chromaKey`. A green-screen clip keyed over a background video (the green source was itself rendered by Zvid) [Watch rendered example](https://cdn.zvid.io/library/docs/chroma-demo.mp4) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/crop-params/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/crop-params.md # CropParams `cropParams` selects a rectangular region from the source asset before the element is composed. Location: an `IMAGE`, `VIDEO`, or `GIF` visual's `cropParams` field. Coordinates are **source pixels**, measured from the source's top-left corner; they are not output-canvas coordinates. Cropping and the element's placement/size are separate operations. Keep the rectangle inside the source dimensions. ```typescript interface CropParams { x: number; y: number; width: number; height: number; } ``` | Property | Required | Notes | | -------- | -------- | ----------------------------- | | `x` | Yes | Left edge of the crop region. | | `y` | Yes | Top edge of the crop region. | | `width` | Yes | Crop region width. | | `height` | Yes | Crop region height. | `x` and `y` are nonnegative; `width` and `height` are at least 1 pixel, subject to your plan's input-resolution limits. Check actual source dimensions before choosing the crop. See [Media assets](https://docs.zvid.io/docs/concepts/media-assets/). ## Example This is a property fragment to merge into a visual element: ```json { "cropParams": { "x": 100, "y": 50, "width": 800, "height": 600 } } ``` ## Used By - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/filter-options/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/filter-options.md # FilterOptions `filter` adjusts visual appearance before the element is composed. ```typescript interface FilterOptions { brightness?: number; contrast?: number; saturate?: number; "hue-rotate"?: string; blur?: number | string; invert?: boolean | number; colorTint?: string; } ``` | Property | Range/format | Notes | | ------------ | ------------------------ | ------------------------------------------------------------------------- | | `brightness` | number, `-100` to `100` | Lower or raise brightness. | | `contrast` | number, `-100` to `100` | Lower or raise contrast. | | `saturate` | number, `-100` to `100` | Lower or raise saturation. | | `hue-rotate` | degree string | Prefer `"30deg"`; legacy numeric strings such as `"30"` remain accepted. | | `blur` | number, `0` to `100` | Unitless amount; numeric strings are accepted for backward compatibility. | | `invert` | boolean or number `0..1` | `true` fully inverts; a number applies partial inversion. | | `colorTint` | `#rgb` or `#rrggbb` | Multiplies the red, green, and blue channels. | `blur` is not a CSS length. Values such as `"4px"` are not supported. Brightness, contrast, and saturation use `0` for an unchanged image. Positive contrast increases gradually: `1` is a small adjustment, `25` maps to a contrast factor of `1.5`, `50` to `2`, and `100` to `3`. Negative contrast reduces that factor toward `0` at `-100`. Saturation uses the same factor range; brightness instead adds or subtracts lightness. Blur increases linearly relative to the image or video's shortest side, so the same setting scales with the element's dimensions. Invert blends RGB colors continuously from `0` (unchanged) to `1` (fully inverted), including when combined with brightness, contrast, saturation, or hue adjustments. Tint `#ffffff` leaves the RGB channels unchanged. ## Example This is a property fragment to merge into an `IMAGE`, `VIDEO`, `GIF`, or legacy `SVG` visual. It is not a complete project or a CSS filter string. ```json { "filter": { "brightness": -10, "hue-rotate": "30deg", "blur": 20, "invert": 0.25 } } ``` ## Used By - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/) - [SVG Elements (deprecated)](https://docs.zvid.io/docs/structure/svg-elements/) For [HTML elements](https://docs.zvid.io/docs/structure/text-elements/), use the CSS `filter` property through [`customCode`](https://docs.zvid.io/docs/structure/text-elements/#native-css--javascript-customcode) instead of the `filter` object. ```json { "name": "docs-img-filters", "width": 960, "height": 540, "duration": 4, "backgroundColor": "#0b0d12", "visuals": [ { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2022/08/16/05/50/straw-bales-7389396_1280.jpg", "x": 15, "y": 90, "width": 450, "height": 300 }, { "type": "IMAGE", "src": "https://cdn.pixabay.com/photo/2022/08/16/05/50/straw-bales-7389396_1280.jpg", "x": 495, "y": 90, "width": 450, "height": 300, "filter": { "brightness": 8, "contrast": 12, "saturate": 25 } }, { "type": "TEXT", "text": "original", "x": 240, "y": 440, "anchor": "center-center", "style": { "fontSize": 28, "color": "#ffffff", "fontFamily": "Poppins" } }, { "type": "TEXT", "text": "filter applied", "x": 720, "y": 440, "anchor": "center-center", "style": { "fontSize": 28, "color": "#a78bfa", "fontFamily": "Poppins" } } ] } ``` Recorded comparison fixture: original vs. brightness + contrast + saturation [Watch rendered example](https://cdn.zvid.io/library/docs/img-filters.mp4) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/position/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/position.md # PositionPreset `position` places an element on the canvas using a preset. When `position` is not `custom`, Zvid calculates `x` and `y` from the project dimensions and item dimensions. If no explicit `anchor` is set, Zvid uses the same value as the non-custom `position`. Default: `custom`. Non-custom presets override authored `x` and `y`, so adding `x: 40` to `position: "bottom-right"` does not create a 40-pixel margin. For an inset, use custom coordinates and an appropriate `anchor`; see [Layout](https://docs.zvid.io/docs/concepts/layout/). ```typescript type PositionPreset = | "top-left" | "top-center" | "top-right" | "center-left" | "center-center" | "center-right" | "bottom-left" | "bottom-center" | "bottom-right" | "custom"; ``` | Value | Placement | | --------------- | ------------------------------- | | `top-left` | Top-left corner | | `top-center` | Top edge, centered | | `top-right` | Top-right corner | | `center-left` | Left edge, vertically centered | | `center-center` | Center of the canvas | | `center-right` | Right edge, vertically centered | | `bottom-left` | Bottom-left corner | | `bottom-center` | Bottom edge, centered | | `bottom-right` | Bottom-right corner | | `custom` | Use explicit `x` and `y` | ## Example Visual element fragment for `payload.visuals`: ```json { "type": "TEXT", "text": "Centered", "position": "center-center" } ``` ## Used By - [Text & HTML Elements](https://docs.zvid.io/docs/structure/text-elements/) - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/) - [SVG Elements (deprecated)](https://docs.zvid.io/docs/structure/svg-elements/) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/resize/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/resize.md # Resize `resize` scales media while preserving the asset aspect ratio. What it scales against depends on whether the element declares its own box: - **With explicit `width`/`height`** the media is fitted **into that box**, exactly like CSS `object-fit`. The box, position, corner radius, and animations all stay as authored; only the pixels inside the box change. - **Without `width`/`height`** the media is sized against the **project canvas** — the usual way to make a background fill the whole frame. ```typescript type ResizeMode = "contain" | "cover"; ``` | Value | With a box | Without a box | | --------- | ------------------------------------------------- | ---------------------------------------- | | `contain` | Fits the full asset inside the box (letterboxed). | Fits the full asset inside the canvas. | | `cover` | Fills the box, center-cropping the overflow. | Fills the canvas and may crop the asset. | `resize` is supported for `IMAGE`, `VIDEO`, and `GIF` elements. > **HTML/`TEXT` elements (and the deprecated SVG element) use explicit `width` and** `height` instead of `resize`. Although `resize` may pass API validation for SVG, it is not applied during rendering. ## Full-frame background Visual element fragment for `payload.visuals`. Replace the illustrative asset URL with your own public image URL. See [Layout](https://docs.zvid.io/docs/concepts/layout/) for the difference between an element's box and the pixels fitted inside it. ```json { "type": "IMAGE", "src": "https://cdn.example.com/photo.jpg", "resize": "cover", "position": "center-center" } ``` ## Boxed card ```json { "type": "IMAGE", "src": "https://cdn.example.com/photo.jpg", "width": 880, "height": 1180, "x": 540, "y": 780, "anchor": "center-center", "resize": "cover", "radius": { "tl": 48, "tr": 48, "br": 48, "bl": 48 } } ``` The photo is center-cropped into the 880×1180 rounded card no matter what aspect ratio the source has. ## Used By - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/) resize: cover fills the box, cropping overflow [Watch rendered example](https://cdn.zvid.io/library/docs/img-cover.mp4) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/resolution-presets/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/resolution-presets.md # ResolutionPreset `resolution` selects a preset canvas size. When `resolution` is not `custom`, preset dimensions override `width` and `height`. ```typescript type ResolutionPreset = | "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"; ``` | Preset | Dimensions | Use case | | ------------------- | ------------------ | ----------------------- | | `sd` | 640 x 480 | Standard definition | | `hd` | 1280 x 720 | HD landscape | | `full-hd` | 1920 x 1080 | Full HD landscape | | `squared` | 1080 x 1080 | Square format | | `youtube-short` | 1080 x 1920 | YouTube Shorts | | `youtube-video` | 1920 x 1080 | YouTube videos | | `tiktok` | 1080 x 1920 | TikTok videos | | `instagram-reel` | 1080 x 1920 | Instagram Reels | | `instagram-post` | 1080 x 1080 | Instagram posts | | `instagram-story` | 1080 x 1920 | Instagram Stories | | `instagram-feed` | 1080 x 1080 | Instagram feed posts | | `twitter-landscape` | 1200 x 675 | Landscape social video | | `twitter-portrait` | 1080 x 1350 | Portrait social video | | `twitter-square` | 1080 x 1080 | Square social video | | `facebook-video` | 1080 x 1920 | Facebook vertical video | | `facebook-story` | 1080 x 1920 | Facebook Stories | | `facebook-post` | 1080 x 1080 | Facebook posts | | `snapchat` | 1080 x 1920 | Snapchat content | | `custom` | `width` x `height` | Custom dimensions | > **Compatibility** Use the canonical spelling `snapchat` in API requests. The renderer also recognizes the legacy misspelling `snapshat`; some editor versions export that alias. For portability, normalize the spelling or use `resolution: "custom"` with `width: 1080` and `height: 1920` before hosted validation. ## Used By - [JSON Structure Overview](https://docs.zvid.io/docs/structure/) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/subtitle-styles/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/subtitle-styles.md # SubtitleStyles (legacy) `SubtitleStyles` is the **legacy** style block for subtitles. It is still accepted by the renderer, but new projects should use the flat fields on [`Subtitle`](https://docs.zvid.io/docs/structure/subtitle/) (`animation`, `font`, `stroke`, `background`, `position`, `margin`, …). A subtitle object cannot combine `styles` with the flat style fields. ```typescript interface SubtitleStyles { color?: string; background?: string; backgroundPadding?: number; backgroundRadius?: number; isBold?: boolean; isItalic?: boolean; fontSize?: number; fontFamily?: string; textTransform?: "uppercase" | "lowercase" | "capitalize"; outline?: { width: number; color: string; }; position?: | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" | "center-center" | "center-left" | "center-right"; marginV?: number; marginH?: number; mode?: | "normal" | "none" | "one-word" | "karaoke" | "progressive" | "highlight" | "fill" | "pop" | "bounce" | "fade" | "typewriter" | "slide"; slideDirection?: "up" | "down" | "left" | "right"; activeWord?: { color?: string; background?: string; radius?: number; }; } ``` | Property | Default | Flat equivalent on `Subtitle` | Notes | | -------------------- | ------------------ | ------------------------------- | ------------------------------------------------------------------------------ | | `color` | `#FFFFFF` | `font.color` | Hex with optional alpha. | | `background` | none | `background.color` (+`opacity`) | Hex with optional alpha. | | `backgroundPadding` | `0` | `background.padding` | Box padding in px. | | `backgroundRadius` | `0` | `background.radius` | Corner radius in pixels, 0–200. | | `fontSize` | `50` | `font.size` | Pixels. | | `fontFamily` | `Poppins` | `font.family` | Google Fonts family name. | | `isBold`, `isItalic` | `false` | `font.bold`, `font.italic` | Typography flags. | | `textTransform` | none | `font.transform` | `uppercase`, `lowercase`, or `capitalize`. | | `outline` | none | `stroke` | Requires `width` and `color`. | | `position` | `bottom-center` | `position` | Full 9-grid; flat form adds `top`/`center`/`bottom`. | | `marginV`, `marginH` | 5% of project size | `margin.y`, `margin.x` | Integer pixels. | | `mode` | `normal` | `animation` | All documented modes; `none` aliases `normal`. See [Subtitle](https://docs.zvid.io/docs/structure/subtitle/). | | `slideDirection` | `up` | `direction` | `slide` mode only. | | `activeWord` | none | `activeWord` | Color/background and optional `radius` (0–200 pixels) of the spoken word. | ## Used By - [Subtitle](https://docs.zvid.io/docs/structure/subtitle/) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/word/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/word.md # Word `Word` defines the timing for one word inside a subtitle caption. Location: `payload.subtitle.captions[n].words[m]`. Both timestamps use **absolute project seconds**, not seconds relative to the caption. Keep `start < end`, words in chronological order, and each interval inside the parent caption's interval. A caption beginning at 5 seconds has word times such as 5.0–5.4, not 0.0–0.4. ```typescript interface Word { start: number; end: number; text: string; } ``` | Property | Required | Notes | | -------- | -------- | --------------------------- | | `start` | Yes | Word start time in seconds. | | `end` | Yes | Word end time in seconds. | | `text` | Yes | Word text. | Word timings drive the word-timed subtitle animations (`one-word`, `karaoke`, `highlight`, `progressive`, `fill`, `pop`, `bounce`, `fade`, `typewriter`, `slide`). Words are optional on a caption — when omitted, timings are auto-distributed from the caption text. ## Used By - [Caption](https://docs.zvid.io/docs/structure/properties/caption/) - [Subtitle](https://docs.zvid.io/docs/structure/subtitle/) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/xfade-effects/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/xfade-effects.md # XFadeEffect `XFadeEffect` is used by `enterAnimation`, `exitAnimation`, and video `transition`. Scene `transition` uses the same names. The list below is the shared set declared by the renderer and accepted by the hosted API. ## Effect Gallery Hover a tile to play it. Every clip is two scenes joined by that effect with `transitionDuration: 1` — rendered by Zvid. Effect previews (each link demonstrates the named effect): - [`circleclose` preview](https://cdn.zvid.io/library/docs/xfade-circleclose.mp4) - [`circlecrop` preview](https://cdn.zvid.io/library/docs/xfade-circlecrop.mp4) - [`circleopen` preview](https://cdn.zvid.io/library/docs/xfade-circleopen.mp4) - [`diagbl` preview](https://cdn.zvid.io/library/docs/xfade-diagbl.mp4) - [`diagbr` preview](https://cdn.zvid.io/library/docs/xfade-diagbr.mp4) - [`diagtl` preview](https://cdn.zvid.io/library/docs/xfade-diagtl.mp4) - [`diagtr` preview](https://cdn.zvid.io/library/docs/xfade-diagtr.mp4) - [`dissolve` preview](https://cdn.zvid.io/library/docs/xfade-dissolve.mp4) - [`fade` preview](https://cdn.zvid.io/library/docs/xfade-fade.mp4) - [`fadeblack` preview](https://cdn.zvid.io/library/docs/xfade-fadeblack.mp4) - [`fadegrays` preview](https://cdn.zvid.io/library/docs/xfade-fadegrays.mp4) - [`fadewhite` preview](https://cdn.zvid.io/library/docs/xfade-fadewhite.mp4) - [`hblur` preview](https://cdn.zvid.io/library/docs/xfade-hblur.mp4) - [`hlslice` preview](https://cdn.zvid.io/library/docs/xfade-hlslice.mp4) - [`horzclose` preview](https://cdn.zvid.io/library/docs/xfade-horzclose.mp4) - [`horzopen` preview](https://cdn.zvid.io/library/docs/xfade-horzopen.mp4) - [`hrslice` preview](https://cdn.zvid.io/library/docs/xfade-hrslice.mp4) - [`pixelize` preview](https://cdn.zvid.io/library/docs/xfade-pixelize.mp4) - [`radial` preview](https://cdn.zvid.io/library/docs/xfade-radial.mp4) - [`rectcrop` preview](https://cdn.zvid.io/library/docs/xfade-rectcrop.mp4) - [`slidedown` preview](https://cdn.zvid.io/library/docs/xfade-slidedown.mp4) - [`slideleft` preview](https://cdn.zvid.io/library/docs/xfade-slideleft.mp4) - [`slideright` preview](https://cdn.zvid.io/library/docs/xfade-slideright.mp4) - [`slideup` preview](https://cdn.zvid.io/library/docs/xfade-slideup.mp4) - [`smoothdown` preview](https://cdn.zvid.io/library/docs/xfade-smoothdown.mp4) - [`smoothleft` preview](https://cdn.zvid.io/library/docs/xfade-smoothleft.mp4) - [`smoothright` preview](https://cdn.zvid.io/library/docs/xfade-smoothright.mp4) - [`smoothup` preview](https://cdn.zvid.io/library/docs/xfade-smoothup.mp4) - [`vdslice` preview](https://cdn.zvid.io/library/docs/xfade-vdslice.mp4) - [`vertclose` preview](https://cdn.zvid.io/library/docs/xfade-vertclose.mp4) - [`vertopen` preview](https://cdn.zvid.io/library/docs/xfade-vertopen.mp4) - [`vuslice` preview](https://cdn.zvid.io/library/docs/xfade-vuslice.mp4) - [`wipebl` preview](https://cdn.zvid.io/library/docs/xfade-wipebl.mp4) - [`wipebr` preview](https://cdn.zvid.io/library/docs/xfade-wipebr.mp4) - [`wipedown` preview](https://cdn.zvid.io/library/docs/xfade-wipedown.mp4) - [`wipeleft` preview](https://cdn.zvid.io/library/docs/xfade-wipeleft.mp4) - [`wiperight` preview](https://cdn.zvid.io/library/docs/xfade-wiperight.mp4) - [`wipetl` preview](https://cdn.zvid.io/library/docs/xfade-wipetl.mp4) - [`wipetr` preview](https://cdn.zvid.io/library/docs/xfade-wipetr.mp4) - [`wipeup` preview](https://cdn.zvid.io/library/docs/xfade-wipeup.mp4) ```typescript type XFadeEffect = | "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"; ``` Use this shared set for portable projects. The hosted validator also recognizes additional FFmpeg names, including `squeezeh`, `squeezev`, `fadefast`, `fadeslow`, vertical wind, cover, and reveal effects; these are not all declared by the renderer/editor's shared effect list. API acceptance alone does not establish equivalent editor preview support. The editor intentionally omits `distance` from its picker because it has no faithful preview. Validate the request with [Validate and estimate](https://docs.zvid.io/docs/validate-and-estimate/). That checks the payload; inspect an actual render when visual effect fidelity matters. The gallery demonstrates its checked-in subset, not every accepted name. ## Used By - [Animation Effects](https://docs.zvid.io/docs/structure/animations/) - [Video Transitions](https://docs.zvid.io/docs/structure/transitions/) - [Text & HTML Elements](https://docs.zvid.io/docs/structure/text-elements/) - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/) - [SVG Elements (deprecated)](https://docs.zvid.io/docs/structure/svg-elements/) --- Canonical URL: https://docs.zvid.io/docs/structure/properties/zoom/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/properties/zoom.md # Zoom `zoom` applies a center-based Ken Burns zoom effect during the element's visible duration. `zoom: true` uses the default depth of `1.2`; pass an object to choose how far the zoom ends. ```typescript zoom?: boolean | { depth?: number }; ``` `depth` is the final scale factor reached at the end of the element's visible window, between `1` and `10` (default `1.2`). `zoom` is supported for `IMAGE`, `VIDEO`, and `GIF` elements. It is not available on `TEXT`/HTML elements (use a CSS `transform: scale(...)` animation through [`customCode`](https://docs.zvid.io/docs/structure/text-elements/#native-css--javascript-customcode) instead) or on the deprecated `SVG` element. ## Example Visual element fragments for a video project's `payload.visuals`. Replace illustrative asset URLs before rendering; the recorded preview uses a separate fixture to demonstrate the effect. Omit `zoom`, or set it to `false`, to keep the original scale. ```json { "type": "IMAGE", "src": "https://cdn.example.com/photo.jpg", "width": 1280, "height": 720, "zoom": true } ``` With an explicit depth (ends at 1.5× zoom): ```json { "type": "VIDEO", "src": "https://cdn.example.com/clip.mp4", "resize": "cover", "zoom": { "depth": 1.5 } } ``` ## Used By - [Image Elements](https://docs.zvid.io/docs/structure/image-elements/) - [Video Elements](https://docs.zvid.io/docs/structure/video-elements/) - [GIF Elements](https://docs.zvid.io/docs/structure/gif-elements/) zoom: { depth: 1.35 } — the Ken Burns effect [Watch rendered example](https://cdn.zvid.io/library/docs/zoom-kenburns.mp4) --- Canonical URL: https://docs.zvid.io/docs/structure/scenes/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/scenes.md # Scenes Scenes split a project into a sequence of self-contained segments that play one after another. Each scene has its own **local timeline** (where `0` is the start of that scene), its own elements, and an optional transition into the next scene. Zvid renders every scene independently and then stitches them together, so scenes are the easiest way to build multi-part videos — intros, chapters, product shots, outros — without hand-computing global timestamps. Add scenes with the project-level `scenes` array: ```json { "name": "my-video", "resolution": "hd", "scenes": [ { "id": "intro", "duration": 4, "visuals": [] }, { "id": "main", "duration": -1, "visuals": [] }, { "id": "outro", "duration": 3, "visuals": [] } ] } ``` When `scenes` is present and non-empty, it drives the timeline. The project-level `visuals` and `audios` still render, but as a **global overlay** that spans the whole video (see [Global overlay layer](#global-overlay-layer)). If you provide scene IDs, make each one unique within the project. Reusing an ID such as `rest` in two scenes produces a validation error at the duplicate scene's `id` field before the job is queued. Omit IDs when you do not need to refer to scenes by name; Zvid generates IDs without colliding with the IDs you provided. ## Why scenes? Without scenes, every element shares one global timeline, so a clip that starts at second 12 needs `enterBegin: 12`, and inserting a new section means shifting every timestamp after it. With scenes: - Each scene's elements use times relative to that scene, so `enterBegin: 0` is "the start of this scene." - Reordering, adding, or removing a scene never breaks the timing of the others. - Per-scene `backgroundColor` and auto-fitting `duration` remove most manual bookkeeping. - Scene-to-scene transitions reuse the same [xfade effects](https://docs.zvid.io/docs/structure/properties/xfade-effects/) as video transitions. ## Scene Interface The interface describes scene rendering fields. For authoring, scenes can also declare `variables`, `iterate`, `iterateAs`, and `condition`; see [Dynamic content](https://docs.zvid.io/docs/templates/dynamic-content/). ```typescript interface Scene { id?: string; visuals?: Item[]; audios?: AudioItem[]; duration?: number; transition?: XFadeEffect | null; transitionId?: string | null; transitionDuration?: number; backgroundColor?: string; } ``` | Property | Type | Default | Notes | | -------------------- | --------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------- | | `id` | `string` | `scene-` | Unique scene id. Auto-generated from the array index when omitted. Duplicate ids are rejected. | | `visuals` | `Item[]` | `[]` | Visual elements on the scene-local timeline (`0` = scene start). | | `audios` | `AudioItem[]` | `[]` | Audio tracks on the scene-local timeline. | | `duration` | `number` | `-1` (auto) | Scene length in seconds. `-1` (or omitted) auto-fits the scene to its content. | | `transition` | `XFadeEffect \| null` | `null` | Effect blending into the **next** scene. `null` is a hard cut. | | `transitionId` | `string \| null` | next scene | Id of the next scene. Optional; scenes always play in array order (see below). | | `transitionDuration` | `number` | `0.5` (when `transition` set) | Overlap length in seconds. Must be shorter than both adjacent scene durations. | | `backgroundColor` | `string` | project `backgroundColor` | Per-scene background. Falls back to the project background, then `#ffffff`. | > **Resolution and frame rate are project-wide** Scenes never set their own `resolution`, `width`, `height`, or `frameRate`. Every scene is rendered on the parent project's canvas so all intermediates match. Set those on the project. ## Scene Duration Each scene resolves to a concrete length before rendering: - A positive `duration` is used as-is. - `duration: -1` (or omitted) **auto-fits** the scene: Zvid uses the latest end time among the scene's elements. - Videos and audios without an explicit end contribute their intrinsic (probed) length. - Images, text/HTML, SVG, and GIF elements stretch or loop to any length, so without an explicit `exitEnd` they do **not** constrain the scene. - If nothing constrains the scene, it falls back to a default of **10 seconds**. ```json { "id": "hero-clip", "duration": -1, "visuals": [ { "type": "VIDEO", "src": "https://videos.pexels.com/video-files/1409899/1409899-sd_640_360_25fps.mp4", "resize": "cover", "position": "center-center", "volume": 0 } ] } ``` Here the scene auto-fits to the clip's length. To pin an auto-fit scene to a text element, give that element an explicit `exitEnd`. The example above is a **scene fragment**, placed in `payload.scenes`. Hosted project `durationMode: "auto"` needs explicit media bounds so the API can calculate plan limits and credits without probing source files. Stored video templates require positive resolved scene durations. For those paths, set `videoEnd`/`exitEnd` or an explicit scene `duration` as appropriate, and read [Timing](https://docs.zvid.io/docs/concepts/timing/) before submitting. ## Transitions Between Scenes Scenes always play in **array order**. A transition blends the current scene into the one that follows it: ```json { "scenes": [ { "id": "intro", "duration": 4, "transition": "fade", "transitionDuration": 0.8, "visuals": [] }, { "id": "main", "duration": 5, "visuals": [] } ] } ``` Key rules: - `transition` is the [xfade effect](https://docs.zvid.io/docs/structure/properties/xfade-effects/) into the next scene. `null` (the default) is a hard cut. - A transition **overlaps** the two scenes: the next scene starts `transitionDuration` seconds before the current one ends, exactly like [video transitions](https://docs.zvid.io/docs/structure/transitions/). - `transitionDuration` defaults to `0.5` and must be shorter than both adjacent scene durations. - `transitionId` is optional. Because scenes play in array order, you may omit it. If you do set it, it must match the **next** scene's id (or be `null` / `"none"`); pointing it anywhere else disables the transition and falls back to a hard cut. - A `transition` on the **last** scene is ignored (there is nothing to blend into). ## Global overlay layer Project-level `visuals` and `audios` still apply when scenes are used. They are composited **on top of** the stitched scenes and span the **entire** video — ideal for a persistent watermark, logo, or background music bed: ```json { "name": "branded-video", "resolution": "hd", "scenes": [ { "id": "a", "duration": 3, "visuals": [] }, { "id": "b", "duration": 3, "visuals": [] } ], "visuals": [ { "type": "TEXT", "html": "
© zvid
", "position": "bottom-right", "opacity": 0.6, "customCode": { "css": ".wm { color: #ffffff; font-family: Montserrat; font-size: 32px; }" } } ], "audios": [ { "src": "https://cdn.pixabay.com/audio/2026/02/24/audio_3f375fdf97.mp3", "volume": 0.2 } ] } ``` Scene elements always render **below** every global visual, so overlays stay on top regardless of `track` values inside scenes. ## Total Duration The scene sequence length is the sum of scene durations minus transition overlaps. With fixed/default project timing, `duration` interacts with that total: - If `duration` is omitted, the video is exactly the scenes' total length. - If `duration` is **shorter** than the scenes' total, it is extended to fit the scenes (a warning is logged). - If `duration` is **longer** than the scenes' total, the video is extended to `duration`; the global overlay layer keeps playing over the final frame. With `durationMode: "auto"`, timed global visuals, audio, and captions can also extend the project. `duration` is a minimum. See [Automatic timing](https://docs.zvid.io/docs/concepts/timing/) for the complete calculation. ## Complete Example Three scenes, two transitions, a global watermark and music bed — the payload below, rendered by Zvid [Watch rendered example](https://cdn.zvid.io/library/docs/scenes-demo.mp4) A three-scene promo: a title built with an [HTML element](https://docs.zvid.io/docs/structure/text-elements/), a background video clip, and an outro — with cross-scene transitions and a global watermark. ```json { "name": "scenes-demo", "resolution": "hd", "frameRate": 30, "backgroundColor": "#000000", "outputFormat": "mp4", "scenes": [ { "id": "intro", "duration": 4, "backgroundColor": "#1a1a2e", "transition": "fade", "transitionId": "clip", "transitionDuration": 0.8, "visuals": [ { "type": "TEXT", "html": "
Welcome
", "position": "center-center", "enterBegin": 0.5, "exitEnd": 4, "customCode": { "css": ".title { color: #ffffff; font-family: Montserrat; font-size: 96px; font-weight: 800; } .title { animation: rise 0.8s ease-out both; } @keyframes rise { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }" } } ] }, { "id": "clip", "duration": -1, "transition": "slideleft", "transitionId": "outro", "transitionDuration": 0.6, "visuals": [ { "type": "VIDEO", "src": "https://www.pexels.com/download/video/4927963/", "resize": "cover", "position": "center-center", "anchor": "center-center", "volume": 0, "videoBegin": 0, "videoEnd": 5 } ] }, { "id": "outro", "duration": 3, "backgroundColor": "#16213e", "visuals": [ { "type": "TEXT", "html": "
Thanks for watching
", "position": "center-center", "customCode": { "css": ".thanks { color: #f3efa2; font-family: Montserrat; font-size: 72px; font-weight: 800; }" } } ] } ], "visuals": [ { "type": "TEXT", "text": "© zvid", "position": "bottom-right", "track": 5, "opacity": 0.6, "style": { "fontFamily": "Montserrat", "fontSize": "32px", "color": "#ffffff" } } ], "audios": [ { "src": "https://cdn.pixabay.com/audio/2026/02/24/audio_3f375fdf97.mp3", "volume": 0.2 } ] } ``` ## Best Practices - Keep timings **scene-local**: prefer `enterBegin: 0` inside a scene over large global offsets. - Use `duration: -1` for video/audio-driven scenes and an explicit `duration` for text-only scenes. - Reach for scenes when sections have distinct backgrounds or you want clean cross-section transitions; reach for a single `visuals` timeline when elements overlap continuously. - Put persistent branding (logo, watermark, music) on the **project** level so it spans every scene. - Keep `transitionDuration` well under the shorter of the two scenes it joins. ## Related Pages - [JSON Structure Overview](https://docs.zvid.io/docs/structure/) - [Text / HTML Elements](https://docs.zvid.io/docs/structure/text-elements/) - [Video Transitions](https://docs.zvid.io/docs/structure/transitions/) - [Animation Effects](https://docs.zvid.io/docs/structure/animations/) - [XFadeEffect](https://docs.zvid.io/docs/structure/properties/xfade-effects/) --- Canonical URL: https://docs.zvid.io/docs/structure/subtitle/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/subtitle.md # Subtitle The root-level `subtitle` property burns word-timed captions into the video. Adding captions can be as simple as: ```json { "subtitle": { "captions": [{ "start": 0, "end": 2, "text": "Hello world" }] } } ``` Word timings are generated automatically from the text (proportionally to word length), so every animation works out of the box. You can still provide exact per-word timings when you have them (e.g. from a transcription model). ## Interface ```typescript interface Subtitle { /** content — provide exactly ONE of src / captions */ src?: string; // public http(s) URL of an SRT or VTT file captions?: Caption[]; /** animation */ animation?: SubtitleAnimation; // default "normal" direction?: "up" | "down" | "left" | "right"; // slide only, default "up" activeWord?: { color?: string; background?: string; radius?: number }; /** typography */ font?: { family?: string; // Google Fonts name, default "Poppins" size?: number; // px, default 50 color?: string; // hex (+ optional alpha), default "#FFFFFF" bold?: boolean; italic?: boolean; transform?: "uppercase" | "lowercase" | "capitalize"; }; stroke?: { color: string; width: number }; /** background box */ background?: { color?: string; // hex (+ optional alpha) opacity?: number; // 0–1, multiplies the color's alpha padding?: number; // px around the text radius?: number; // corner radius in px, 0–200, default 0 }; /** placement */ position?: // default "bottom" | "top" | "center" | "bottom" // shorthands for *-center | "top-left" | "top-center" | "top-right" | "center-left" | "center-center" | "center-right" | "bottom-left" | "bottom-center" | "bottom-right"; margin?: { x?: number; y?: number }; // px from edge, default 5% of size /** layout */ maxWordsPerLine?: number; // split captions into lines of ≤ N words } interface Caption { start: number; // seconds end: number; // seconds text?: string; // words auto-timed when `words` is omitted words?: Word[]; // optional exact per-word timing } ``` Each caption needs `text` and/or `words`. See [`Caption`](https://docs.zvid.io/docs/structure/properties/caption/) and [`Word`](https://docs.zvid.io/docs/structure/properties/word/). Caption and word timestamps are **absolute seconds on the project timeline**, not offsets from the caption start. Subtitles are project-level, including when visuals use scenes. Use increasing `start`/`end` values and keep each word inside its caption's interval. `maxWordsPerLine` is an integer from 1 to 20; omitted, no word-count limit is imposed by this field. ## Loading captions from a file (`src`) Instead of inlining `captions`, point `src` at a public SRT or VTT file. The file is fetched at render time, parsed, and word timings are distributed automatically: ```json { "subtitle": { "src": "https://cdn.example.com/captions.srt", "animation": "highlight", "activeWord": { "color": "#0b0d12", "background": "#7CFFB2" } } } ``` `src` and `captions` are mutually exclusive. The URL must be a public http(s) address (private hosts and non-standard ports are rejected). ## Animations | `animation` | Behavior | | ------------- | -------------------------------------------------------------------- | | `normal` | Static captions (default). `none` is an alias. | | `one-word` | Only the word being spoken is shown. | | `karaoke` | Full text; the spoken word switches to `activeWord.color`. | | `highlight` | Karaoke plus a box behind the spoken word (`activeWord.background`). | | `progressive` | Words appear as they are spoken and stay. | | `fill` | Color sweeps across each word while it is spoken (true karaoke). | | `pop` | The spoken word scales up with a punchy two-stage animation. | | `bounce` | The spoken word bounces in with a spring overshoot. | | `fade` | Words fade in as they are spoken. | | `typewriter` | Characters type on at the spoken pace. | | `slide` | Each word slides into its slot (`direction`: up/down/left/right). | There are 11 distinct modes; `none` is an alias for `normal`. ### Mode Gallery The same caption rendered in every animation mode — hover to play: Effect previews (each link demonstrates the named effect): - [`bounce` preview](https://cdn.zvid.io/library/docs/subtitle-mode-bounce.mp4) - [`fade` preview](https://cdn.zvid.io/library/docs/subtitle-mode-fade.mp4) - [`fill` preview](https://cdn.zvid.io/library/docs/subtitle-mode-fill.mp4) - [`highlight` preview](https://cdn.zvid.io/library/docs/subtitle-mode-highlight.mp4) - [`karaoke` preview](https://cdn.zvid.io/library/docs/subtitle-mode-karaoke.mp4) - [`normal` preview](https://cdn.zvid.io/library/docs/subtitle-mode-normal.mp4) - [`one-word` preview](https://cdn.zvid.io/library/docs/subtitle-mode-one-word.mp4) - [`pop` preview](https://cdn.zvid.io/library/docs/subtitle-mode-pop.mp4) - [`progressive` preview](https://cdn.zvid.io/library/docs/subtitle-mode-progressive.mp4) - [`slide` preview](https://cdn.zvid.io/library/docs/subtitle-mode-slide.mp4) - [`typewriter` preview](https://cdn.zvid.io/library/docs/subtitle-mode-typewriter.mp4) ## Examples ### Karaoke captions with a background box ```json { "subtitle": { "captions": [ { "start": 0, "end": 3, "text": "Let's create amazing videos" } ], "animation": "karaoke", "activeWord": { "color": "#FFD700" }, "font": { "family": "Montserrat", "size": 50, "bold": true }, "background": { "color": "#000000", "opacity": 0.8, "padding": 12 }, "position": "center" } } ``` ### Short lines, top of frame ```json { "subtitle": { "captions": [ { "start": 0, "end": 5, "text": "This caption is split into short lines automatically" } ], "maxWordsPerLine": 4, "position": "top", "margin": { "x": 40, "y": 60 }, "font": { "size": 40, "transform": "uppercase" }, "stroke": { "color": "#000000", "width": 3 } } } ``` ### Exact word timings (e.g. from Whisper) ```json { "subtitle": { "captions": [ { "start": 0.5, "end": 2, "text": "Welcome to our video", "words": [ { "start": 0.5, "end": 0.9, "text": "Welcome" }, { "start": 0.9, "end": 1.1, "text": "to" }, { "start": 1.1, "end": 1.4, "text": "our" }, { "start": 1.4, "end": 2, "text": "video" } ] } ], "animation": "fill", "activeWord": { "color": "#7CFFB2" } } } ``` ## Limits and notes - `stroke` and `background` can be combined — the text keeps its outline on top of the caption box. - Captions are counted against your plan's caption limit; `src` files are capped at 5,000 cues and 2 MB. - Set `background.radius` for rounded caption boxes and `activeWord.radius` for rounded active-word boxes. Both use pixels, accept 0–200, and default to square corners (`0`). A radius needs a corresponding background color to be visible. Active-word boxes apply to modes that display them. - Subtitles are not available on image renders. ## Legacy schema The previous shape — `{ "captions": [...], "styles": { "mode": ..., "isBold": ..., "marginV": ... } }` — remains accepted and uses the same rendering pipeline; see [`SubtitleStyles`](https://docs.zvid.io/docs/structure/properties/subtitle-styles/). It cannot be mixed with the flat style fields above in the same subtitle object. ## Related Pages - [JSON Structure](https://docs.zvid.io/docs/structure/) - [Caption](https://docs.zvid.io/docs/structure/properties/caption/) - [Word](https://docs.zvid.io/docs/structure/properties/word/) - [SubtitleStyles (legacy)](https://docs.zvid.io/docs/structure/properties/subtitle-styles/) --- Canonical URL: https://docs.zvid.io/docs/structure/svg-elements/ Markdown URL: https://docs.zvid.io/markdown/docs/structure/svg-elements.md # SVG Elements (Deprecated) > **Deprecated for new text & animation work** For text, badges, layouts, and animated graphics, prefer [HTML elements](https://docs.zvid.io/docs/structure/text-elements/) with [`customCode`](https://docs.zvid.io/docs/structure/text-elements/#native-css--javascript-customcode) — they provide rich HTML layout and native CSS/JavaScript animation. Existing `SVG` elements remain supported, and the editor's Shape library uses them. For new HTML compositions, `TEXT.html` also accepts a [restricted geometry-only SVG subset](https://docs.zvid.io/docs/structure/text-elements/#inline-svg-in-html). Its allowed tags/attributes are stricter than the standalone SVG rules here. `SVG` elements render inline SVG markup into a visual layer. The API accepts safe SVG content and rejects active content or external resource references. ## Interface ```typescript interface SVGItem { type: "SVG"; svg: string; // must start with `, ``, `