# Get started # Getting Started Pick the path that fits how you build. ## I'm a developer Build with the REST API. Send JSON, get an MP4 — automate everything. 1. [Quickstart](@/getting-started/quickstart) 2. [Tutorials (chapters 1-3)](@/tutorials/01-your-first-video) 3. [API Endpoints / Movies](@/reference/api-endpoints/movies-create) 4. [SDKs](@/reference/sdks) ## I'm using no-code tools Connect JSON2Video to Make.com, n8n or Zapier — no code required. 1. [What is JSON2Video](@/getting-started/what-is-json2video) 2. [Make.com end-to-end](@/guides/no-code/makecom/end-to-end) 3. [Templates](@/guides/dashboard/templates) ## I'm using the dashboard Create and edit videos visually — perfect for marketers and creators. 1. [What is JSON2Video](@/getting-started/what-is-json2video) 2. [Visual editor](@/guides/dashboard/visual-editor) 3. [Templates](@/guides/dashboard/templates) 4. [Examples gallery](@/guides/examples) # What is JSON2Video # What is JSON2Video JSON2Video is a video-generation API that turns a JSON document into a rendered MP4. You describe the movie — scenes, images, video clips, text, audio, TTS voices, subtitles, components — as a JSON object, send it to the API, and get back a downloadable video file. It is built for automation. Because the input is plain JSON, movies are programmatic: you can store reusable templates, swap variables per request, and produce thousands of personalized videos from a single specification. The same JSON can include text, voiceovers from text-to-speech models, automatic subtitles, HTML elements, and prebuilt components — without leaving the API. ## Three ways to use it - **Use the API directly.** Send JSON to `POST /v2/movies` from your backend or any HTTP client. Start at [Your first video](@/tutorials/01-your-first-video). - **Use the Dashboard.** Build and edit movies visually, manage templates, media, and API keys. See [Visual editor](@/guides/dashboard/visual-editor). - **Use a no-code platform.** Connect JSON2Video to Make.com, n8n or Zapier. See the [Make.com end-to-end guide](@/guides/no-code/makecom/end-to-end). ## Watch a 2-minute introduction https://www.youtube.com/watch?v=Wf9ck5KDo80 # Quickstart # Quickstart Render your first video in five minutes. This guide assumes you have a JSON2Video account and a terminal or runtime with HTTP client capabilities. ## 1. Get your API key Sign in to the Dashboard and copy your API key from the API Keys page. Full instructions are in the [API keys guide](@/guides/dashboard/api-keys). All requests to the API must include the key in the `x-api-key` header. ## 2. Create your first movie Send a `POST /v2/movies` request with a minimal JSON body. The example below produces a 5-second 1920x1080 video with a single line of text. :::tabs ::: curl ```bash curl -X POST "https://api.json2video.com/v2/movies" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "resolution": "full-hd", "scenes": [ { "duration": 5, "elements": [ { "type": "text", "text": "Hello, JSON2Video!", "style": "001" } ] } ] }' ``` ::: ::: node ```javascript const res = await fetch("https://api.json2video.com/v2/movies", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ resolution: "full-hd", scenes: [ { duration: 5, elements: [ { type: "text", text: "Hello, JSON2Video!", style: "001" }, ], }, ], }), }); const { project } = await res.json(); console.log("Project ID:", project); ``` ::: ::: python ```python import requests res = requests.post( "https://api.json2video.com/v2/movies", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json", }, json={ "resolution": "full-hd", "scenes": [ { "duration": 5, "elements": [ {"type": "text", "text": "Hello, JSON2Video!", "style": "001"} ], } ], }, ) project = res.json()["project"] print("Project ID:", project) ``` ::: ::: php ```php "full-hd", "scenes" => [[ "duration" => 5, "elements" => [ ["type" => "text", "text" => "Hello, JSON2Video!", "style" => "001"] ] ]] ]); $ch = curl_init("https://api.json2video.com/v2/movies"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $body); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "x-api-key: YOUR_API_KEY", "Content-Type: application/json" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = json_decode(curl_exec($ch), true); curl_close($ch); echo "Project ID: " . $response["project"]; ``` ::: ::: The response contains a `project` ID. Rendering is asynchronous — use the ID to poll for the result. ## 3. Poll for the result Send `GET /v2/movies?project={id}` every few seconds. The response includes a `status` field that progresses through `queued`, `running`, and finally `done` or `error`. :::tabs ::: curl ```bash curl "https://api.json2video.com/v2/movies?project=YOUR_PROJECT_ID" \ -H "x-api-key: YOUR_API_KEY" ``` ::: ::: node ```javascript async function waitForMovie(projectId) { while (true) { const res = await fetch( `https://api.json2video.com/v2/movies?project=${projectId}`, { headers: { "x-api-key": "YOUR_API_KEY" } } ); const data = await res.json(); if (data.movie.status === "done") return data.movie; if (data.movie.status === "error") throw new Error(data.movie.message); await new Promise(r => setTimeout(r, 3000)); } } const movie = await waitForMovie("YOUR_PROJECT_ID"); console.log("Video URL:", movie.url); ``` ::: ::: python ```python import time, requests def wait_for_movie(project_id): while True: res = requests.get( "https://api.json2video.com/v2/movies", params={"project": project_id}, headers={"x-api-key": "YOUR_API_KEY"}, ) movie = res.json()["movie"] if movie["status"] == "done": return movie if movie["status"] == "error": raise RuntimeError(movie.get("message")) time.sleep(3) movie = wait_for_movie("YOUR_PROJECT_ID") print("Video URL:", movie["url"]) ``` ::: ::: php ```php # Core concepts # Core concepts These are the building blocks every JSON2Video request shares. Understanding them is enough to read and write any movie JSON. - **Movie.** The final video output. A movie is a JSON document describing the video's resolution, scenes, and movie-level elements that overlay every scene. - **Scene.** A segment of the movie. A movie contains one or more scenes, played back-to-back. Scenes cannot overlap; each renders independently and is then chained into the final output. - **Element.** The atomic content unit inside a scene or movie. Element types include `image`, `video`, `text`, `audio`, `voice`, `subtitles`, `component`, `html`, and `audiogram`. - **Duration and timing.** Every element has a `start` (when it appears) and a `duration` (how long it plays). `duration` accepts a number in seconds, or `-1` for "natural duration of the asset", or `-2` for "match the container". - **Coordinates.** Positioning works like HTML/CSS: the canvas origin `(0, 0)` is the top-left corner. `x` and `y` move right and down. Width/height match the movie's `resolution`. - **Layering.** Elements stack in the order they appear in the JSON array. Later elements paint on top of earlier ones, the same way HTML siblings stack along the z-axis. - **Caching.** The renderer caches downloaded assets and intermediate scene renders to speed up repeat requests and reduce cost. Set `cache: false` on an element to bypass the cache for that asset. ## Watch a 2-minute walkthrough https://www.youtube.com/watch?v=Wf9ck5KDo80 # Next steps # Next steps You have the basics. Pick the path that matches your goal. ## Tutorials A 16-chapter developer course that builds one project from scratch — each chapter adds a new concept (scenes, transitions, voice, subtitles, templates, expressions, webhooks). Start at [Tutorials](@/tutorials). ## Guides Task-oriented walkthroughs: Dashboard usage, no-code integrations with Make.com and n8n, third-party providers, advanced patterns (audiograms, chroma key, HTML rendering), and a gallery of ready-made examples. Browse [Guides](@/guides). ## Reference The full specification: every API endpoint, every JSON property, the webhooks payload, the error catalog, credits and limits, and the changelog. Browse [Reference](@/reference). ## SDKs Official client libraries for PHP and NodeJS that wrap the REST API. See [Reference / SDKs](@/reference/sdks). # Tutorials # Tutorials This page is being prepared. In the meantime, see [the overview](../) for an introduction to this section. # 1. Your first video --- chapter: 01 title: Your first video throughline: real-estate property listings requires_chapter: null last_reviewed: 2026-05-12 --- # 1. Your first video This chapter renders your first video with JSON2Video: a single still image with a "For Sale" text overlay. It introduces the four building blocks every JSON2Video movie has — `scenes`, `elements`, `type`, and `src`/`text` — and the `duration` property that controls how long each element stays on screen. **Prerequisites:** none. If you have not done the [Quickstart](@/getting-started/quickstart), that's fine — this tutorial reproduces the same render with annotations. **Throughline.** Across all 16 chapters you will build a real-estate listing video for a single property at 123 Oak Street. By the end you will have a multi-scene listing with voice-over, subtitles, variables, conditions, and webhook delivery. We start small: one image, one line of text. ## Step 1 — A movie is a JSON document The smallest valid JSON2Video movie is a `scenes` array with one scene. A scene must contain at least one element. Without elements there is nothing to render. ```json { "scenes": [ { "elements": [] } ] } ``` This renders a 360p, 5-second black video. Not useful yet, but valid. Let's add content. ## Step 2 — Add the property photo Add an `image` element pointing at the exterior shot of the house. Every element needs a `type` field that tells JSON2Video what it is. For images, `type` is `"image"` and `src` is the public URL of the file. ```json { "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" } ] } ] } ``` The image fills the canvas. Default resolution is small (640×360), so let's also pick a sensible movie size. ## Step 3 — Set the canvas size Add `resolution: "full-hd"` at the top level. This is the most common 16:9 size (1920×1080). Other valid values are documented in the [Movie reference](@/reference/json-syntax/movie). ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" } ] } ] } ``` ## Step 4 — Overlay a "For Sale" text Elements in a scene stack from bottom to top in array order. Add a `text` element after the image so it appears on top. `type: "text"` requires a `text` field with the literal string to show. ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "FOR SALE — 123 Oak Street" } ] } ] } ``` ## Step 5 — Control how long the scene lasts By default an image element renders for the scene's duration; without a duration the scene runs for 5 seconds. To make a slower-paced 8-second card, set `duration` on each element. The scene takes the maximum of its elements' durations. ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg", "duration": 8 }, { "type": "text", "text": "FOR SALE — 123 Oak Street", "duration": 8 } ] } ] } ``` ## Step 6 — Send it to the API Submit the JSON with `POST /v2/movies`. You can use any HTTP client. Replace `YOUR_API_KEY` with the key from the [Dashboard → API keys](@/guides/dashboard/api-keys). :::tabs ::: curl ```bash curl -X POST https://api.json2video.com/v2/movies \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d @movie.json ``` ::: ::: node ```javascript import { writeFileSync } from "node:fs"; const movie = JSON.parse(await import("node:fs").then(fs => fs.promises.readFile("movie.json", "utf8"))); const res = await fetch("https://api.json2video.com/v2/movies", { method: "POST", headers: { "x-api-key": process.env.JSON2VIDEO_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify(movie), }); const { project } = await res.json(); console.log("Project ID:", project); ``` ::: ::: python ```python import os, json, requests with open("movie.json") as f: movie = json.load(f) r = requests.post( "https://api.json2video.com/v2/movies", headers={ "x-api-key": os.environ["JSON2VIDEO_API_KEY"], "Content-Type": "application/json", }, json=movie, ) print("Project ID:", r.json()["project"]) ``` ::: ::: php ```php true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "x-api-key: " . getenv("JSON2VIDEO_API_KEY"), "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode($movie), ]); $res = json_decode(curl_exec($ch), true); echo "Project ID: " . $res["project"]; ``` ::: ::: The response gives you a `project` ID. Poll `GET /v2/movies?project=` until `status` is `done`, then download the `url`. See [Get movie status](@/reference/api-endpoints/movies-status) for the polling contract. ## The complete final JSON ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg", "duration": 8 }, { "type": "text", "text": "FOR SALE — 123 Oak Street", "duration": 8 } ] } ] } ``` ## Expected output An 8-second 1920×1080 MP4 showing the property photo with a centred "FOR SALE — 123 Oak Street" text card on top. Sample render: [tutorial-01.mp4](https://cdn.json2video.com/samples/tutorial-01.mp4) (placeholder — see [_assets](@/tutorials/_assets) for notes on sample URLs). ## What you learned - A JSON2Video movie is a JSON document with a `scenes` array. - A scene contains `elements`. Every element has a `type` (`image`, `text`, `video`, …). - Element order in the array sets layering — later elements draw on top. - `resolution` sets canvas size; `duration` sets how long an element renders. - You submit the JSON to `POST /v2/movies` and poll until the render is done. ## Next chapter [2. Images, videos & audios →](@/tutorials/02-images-videos-audios) # 2. Images, videos & audios --- chapter: 02 title: Images, videos & audios throughline: real-estate property listings requires_chapter: 01-your-first-video last_reviewed: 2026-05-12 --- # 2. Images, videos & audios In chapter 1 you rendered a single still image with a text overlay. This chapter adds the other two asset element types — `video` and `audio` — and introduces element-level timing with `start` and `duration`. By the end you have a short reel that shows two photos, then a short video clip, with background music underneath. **Prerequisites:** [chapter 1](@/tutorials/01-your-first-video). You should know what a scene and an element are, and how to submit a movie with `POST /v2/movies`. ## Step 1 — Start from chapter 1 The starting point is the final JSON from chapter 1 — one image plus a text overlay. ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg", "duration": 8 }, { "type": "text", "text": "FOR SALE — 123 Oak Street", "duration": 8 } ] } ] } ``` ## Step 2 — Add a second image, sequenced We want two photos shown back-to-back inside the same scene: the exterior shot first, then the kitchen. Use `start` to delay the second image. The exterior runs from 0 to 4 s; the kitchen runs from 4 to 8 s. ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg", "start": 0, "duration": 4 }, { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg", "start": 4, "duration": 4 }, { "type": "text", "text": "FOR SALE — 123 Oak Street", "duration": 8 } ] } ] } ``` The text element has no `start`, so it defaults to `0` and is visible across the full 8 seconds. ## Step 3 — Add a short video clip A `video` element is identical to `image` except the source is an MP4/WebM/MOV. Drop a 5-second drone shot at the end and extend the scene to 13 seconds. ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg", "start": 0, "duration": 4 }, { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg", "start": 4, "duration": 4 }, { "type": "video", "src": "https://cdn.json2video.com/assets/videos/sample-house-drone.mp4", "start": 8, "duration": 5 }, { "type": "text", "text": "FOR SALE — 123 Oak Street", "duration": 13 } ] } ] } ``` > Tip — if your source video is longer than `duration`, JSON2Video trims it. If shorter, the last frame freezes. Use the video element's `loop` property when you want continuous playback over a longer slot. See [Video element](@/reference/json-syntax/element/video). ## Step 4 — Add background music Audio plays in parallel with whatever else is on screen. Drop a music track at the **movie level** so it spans the whole video (not just a single scene). Movie-level elements live in a top-level `elements` array — separate from scene-level `elements`. ```json { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 } ], "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg", "start": 0, "duration": 4 }, { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg", "start": 4, "duration": 4 }, { "type": "video", "src": "https://cdn.json2video.com/assets/videos/sample-house-drone.mp4", "start": 8, "duration": 5 }, { "type": "text", "text": "FOR SALE — 123 Oak Street", "duration": 13 } ] } ] } ``` `volume: 0.4` keeps the music in the background so a future voice-over (chapter 7) sits cleanly on top. ## The complete final JSON ```json { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 } ], "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg", "start": 0, "duration": 4 }, { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg", "start": 4, "duration": 4 }, { "type": "video", "src": "https://cdn.json2video.com/assets/videos/sample-house-drone.mp4", "start": 8, "duration": 5 }, { "type": "text", "text": "FOR SALE — 123 Oak Street", "duration": 13 } ] } ] } ``` ## Expected output A 13-second 1920×1080 MP4: 4 s exterior photo, 4 s kitchen photo, 5 s drone video, with quiet uplifting music throughout and the "FOR SALE — 123 Oak Street" label always on screen. Sample render: [tutorial-02.mp4](https://cdn.json2video.com/samples/tutorial-02.mp4) (placeholder). ## What you learned - `start` and `duration` let you sequence multiple elements inside a single scene. - `video` elements work like `image` elements but consume a clip. - Movie-level `elements` overlay every scene — perfect for background music that spans the whole video. - `audio.volume` is a float between 0 and 1. ## Previous chapter / Next chapter [← 1. Your first video](@/tutorials/01-your-first-video) · [3. Multiple scenes & transitions →](@/tutorials/03-multiple-scenes-and-transitions) # 3. Multiple scenes & transitions --- chapter: 03 title: Multiple scenes & transitions throughline: real-estate property listings requires_chapter: 02-images-videos-audios last_reviewed: 2026-05-12 --- # 3. Multiple scenes & transitions So far the listing lives inside a single scene. Real videos cut between scenes — exterior, kitchen, bedroom — and use transitions to smooth those cuts. This chapter introduces the scene boundary, scene-level `duration`, and the `transition` property. **Prerequisites:** [chapter 2](@/tutorials/02-images-videos-audios). You should know how images, videos, and audio elements work, and how `start`/`duration` sequence elements inside a scene. ## Step 1 — Promote each photo to its own scene In chapter 2 every photo was an element inside one giant scene. Here we split the content into three scenes — one per room. Each scene gets its own elements array and its own duration. ```json { "resolution": "full-hd", "scenes": [ { "duration": 4, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior" } ] }, { "duration": 4, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen" } ] }, { "duration": 4, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom" } ] } ] } ``` Scenes run sequentially. Total length here is 12 s (4 + 4 + 4). Scene-level `duration` overrides element-level duration for elements that omit theirs. > Note — within a single scene the timeline is local to the scene. `start: 0` inside scene 2 means "0 s after scene 2 begins", not "0 s into the movie". ## Step 2 — Add a transition between scenes Each scene can define a `transition` object describing how it enters from the previous one. The most common is a fade. The shape of a scene becomes: ``` { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ ... ] } ``` The transition runs at the **start** of the scene and overlaps with the previous one. With `duration: 0.5` the fade-in lasts half a second. ```json { "resolution": "full-hd", "scenes": [ { "duration": 4, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior" } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen" } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom" } ] } ] } ``` The first scene gets no transition because it has nothing to fade from. ## Step 3 — Keep the background music The music track from chapter 2 was movie-level and spans the whole timeline automatically — it does not care about scene boundaries. Add it back: ``` { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 } ], "scenes": [ ... 3 scenes as above ... ] } ``` ## The complete final JSON ```json { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 } ], "scenes": [ { "duration": 4, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior" } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen" } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom" } ] } ] } ``` ## Expected output A 12-second 1920×1080 MP4 showing three rooms — exterior (4 s) → kitchen (4 s, fades in) → bedroom (4 s, fades in) — with background music throughout. Sample render: [tutorial-03.mp4](https://cdn.json2video.com/samples/tutorial-03.mp4) (placeholder). ## What you learned - A movie can have multiple scenes; scenes run sequentially. - Scene-level `duration` applies to all elements in the scene that omit their own. - `transition` sits on a scene and describes how it enters from the previous one. - Movie-level elements (like background music) span every scene automatically. - Inside a scene, `start: 0` means "scene-local 0", not "movie-local 0". ## Going further Other transition styles include `slide`, `zoom`, `wipe`, and `circle`. See the [Scene reference](@/reference/json-syntax/scene) for the full list. ## Previous chapter / Next chapter [← 2. Images, videos & audios](@/tutorials/02-images-videos-audios) · [4. Text & styling →](@/tutorials/04-text-and-styling) # 4. Text & styling --- chapter: 04 title: Text & styling throughline: real-estate property listings requires_chapter: 03-multiple-scenes-and-transitions last_reviewed: 2026-05-12 --- # 4. Text & styling The text overlays in chapter 3 used default styling and were stuck centred on the canvas. Real listings need branded type and labels in specific positions. This chapter introduces the text `settings` object (font, size, colour), positioning, fade animations, and `z-index` for layering control. **Prerequisites:** [chapter 3](@/tutorials/03-multiple-scenes-and-transitions). You should be comfortable with multiple scenes and the `transition` property. ## Step 1 — Start from chapter 3 We pick up the three-scene listing from chapter 3. For clarity the snippets below show only the first scene; apply the same changes to the other two. ```json { "duration": 4, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior" } ] } ``` ## Step 2 — Pick a font, size, and colour Text styling lives in a `settings` object. Its keys are plain CSS property names, so the most-used ones are `font-family`, `font-size`, `color`, and `font-weight`. Use the CSS name exactly — the colour property is `color`, not `font-color`, and a key that is not a CSS property is ignored without any error. ```json { "type": "text", "text": "Exterior", "settings": { "font-family": "Inter", "font-size": "72px", "color": "#FFFFFF", "font-weight": "700" } } ``` > Note — JSON2Video accepts any Google Font name in `font-family`. If you need a custom typeface, supply `font-url` pointing at a TTF/OTF file. See [Text element](@/reference/json-syntax/element/text) for the full settings list. ## Step 3 — Position the label By default text is centred. To anchor it at the bottom-left use the `position` property plus margin offsets. Positions are `top-left`, `top-center`, `top-right`, `center-left`, `center`, `center-right`, `bottom-left`, `bottom-center`, `bottom-right`, or `custom` (with explicit `x`/`y`). ```json { "type": "text", "text": "Exterior", "position": "bottom-left", "x": 60, "y": -60, "settings": { "font-family": "Inter", "font-size": "72px", "color": "#FFFFFF", "font-weight": "700" } } ``` `x` and `y` shift the label relative to the anchor. For `bottom-left` anchors, positive `x` moves right and negative `y` moves up — so `60, -60` insets the label 60 px from the left edge and 60 px from the bottom. ## Step 4 — Fade in / fade out Element-level animations live in `fade-in` and `fade-out` (number of seconds). The label fades in over half a second and out over half a second: ```json { "type": "text", "text": "Exterior", "position": "bottom-left", "x": 60, "y": -60, "fade-in": 0.5, "fade-out": 0.5, "settings": { "font-family": "Inter", "font-size": "72px", "color": "#FFFFFF", "font-weight": "700" } } ``` ## Step 5 — Layer a translucent rectangle behind the text A coloured plate behind the label boosts readability. Use a `component` element from the basic library (chapter 5 covers components in depth) and a `z-index` so it sits between the photo and the text. ```json { "type": "text", "text": "Exterior", "position": "bottom-left", "x": 60, "y": -60, "z-index": 2, "settings": { "font-family": "Inter", "font-size": "72px", "color": "#FFFFFF", "font-weight": "700", "text-shadow": "0 2px 8px rgba(0,0,0,0.6)" } } ``` `z-index` defaults to array order. Setting it explicitly is useful when one element needs to jump above a later sibling. Higher numbers draw on top. ## The complete final JSON ```json { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 } ], "scenes": [ { "duration": 4, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior", "position": "bottom-left", "x": 60, "y": -60, "fade-in": 0.5, "fade-out": 0.5, "z-index": 2, "settings": { "font-family": "Inter", "font-size": "72px", "color": "#FFFFFF", "font-weight": "700", "text-shadow": "0 2px 8px rgba(0,0,0,0.6)" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen", "position": "bottom-left", "x": 60, "y": -60, "fade-in": 0.5, "fade-out": 0.5, "settings": { "font-family": "Inter", "font-size": "72px", "color": "#FFFFFF", "font-weight": "700", "text-shadow": "0 2px 8px rgba(0,0,0,0.6)" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom", "position": "bottom-left", "x": 60, "y": -60, "fade-in": 0.5, "fade-out": 0.5, "settings": { "font-family": "Inter", "font-size": "72px", "color": "#FFFFFF", "font-weight": "700", "text-shadow": "0 2px 8px rgba(0,0,0,0.6)" } } ] } ] } ``` ## Expected output Same 12-second three-scene cut as chapter 3, but each room label now appears bottom-left in white 72-px Inter Bold with a soft drop-shadow, fading in and out at scene boundaries. Sample render: [tutorial-04.mp4](https://cdn.json2video.com/samples/tutorial-04.mp4) (placeholder). ## What you learned - Text appearance lives in `settings`, using CSS property names (`font-family`, `font-size`, `color`, `font-weight`, `text-shadow`). - `position` picks a 9-anchor preset; `x` / `y` apply pixel offsets from that anchor. - `fade-in` / `fade-out` add per-element animation in seconds. - `z-index` overrides array-order layering when you need fine control. ## Previous chapter / Next chapter [← 3. Multiple scenes & transitions](@/tutorials/03-multiple-scenes-and-transitions) · [5. Component library →](@/tutorials/05-component-library) # 5. Component library --- chapter: 05 title: Component library throughline: real-estate property listings requires_chapter: 04-text-and-styling last_reviewed: 2026-05-12 --- # 5. Component library Hand-styling text gets old fast. The component library ships pre-built animated overlays — lower-thirds, title cards, stat boxes, callouts — that you drop in with a `component` element and a small `settings` object. This chapter adds an animated title card at the start of the listing. **Prerequisites:** [chapter 4](@/tutorials/04-text-and-styling). You should be comfortable styling text manually before relying on components. ## Step 1 — What a component is A component is a pre-built animation rendered by the JSON2Video engine on its own canvas. You reference it by an ID (e.g. `basic/000`) and pass `settings` that customise the visible parameters — usually text strings and colours. ```json { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "123 Oak Street" }, "duration": 4 } ``` Components are listed in the [component library](@/reference/components). For this tutorial we use `basic/000`, a clean title card with a headline + subline. Replace `headline`, `subline`, and colour keys to match your brand. ## Step 2 — Insert the title card as a new opening scene Prepend a new scene before the three room scenes. The component takes the full canvas and runs its built-in entry animation automatically. ```json { "resolution": "full-hd", "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "123 Oak Street" } } ] } ] } ``` Components have their own internal timing (entry, hold, exit). Setting `duration: 4` on the element (or letting the scene's duration apply) gives the component the full 4 seconds to play. ## Step 3 — Combine the title card with the room scenes The room scenes from chapter 4 stay unchanged. The new title card precedes them, transitioning into scene 2 with the existing fade. ```json { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 } ], "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "123 Oak Street" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior", "position": "bottom-left", "x": 60, "y": -60, "fade-in": 0.5, "fade-out": 0.5, "settings": { "font-family": "Inter", "font-size": "72px", "color": "#FFFFFF", "font-weight": "700" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen", "position": "bottom-left", "x": 60, "y": -60, "fade-in": 0.5, "fade-out": 0.5, "settings": { "font-family": "Inter", "font-size": "72px", "color": "#FFFFFF", "font-weight": "700" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom", "position": "bottom-left", "x": 60, "y": -60, "fade-in": 0.5, "fade-out": 0.5, "settings": { "font-family": "Inter", "font-size": "72px", "color": "#FFFFFF", "font-weight": "700" } } ] } ] } ``` ## The complete final JSON (Identical to the JSON in step 3 — the listing now opens with an animated title card and continues with three room scenes.) ```json { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 } ], "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "123 Oak Street" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior", "position": "bottom-left", "x": 60, "y": -60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen", "position": "bottom-left", "x": 60, "y": -60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom", "position": "bottom-left", "x": 60, "y": -60 } ] } ] } ``` ## Expected output A 16-second video: animated "FOR SALE / 123 Oak Street" title card (4 s) → exterior → kitchen → bedroom. The title card uses the component's built-in motion design; no manual animation needed. Sample render: [tutorial-05.mp4](https://cdn.json2video.com/samples/tutorial-05.mp4) (placeholder). ## What you learned - A `component` element pulls a pre-built animated overlay from the library. - The `component` field holds the ID (e.g. `basic/000`); the `settings` object customises text, colours, and component-specific knobs. - Each component has its own internal entry / hold / exit timing — `duration` gives it room to play. - Components keep the JSON readable for branded motion design that would otherwise require dozens of lines of `text` styling. ## Going further Components have their own settings catalogue. For a custom intro animation that the library does not provide, use the `html` element introduced in chapter 6 instead. ## Previous chapter / Next chapter [← 4. Text & styling](@/tutorials/04-text-and-styling) · [6. HTML elements →](@/tutorials/06-html-elements) # 6. HTML elements --- chapter: 06 title: HTML elements throughline: real-estate property listings requires_chapter: 05-component-library last_reviewed: 2026-05-12 --- # 6. HTML elements When the component library does not have what you need — say, a custom price tag with rounded corners, brand colours, and a dollar-sign icon — drop an `html` element. JSON2Video renders the HTML, takes a snapshot (optionally after a delay), and composes it onto the canvas. This chapter adds a styled `$849,000` price tag in the bottom-right corner of every room scene. **Prerequisites:** [chapter 5](@/tutorials/05-component-library). You should know what scene-level vs movie-level elements are. ## Step 1 — A minimal HTML element The `html` element takes an `html` string. Empty/default styles inherit from a transparent body — useful for overlays. ```json { "type": "html", "html": "
$849,000
", "position": "bottom-right", "x": -60, "y": -60, "duration": 4 } ``` The HTML is rendered in a transparent viewport and trimmed to the element's bounding box. Position works exactly like the text element from chapter 4. ## Step 2 — Use Tailwind utilities for readability Inline `style` attributes become unreadable past a couple of properties. JSON2Video can preload Tailwind CSS for `html` elements — flip the `tailwind` flag. ```json { "type": "html", "tailwind": true, "html": "
💰$849,000
", "position": "bottom-right", "x": -60, "y": -60, "duration": 4 } ``` With `tailwind: true` the element's HTML is wrapped in a document with the Tailwind stylesheet preloaded — so utility classes resolve directly. ## Step 3 — Wait for fonts / images to load before screenshot Custom fonts and remote images need time to load. The `wait` property delays the screenshot by N seconds: ```json { "type": "html", "tailwind": true, "wait": 0.5, "html": "
💰 $849,000
", "position": "bottom-right", "x": -60, "y": -60, "duration": 4 } ``` 500 ms is enough for Tailwind + a Google Font; 1–2 s is safe for any custom asset. ## Step 4 — Apply the price tag to every room scene Promote the price tag to a **movie-level** element so it shows on every room scene. Limit it to scenes 2–4 with `start`/`duration` so it does not overlay the title card. ```json { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 }, { "type": "html", "tailwind": true, "wait": 0.5, "html": "
💰 $849,000
", "position": "bottom-right", "x": -60, "y": -60, "start": 4, "duration": 12 } ], "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "123 Oak Street" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior", "position": "bottom-left", "x": 60, "y": -60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen", "position": "bottom-left", "x": 60, "y": -60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom", "position": "bottom-left", "x": 60, "y": -60 } ] } ] } ``` Movie-level elements use the **movie timeline**, so `start: 4` means "4 s into the movie" — i.e. right when scene 2 begins. `duration: 12` covers the three room scenes. ## The complete final JSON ```json { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 }, { "type": "html", "tailwind": true, "wait": 0.5, "html": "
💰 $849,000
", "position": "bottom-right", "x": -60, "y": -60, "start": 4, "duration": 12 } ], "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "123 Oak Street" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior", "position": "bottom-left", "x": 60, "y": -60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen", "position": "bottom-left", "x": 60, "y": -60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom", "position": "bottom-left", "x": 60, "y": -60 } ] } ] } ``` ## Expected output A 16-second video. The intro title card runs for 4 s, then a green rounded "💰 $849,000" tag appears bottom-right and stays through the three room scenes. Sample render: [tutorial-06.mp4](https://cdn.json2video.com/samples/tutorial-06.mp4) (placeholder). ## What you learned - `type: html` renders an arbitrary HTML snippet into an image and composes it on the canvas. - `tailwind: true` preloads Tailwind utility classes so you can skip inline `style` blocks. - `wait` delays the screenshot — set it to ~0.5 s for Tailwind, longer for custom fonts or remote images. - Movie-level elements have a movie-wide timeline; `start` is measured from movie start, not scene start. ## Going further The `html` element accepts a `src` URL instead of inline `html` for screenshots of a live web page. See [HTML element reference](@/reference/json-syntax/element/html) and the [HTML rendering guide](@/guides/advanced/html-rendering). ## Previous chapter / Next chapter [← 5. Component library](@/tutorials/05-component-library) · [7. Text-to-speech voiceover →](@/tutorials/07-ai-voiceover) # 7. Text-to-speech voiceover --- chapter: 07 title: Text-to-speech voiceover throughline: real-estate property listings requires_chapter: 06-html-elements last_reviewed: 2026-05-22 --- # 7. Text-to-speech voiceover A silent listing only goes so far. This chapter adds a synthesised voice-over narrating the property. JSON2Video ships with Microsoft Azure voices included in every plan (no per-character cost), with optional premium ElevenLabs voices when you connect your own key. **Prerequisites:** [chapter 6](@/tutorials/06-html-elements). Background music must be quieter than the voice — the `volume: 0.4` we already set in chapter 2 handles that. ## Step 1 — The voice element A `voice` element converts `text` to audio at render time. The required fields are `type: "voice"` and `text`. `voice` and `model` are optional; the default model is Azure. ```json { "type": "voice", "text": "Welcome to 123 Oak Street — a four-bedroom craftsman home, listed at $849,000.", "voice": "en-US-EmmaMultilingualNeural" } ``` `voice` is the speaker ID. Azure exposes hundreds — `en-US-EmmaMultilingualNeural`, `en-US-AndrewMultilingualNeural`, `es-ES-ElviraNeural`, and so on. Browse them all, by language and with audio samples, in the [Azure voices catalog](https://json2video.com/ai-voices/azure/languages/). > Note — voice elements do not need a `duration`. The renderer reads the synthesised audio length and applies it automatically. You can still cap it manually if needed. ## Step 2 — Insert the voice at movie level Put the voice in the top-level `elements` array so it spans across the title card and the room scenes. Use `start` to delay it until after the title card animation finishes. ```json { "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 }, { "type": "voice", "text": "Welcome to 123 Oak Street — a four-bedroom craftsman home, listed at $849,000.", "voice": "en-US-EmmaMultilingualNeural", "start": 1.5 } ] } ``` A 1.5 s delay lets the title card's animation breathe before the voice starts. ## Step 3 — Use a premium ElevenLabs voice (optional) For higher-fidelity speech, switch the model to `elevenlabs` and add a `connection` ID pointing at your ElevenLabs API key (configure in [Dashboard → Connections](@/guides/dashboard/connections)). Without `connection`, the default Azure voice is used. ```json { "type": "voice", "text": "Welcome to 123 Oak Street — a four-bedroom craftsman home, listed at $849,000.", "model": "elevenlabs", "voice": "21m00Tcm4TlvDq8ikWAM", "connection": "my-elevenlabs" } ``` Browse the available ElevenLabs voices, by language and with audio samples, in the [ElevenLabs voices catalog](https://json2video.com/ai-voices/elevenlabs/languages/). ElevenLabs voices consume extra credits (~60 per minute). Azure is free under every plan. See [Credit consumption](@/reference/credits/credit-consumption). ## Step 4 — Submit the SDK call The JSON payload is the same regardless of language. Here is the full POST in four flavours. :::tabs ::: curl ```bash curl -X POST https://api.json2video.com/v2/movies \ -H "x-api-key: $JSON2VIDEO_API_KEY" \ -H "Content-Type: application/json" \ -d @movie.json ``` ::: ::: node ```javascript const movie = await import("node:fs").then(fs => JSON.parse(fs.readFileSync("movie.json", "utf8"))); const res = await fetch("https://api.json2video.com/v2/movies", { method: "POST", headers: { "x-api-key": process.env.JSON2VIDEO_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify(movie), }); console.log(await res.json()); ``` ::: ::: python ```python import os, json, requests movie = json.load(open("movie.json")) r = requests.post( "https://api.json2video.com/v2/movies", headers={"x-api-key": os.environ["JSON2VIDEO_API_KEY"]}, json=movie, ) print(r.json()) ``` ::: ::: php ```php true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["x-api-key: " . getenv("JSON2VIDEO_API_KEY"), "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode($movie), ]); echo curl_exec($ch); ``` ::: ::: ## The complete final JSON ```json { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 }, { "type": "voice", "text": "Welcome to 123 Oak Street — a four-bedroom craftsman home, listed at $849,000.", "voice": "en-US-EmmaMultilingualNeural", "start": 1.5 }, { "type": "html", "tailwind": true, "wait": 0.5, "html": "
💰 $849,000
", "position": "bottom-right", "x": -60, "y": -60, "start": 4, "duration": 12 } ], "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "123 Oak Street" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior", "position": "bottom-left", "x": 60, "y": -60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen", "position": "bottom-left", "x": 60, "y": -60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom", "position": "bottom-left", "x": 60, "y": -60 } ] } ] } ``` ## Expected output The same 16-second listing as chapter 6, now with a clean Emma voice narrating "Welcome to 123 Oak Street — a four-bedroom craftsman home, listed at $849,000." starting 1.5 s in. Sample render: [tutorial-07.mp4](https://cdn.json2video.com/samples/tutorial-07.mp4) (placeholder). ## What you learned - `type: voice` synthesises an audio track from `text` using a text-to-speech engine. - `voice` picks a speaker, `model` picks the provider (default: `azure`). - A movie-level voice element runs across all scenes — use `start` to delay it. - Azure is included in every plan; ElevenLabs requires a `connection` and consumes extra credits. ## Previous chapter / Next chapter [← 6. HTML elements](@/tutorials/06-html-elements) · [8. Automatic subtitles →](@/tutorials/08-automatic-subtitles) # 8. Automatic subtitles --- chapter: 08 title: Automatic subtitles throughline: real-estate property listings requires_chapter: 07-ai-voiceover last_reviewed: 2026-05-12 --- # 8. Automatic subtitles Most social platforms autoplay video with sound off. Subtitles double watch time. This chapter adds a `subtitles` element that transcribes the chapter-7 voice-over automatically — no SRT file required. **Prerequisites:** [chapter 7](@/tutorials/07-ai-voiceover). The listing must already include a `voice` element; the subtitles element transcribes it. ## Step 1 — The simplest subtitles element A bare `subtitles` element transcribes the audio track of the movie automatically. There can be only one subtitles element per movie, and it always lives at movie level. ```json { "type": "subtitles" } ``` That's it. Add it to the top-level `elements` array and the renderer: 1. Mixes the voice + audio tracks. 2. Runs speech-to-text on the result. 3. Burns the captions onto the canvas in a default style. ## Step 2 — Customise the look Subtitles styling lives in a `settings` object. The most-used keys are `style`, `font-family`, `font-size`, `word-color`, `line-color`, `outline-color`, `position`, and `all-caps`. Unlike the `text` element of chapter 4, these settings are **not** CSS: they are a closed list, and any key outside it is rejected before the render starts (`Property 'X' is not allowed in movie/elements[0]/settings`). There is no `color` or `font-color` here — use `word-color` for the word being spoken and `line-color` for the rest of the line (set both to the same value for a uniform caption). `font-size` is a number of pixels, not a CSS length like `"7vw"`. ```json { "type": "subtitles", "settings": { "style": "boxed-word", "font-family": "Inter", "font-size": 90, "word-color": "#FFFFFF", "line-color": "#FFFFFF", "outline-color": "#000000", "position": "bottom-center", "all-caps": true, "box-color": "#0E7C66" } } ``` `style` ranges from `classic` (simple text overlay) to `boxed-word` (modern social-style with a coloured box behind the current word). See the [Subtitles element reference](@/reference/json-syntax/element/subtitles) for all styles and settings. ## Step 3 — Specify the language (optional) The transcription engine auto-detects language by default. If you want to be explicit (or speed up the model), set `language`: ```json { "type": "subtitles", "language": "en", "settings": { "style": "boxed-word", "font-family": "Inter", "font-size": 90, "word-color": "#FFFFFF", "line-color": "#FFFFFF", "outline-color": "#000000", "position": "bottom-center", "all-caps": true, "box-color": "#0E7C66" } } ``` `language` accepts ISO 639-1 codes (`en`, `es`, `fr`, …). ## Step 4 — Mind the room labels at the bottom-left The chapter-4 room labels live at `bottom-left`. Subtitles at `bottom-center` are far enough away that they don't clash, but if you wanted them not to overlap you could move the room labels to `top-left` instead. We keep both for clarity. ## The complete final JSON ```json { "resolution": "full-hd", "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 }, { "type": "voice", "text": "Welcome to 123 Oak Street — a four-bedroom craftsman home, listed at $849,000.", "voice": "en-US-EmmaMultilingualNeural", "start": 1.5 }, { "type": "subtitles", "language": "en", "settings": { "style": "boxed-word", "font-family": "Inter", "font-size": 90, "word-color": "#FFFFFF", "line-color": "#FFFFFF", "outline-color": "#000000", "position": "bottom-center", "all-caps": true, "box-color": "#0E7C66" } }, { "type": "html", "tailwind": true, "wait": 0.5, "html": "
💰 $849,000
", "position": "bottom-right", "x": -60, "y": -60, "start": 4, "duration": 12 } ], "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "123 Oak Street" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior", "position": "top-left", "x": 60, "y": 60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen", "position": "top-left", "x": 60, "y": 60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom", "position": "top-left", "x": 60, "y": 60 } ] } ] } ``` (Room labels moved to `top-left` to keep the bottom band reserved for subtitles.) ## Expected output The chapter-7 listing with bold green-boxed subtitles word-by-word at the bottom, room labels in the top-left, and the price tag bottom-right. Sample render: [tutorial-08.mp4](https://cdn.json2video.com/samples/tutorial-08.mp4) (placeholder). ## What you learned - `type: subtitles` auto-transcribes the audio track of the movie. - Only one subtitles element per movie; it always sits at movie level. - `settings.style` switches between classic captions and modern boxed-word styles. - `language` is optional — set it to skip auto-detection and pick a specific transcription model. ## Going further You can also supply subtitles from a pre-existing SRT/VTT/ASS file with `captions: "https://…"`. Useful for translated subtitle tracks the engine cannot generate yet. See the [Subtitles reference](@/reference/json-syntax/element/subtitles). ## Previous chapter / Next chapter [← 7. Text-to-speech voiceover](@/tutorials/07-ai-voiceover) · [10. Variables →](@/tutorials/10-variables) # 10. Variables --- chapter: 10 title: Variables throughline: real-estate property listings requires_chapter: 08-automatic-subtitles source: api/endpoints/00_common/j2v-lib-evaluator.js last_reviewed: 2026-07-14 --- # 10. Variables The address "123 Oak Street" and the price "$849,000" are hard-coded in five different places across our movie. Every new listing means find-and-replace. This chapter moves those values into a `variables` object and references them with `{{name}}` placeholders. One JSON, dozens of properties. **Prerequisites:** [chapter 8](@/tutorials/08-automatic-subtitles). Variables work the same way for any string property. ## Step 1 — Declare variables at movie level The `variables` object lives at the top level. Keys should use only letters, numbers, and underscores — any other character is silently replaced with an underscore. Names starting with `?`, `$`, or `@` are reserved: those variables are silently dropped. Neither case is a validation error — the render proceeds and the affected placeholders come out renamed or unresolved. ```json { "variables": { "address": "123 Oak Street", "price": "$849,000", "bedrooms": "4", "agent_name": "Jordan Lee" } } ``` ## Step 2 — Reference variables in strings Any string field in the movie may include `{{name}}` placeholders that resolve at render time. Update the title card and the voice script: ```json { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "{{address}}" } } ``` ```json { "type": "voice", "text": "Welcome to {{address}} — a {{bedrooms}}-bedroom home, listed at {{price}}.", "voice": "en-US-EmmaMultilingualNeural", "start": 1.5 } ``` ```json { "type": "html", "tailwind": true, "wait": 0.5, "html": "
💰 {{price}}
", "position": "bottom-right", "x": -60, "y": -60, "start": 4, "duration": 12 } ``` ## Step 3 — Scene-level variables Variables can also live on a scene. Scene variables override movie variables for that scene only. This is useful when the same template needs different captions per scene. ```json { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "variables": { "room_name": "Master Bedroom" }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "{{room_name}}", "position": "top-left", "x": 60, "y": 60 } ] } ``` Resolution order: scene → movie. If the same name is defined in both, the scene wins inside that scene. ## Step 4 — Plug in a different property Change the property by editing one section, not the whole movie: ```json { "variables": { "address": "47 Cedar Avenue, Seattle, WA", "price": "$1,200,000", "bedrooms": "5", "agent_name": "Pat Morgan" } } ``` Re-submit the same movie — the title card, voice-over, subtitles, and price tag all update. ## The complete final JSON ```json { "resolution": "full-hd", "variables": { "address": "123 Oak Street", "price": "$849,000", "bedrooms": "4", "agent_name": "Jordan Lee" }, "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 }, { "type": "voice", "text": "Welcome to {{address}} — a {{bedrooms}}-bedroom home, listed at {{price}}.", "voice": "en-US-EmmaMultilingualNeural", "start": 1.5 }, { "type": "subtitles", "language": "en", "settings": { "style": "boxed-word", "font-family": "Inter", "font-size": 90, "word-color": "#FFFFFF", "line-color": "#FFFFFF", "outline-color": "#000000", "position": "bottom-center", "all-caps": true, "box-color": "#0E7C66" } }, { "type": "html", "tailwind": true, "wait": 0.5, "html": "
💰 {{price}}
", "position": "bottom-right", "x": -60, "y": -60, "start": 4, "duration": 12 } ], "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "{{address}}" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "variables": { "room_name": "Exterior" }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "{{room_name}}", "position": "top-left", "x": 60, "y": 60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "variables": { "room_name": "Chef's Kitchen" }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "{{room_name}}", "position": "top-left", "x": 60, "y": 60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "variables": { "room_name": "Master Bedroom" }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "{{room_name}}", "position": "top-left", "x": 60, "y": 60 } ] } ] } ``` ## Expected output The chapter-8 listing, but every appearance of the address or price now resolves from variables. Swap the variables block to generate a video for a completely different property — same JSON, different output. Sample render: [tutorial-10.mp4](https://cdn.json2video.com/samples/tutorial-10.mp4) (placeholder). ## What you learned - `variables` is a flat object of `key: value` pairs at movie or scene level. - Reference a variable inside any string with `{{name}}`. - Scene variables shadow movie variables inside that scene only. - Variable names starting with `?`, `$`, or `@` are silently dropped; any other non-alphanumeric character is replaced with an underscore. ## Previous chapter / Next chapter [← 8. Automatic subtitles](@/tutorials/08-automatic-subtitles) · [11. Templates →](@/tutorials/11-templates) # 11. Templates --- chapter: 11 title: Templates throughline: real-estate property listings requires_chapter: 10-variables last_reviewed: 2026-05-12 --- # 11. Templates Variables let one JSON serve many properties. Templates let your application avoid sending that JSON at all — store the movie once in your Dashboard, then trigger a render with `{ "template": "", "variables": { ... } }`. This chapter walks through saving the chapter-10 movie as a template and calling it from the API. **Prerequisites:** [chapter 10](@/tutorials/10-variables). The template body is the chapter-10 movie with placeholders. ## Step 1 — Save the movie as a template Two ways: 1. **Dashboard.** Paste the chapter-10 movie JSON into the [visual editor](@/guides/dashboard/visual-editor) → click **Save as template** → give it a name (e.g. `real-estate-listing-v1`). 2. **API.** Submit `POST /v2/templates` with the body: ``` { "name": "real-estate-listing-v1", "movie": { ... the full chapter-10 JSON, minus the variables block ... } } ``` The response contains a `templateId` — a random 20-character alphanumeric string generated by the API, for example `LerKrmBfiqaIgBuacLWn`. **Keep this ID** — it's what you'll use to render the template. The `name` you provided is only a human-friendly label shown in the dashboard list; every API reference to the template (rendering, fetching, updating, deleting) uses the random ID, not the name. ## Step 2 — Call the template Once saved, the rendering payload shrinks. Submit `POST /v2/movies` with the template ID plus the variables for the specific property (replace `LerKrmBfiqaIgBuacLWn` with the ID returned for your own template): ```json { "template": "LerKrmBfiqaIgBuacLWn", "variables": { "address": "47 Cedar Avenue, Seattle, WA", "price": "$1,200,000", "bedrooms": "5", "agent_name": "Pat Morgan" } } ``` The server loads the template, merges the request's `variables` on top, and renders the result. Same output as chapter 10 — just less payload. ## Step 3 — Override the resolution / quality at call time A few movie-level fields are taken from the **request**, not the template: `resolution`, `width`, `height`, `quality`, `exports`, and `client-data`. This lets one template power vertical-9:16 and horizontal-16:9 outputs without duplicating the JSON. ```json { "template": "LerKrmBfiqaIgBuacLWn", "resolution": "instagram-story", "quality": "medium", "variables": { "address": "47 Cedar Avenue, Seattle, WA", "price": "$1,200,000", "bedrooms": "5" } } ``` ## Step 4 — Submit from your favourite SDK The call looks identical to chapter 7, only the body differs: :::tabs ::: curl ```bash curl -X POST https://api.json2video.com/v2/movies \ -H "x-api-key: $JSON2VIDEO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template": "LerKrmBfiqaIgBuacLWn", "variables": { "address": "47 Cedar Avenue, Seattle, WA", "price": "$1,200,000", "bedrooms": "5" } }' ``` ::: ::: node ```javascript await fetch("https://api.json2video.com/v2/movies", { method: "POST", headers: { "x-api-key": process.env.JSON2VIDEO_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ template: "LerKrmBfiqaIgBuacLWn", variables: { address: "47 Cedar Avenue, Seattle, WA", price: "$1,200,000", bedrooms: "5", }, }), }); ``` ::: ::: python ```python import os, requests requests.post( "https://api.json2video.com/v2/movies", headers={"x-api-key": os.environ["JSON2VIDEO_API_KEY"]}, json={ "template": "LerKrmBfiqaIgBuacLWn", "variables": { "address": "47 Cedar Avenue, Seattle, WA", "price": "$1,200,000", "bedrooms": "5", }, }, ) ``` ::: ::: php ```php "LerKrmBfiqaIgBuacLWn", "variables" => [ "address" => "47 Cedar Avenue, Seattle, WA", "price" => "$1,200,000", "bedrooms" => "5", ], ]); $ch = curl_init("https://api.json2video.com/v2/movies"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["x-api-key: " . getenv("JSON2VIDEO_API_KEY"), "Content-Type: application/json"], CURLOPT_POSTFIELDS => $body, ]); echo curl_exec($ch); ``` ::: ::: ## The complete final JSON A render request that uses the template — replace the body of `POST /v2/movies` with this (substituting your own template ID): ```json { "template": "LerKrmBfiqaIgBuacLWn", "resolution": "full-hd", "variables": { "address": "123 Oak Street", "price": "$849,000", "bedrooms": "4", "agent_name": "Jordan Lee" } } ``` ## Expected output Same 16-second video as chapter 10. The win is on the **client side**: your CRM only needs to remember the template ID plus the per-property variables. Sample render: [tutorial-11.mp4](https://cdn.json2video.com/samples/tutorial-11.mp4) (placeholder). ## What you learned - A template is the movie JSON stored on the server, identified by a random 20-character ID generated by the API (e.g. `LerKrmBfiqaIgBuacLWn`). The `name` you set is only a label for the dashboard list — every API call references the template by its ID. - `POST /v2/movies` accepts either a full `movie` body or a `template` + `variables` body. - Request-level fields (`resolution`, `quality`, `exports`, `client-data`) override the stored template. - Templates are managed via the Dashboard or the `/v2/templates` endpoint family. ## Going further For a deeper dive on the dashboard side, see [Guides → Dashboard → Templates](@/guides/dashboard/templates). ## Previous chapter / Next chapter [← 10. Variables](@/tutorials/10-variables) · [12. Expressions →](@/tutorials/12-expressions) # 12. Expressions --- chapter: 12 title: Expressions throughline: real-estate property listings requires_chapter: 11-templates last_reviewed: 2026-05-12 --- # 12. Expressions Variables resolve as raw strings. Expressions resolve as **computed** values: math, ternary conditions, string operations, and a handful of built-in functions. This chapter swaps the static price label for a dynamic `"Luxury Home"` / `"Family Home"` tag and computes scene durations from a base value. **Prerequisites:** [chapter 11](@/tutorials/11-templates). Expressions and variables share the `{{ ... }}` delimiter. ## Step 1 — Expression syntax An expression is any `{{ ... }}` block that contains an operator, a function call, or a literal that is not a bare variable name. Whitespace inside the braces is allowed and recommended. ``` {{ price_number * 1.2 }} {{ bedrooms >= 4 ? "Family Home" : "Cozy Home" }} {{ ceil(base_duration / 2) }} ``` The renderer detects the operator characters (`+ - * / ? :` and friends) and evaluates the expression. A bare `{{ address }}` stays a plain variable reference. ## Step 2 — A computed "Luxury / Family" tag The current price is stored as a number `price_number` for arithmetic. The visible string `price` is generated separately for the voice-over. ```json { "variables": { "address": "123 Oak Street", "price": "$849,000", "price_number": 849000, "bedrooms": 4 } } ``` Use a ternary expression to choose the tag: ```json { "type": "text", "text": "{{ price_number >= 1000000 ? 'Luxury Home' : 'Family Home' }}", "position": "top-right", "x": -60, "y": 60, "settings": { "font-family": "Inter", "font-size": "48px", "color": "#FFFFFF", "font-weight": "700" } } ``` > Note — single quotes are the recommended string delimiter inside expressions to avoid escaping JSON's double quotes. ## Step 3 — Compute scene durations Pull the scene duration from a base variable so changing one value changes the pacing of every room scene: ```json { "variables": { "base_duration": 4 } } ``` ```json { "duration": "{{ base_duration }}", "transition": { "style": "fade", "duration": "{{ base_duration / 8 }}" } } ``` A `base_duration` of 4 produces 4-second scenes with 0.5-second fades. Bump it to 6 and everything scales. ## Step 4 — Use a built-in function JSON2Video exposes a small library of math, string, and date functions. Common ones: `ceil`, `floor`, `round`, `min`, `max`, `length`, `upper`, `lower`, `concat`. Compute a budget cap that rounds up to the nearest whole second: ```json { "type": "voice", "text": "Welcome to {{ address }}. {{ price_number >= 1000000 ? 'Luxury' : 'Family' }} home, available now.", "voice": "en-US-EmmaMultilingualNeural", "start": "{{ ceil(base_duration / 3) }}" } ``` ## The complete final JSON ```json { "resolution": "full-hd", "variables": { "address": "123 Oak Street", "price": "$849,000", "price_number": 849000, "bedrooms": 4, "base_duration": 4 }, "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 }, { "type": "voice", "text": "Welcome to {{ address }}. {{ price_number >= 1000000 ? 'Luxury' : 'Family' }} home, available now.", "voice": "en-US-EmmaMultilingualNeural", "start": "{{ ceil(base_duration / 3) }}" }, { "type": "subtitles", "language": "en", "settings": { "style": "boxed-word", "font-family": "Inter", "font-size": 90, "word-color": "#FFFFFF", "line-color": "#FFFFFF", "position": "bottom-center", "all-caps": true, "box-color": "#0E7C66" } }, { "type": "html", "tailwind": true, "wait": 0.5, "html": "
💰 {{price}}
", "position": "bottom-right", "x": -60, "y": -60, "start": "{{ base_duration }}", "duration": "{{ base_duration * 3 }}" } ], "scenes": [ { "duration": "{{ base_duration }}", "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "{{address}}" } }, { "type": "text", "text": "{{ price_number >= 1000000 ? 'Luxury Home' : 'Family Home' }}", "position": "top-right", "x": -60, "y": 60, "settings": { "font-family": "Inter", "font-size": "48px", "color": "#FFFFFF", "font-weight": "700" } } ] }, { "duration": "{{ base_duration }}", "transition": { "style": "fade", "duration": "{{ base_duration / 8 }}" }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "type": "text", "text": "Exterior", "position": "top-left", "x": 60, "y": 60 } ] }, { "duration": "{{ base_duration }}", "transition": { "style": "fade", "duration": "{{ base_duration / 8 }}" }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "type": "text", "text": "Chef's Kitchen", "position": "top-left", "x": 60, "y": 60 } ] }, { "duration": "{{ base_duration }}", "transition": { "style": "fade", "duration": "{{ base_duration / 8 }}" }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "type": "text", "text": "Master Bedroom", "position": "top-left", "x": 60, "y": 60 } ] } ] } ``` ## Expected output Same listing as chapter 11, plus: - A "Family Home" tag in the top-right of the title card (would say "Luxury Home" for a `price_number >= 1000000`). - Scene durations now derived from `base_duration`. Bump that variable to instantly retime the whole video. Sample render: [tutorial-12.mp4](https://cdn.json2video.com/samples/tutorial-12.mp4) (placeholder). ## What you learned - Expressions live inside `{{ ... }}` and are detected by the presence of operators or function calls. - Ternary syntax: `condition ? value_if_true : value_if_false`. - Built-in functions: `ceil`, `floor`, `round`, `min`, `max`, `length`, `upper`, `lower`, `concat`, plus arithmetic and string operators. - Use single quotes inside expressions to keep the surrounding JSON valid. ## Previous chapter / Next chapter [← 11. Templates](@/tutorials/11-templates) · [13. Dynamic scenes →](@/tutorials/13-dynamic-scenes) # 13. Dynamic scenes --- chapter: 13 title: Dynamic scenes throughline: real-estate property listings requires_chapter: 12-expressions source: api/endpoints/00_common/j2v-lib-evaluator.js last_reviewed: 2026-07-14 --- # 13. Dynamic scenes So far every listing has exactly three rooms. Real properties have variable counts — sometimes 2 rooms, sometimes 10. The `iterate` property on a scene tells JSON2Video to **repeat that scene** once per item in an array, spreading each item's fields into the scene's variables. This chapter renders a video with one scene per room, where the rooms come from a single array. **Prerequisites:** [chapter 12](@/tutorials/12-expressions). Iterate scenes use the same `{{ variable }}` syntax to read the current item. ## Step 1 — Define the rooms array Add a `rooms` variable at movie level. Each item is an object with whatever fields the iterating scene needs. ```json { "variables": { "address": "123 Oak Street", "rooms": [ { "name": "Exterior", "image": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "name": "Chef's Kitchen", "image": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "name": "Master Bedroom", "image": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "name": "Living Room", "image": "https://cdn.json2video.com/assets/images/sample-house-living.jpg" }, { "name": "Backyard", "image": "https://cdn.json2video.com/assets/images/sample-house-backyard.jpg" } ] } } ``` ## Step 2 — A single iterating scene A scene with `iterate: "rooms"` (where `"rooms"` is a variable name pointing at an array) is expanded into N scenes — one per array item. Inside the scene, the current item's fields are available **directly** as variables: an item like `{ "name": ..., "image": ... }` is read with `{{ name }}` and `{{ image }}`. Three automatic variables are also set on every copy: `iteration` (1-based counter), `first_iteration` and `last_iteration` (booleans). There is no `iterate-as` property and no `item` scope — adding `iterate-as` makes the render fail with `Property 'iterate-as' is not allowed`. ```json { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "iterate": "rooms", "elements": [ { "type": "image", "src": "{{ image }}" }, { "type": "text", "text": "{{ name }}", "position": "top-left", "x": 60, "y": 60 } ] } ``` If `rooms` has 5 items, this single definition expands to 5 scenes, each rendering its own image and label. ## Step 3 — Keep the title card scene as-is The opening title card has no iteration — it appears once. Place it before the iterating scene; the iterating scene comes second. ```json { "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "{{address}}" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "iterate": "rooms", "elements": [ { "type": "image", "src": "{{ image }}" }, { "type": "text", "text": "{{ name }}", "position": "top-left", "x": 60, "y": 60 } ] } ] } ``` The final movie is title card + N room scenes. ## Step 4 — Voice-over that names each room The chapter-9 voice line was static. Move the voice element into the iterating scene so each room scene gets its own short narration. Now we have one voice line per room. ```json { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "iterate": "rooms", "elements": [ { "type": "image", "src": "{{ image }}" }, { "type": "text", "text": "{{ name }}", "position": "top-left", "x": 60, "y": 60 }, { "type": "voice", "text": "Step inside {{ name }}.", "voice": "en-US-EmmaMultilingualNeural" } ] } ``` > Note — scene-level voice elements use the **scene-local** timeline. With no `start`, they begin at scene start. ## The complete final JSON ```json { "resolution": "full-hd", "variables": { "address": "123 Oak Street", "rooms": [ { "name": "Exterior", "image": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "name": "Chef's Kitchen", "image": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "name": "Master Bedroom", "image": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" }, { "name": "Living Room", "image": "https://cdn.json2video.com/assets/images/sample-house-living.jpg" }, { "name": "Backyard", "image": "https://cdn.json2video.com/assets/images/sample-house-backyard.jpg" } ] }, "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 }, { "type": "subtitles", "language": "en", "settings": { "style": "boxed-word", "font-family": "Inter", "font-size": 90, "word-color": "#FFFFFF", "line-color": "#FFFFFF", "position": "bottom-center", "all-caps": true, "box-color": "#0E7C66" } } ], "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "{{address}}" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "iterate": "rooms", "elements": [ { "type": "image", "src": "{{ image }}" }, { "type": "text", "text": "{{ name }}", "position": "top-left", "x": 60, "y": 60 }, { "type": "voice", "text": "Step inside {{ name }}.", "voice": "en-US-EmmaMultilingualNeural" } ] } ] } ``` ## Expected output A 24-second listing: title card (4 s) + five room scenes (4 s each), each with its own narration. To render a property with three rooms instead of five, change `rooms.length` — the movie automatically resizes. Sample render: [tutorial-13.mp4](https://cdn.json2video.com/samples/tutorial-13.mp4) (placeholder). ## What you learned - `iterate: ""` repeats a scene once per item in that array. - The item's keys are spread directly into the scene's variables — there is no `iterate-as` property and no `item` scope. - Inside the iterating scene, `{{ field }}` reads the current item's fields; `{{ iteration }}`, `{{ first_iteration }}` and `{{ last_iteration }}` are set automatically on each copy. - The number of scenes becomes data-driven without re-writing the movie. ## Going further Iteration combined with templates (chapter 11) is the killer pattern for bulk video rendering: store the template once, drive 1000 properties through the same `POST /v2/movies` with different `rooms` arrays. ## Previous chapter / Next chapter [← 12. Expressions](@/tutorials/12-expressions) · [14. Conditions →](@/tutorials/14-conditions) # 14. Conditions --- chapter: 14 title: Conditions throughline: real-estate property listings requires_chapter: 13-dynamic-scenes last_reviewed: 2026-05-12 --- # 14. Conditions Some content only makes sense when a flag is true. We do not want the "Open House This Sunday" badge to show up on every listing — only the ones with an open house this weekend. The `condition` property on any element or scene controls whether it renders. Drop it on, set the flag, and the rest of the JSON stays intact. **Prerequisites:** [chapter 13](@/tutorials/13-dynamic-scenes). Conditions evaluate expressions, so review chapter 12 if anything looks unfamiliar. ## Step 1 — A conditional element `condition` accepts an expression. The element renders when the expression evaluates truthy. ```json { "type": "html", "tailwind": true, "wait": 0.5, "html": "
OPEN HOUSE — SUNDAY 1–4 PM
", "position": "top-center", "y": 60, "condition": "{{ open_house }}" } ``` If `open_house` is `true`, `"true"`, or any truthy value, the badge renders. If it's `false`, missing, or an empty string, the element is skipped entirely — as if it were never in the JSON. ## Step 2 — Add the flag at variables-level Declare `open_house` so the condition has something to read: ``` { "variables": { "address": "123 Oak Street", "open_house": true, "rooms": [ ... as in chapter 13 ... ] } } ``` Flip to `false` to suppress the badge without touching the rest of the movie. ## Step 3 — Conditional voice line Layer in a voice-over that runs only when there is an open house. Use a movie-level voice element with `condition`: ```json { "type": "voice", "text": "Open house this Sunday from one to four PM.", "voice": "en-US-EmmaMultilingualNeural", "start": 18, "condition": "{{ open_house }}" } ``` `start: 18` places it near the end of the iterating-room scenes (chapter 13 totals ~24 s). ## Step 4 — Conditional scene `condition` also works on a whole scene. If `open_house` is true, add a dedicated closing scene with a wider "schedule a viewing" message; otherwise, skip it. ```json { "duration": 3, "transition": { "style": "fade", "duration": 0.5 }, "condition": "{{ open_house }}", "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "OPEN HOUSE", "subline": "Sunday 1–4 PM" } } ] } ``` ## Step 5 — Compose more complex conditions `condition` is a regular expression, so you can combine clauses: ```json { "condition": "{{ open_house && price_number < 1000000 }}" } { "condition": "{{ bedrooms >= 3 }}" } { "condition": "{{ rooms.length > 0 }}" } ``` ## The complete final JSON ```json { "resolution": "full-hd", "variables": { "address": "123 Oak Street", "open_house": true, "rooms": [ { "name": "Exterior", "image": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "name": "Chef's Kitchen", "image": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "name": "Master Bedroom", "image": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" } ] }, "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 }, { "type": "html", "tailwind": true, "wait": 0.5, "html": "
OPEN HOUSE — SUNDAY 1–4 PM
", "position": "top-center", "y": 60, "condition": "{{ open_house }}" }, { "type": "voice", "text": "Open house this Sunday from one to four PM.", "voice": "en-US-EmmaMultilingualNeural", "start": 14, "condition": "{{ open_house }}" }, { "type": "subtitles", "language": "en", "settings": { "style": "boxed-word", "font-family": "Inter", "font-size": 90, "word-color": "#FFFFFF", "line-color": "#FFFFFF", "position": "bottom-center", "all-caps": true, "box-color": "#0E7C66" } } ], "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "{{address}}" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "iterate": "rooms", "elements": [ { "type": "image", "src": "{{ image }}" }, { "type": "text", "text": "{{ name }}", "position": "top-left", "x": 60, "y": 60 } ] }, { "duration": 3, "transition": { "style": "fade", "duration": 0.5 }, "condition": "{{ open_house }}", "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "OPEN HOUSE", "subline": "Sunday 1–4 PM" } } ] } ] } ``` ## Expected output With `open_house: true`: the listing now opens with a red badge across the top of every scene, plus a closing "Open House Sunday 1–4 PM" card. With `open_house: false`: the same JSON renders the chapter-13 listing exactly as before — no badge, no closing card, no extra voice line. Sample render: [tutorial-14.mp4](https://cdn.json2video.com/samples/tutorial-14.mp4) (placeholder). ## What you learned - `condition` on an element or a scene controls whether it renders. - The expression is evaluated like any other `{{ ... }}` — booleans, comparisons, function calls all work. - A falsy condition (false, missing, empty string, zero) skips the element/scene entirely. - One template can switch large blocks of behaviour on or off by toggling a single variable. ## Previous chapter / Next chapter [← 13. Dynamic scenes](@/tutorials/13-dynamic-scenes) · [15. Webhooks →](@/tutorials/15-webhooks) # 15. Webhooks --- chapter: 15 title: Webhooks throughline: real-estate property listings requires_chapter: 14-conditions last_reviewed: 2026-05-12 --- # 15. Webhooks Polling `GET /v2/movies?project=…` is fine for scripts. Production apps prefer to be **notified** when a render finishes. This chapter adds a webhook destination to the listing — when the video is ready, JSON2Video POSTs a JSON payload to your URL with the result. **Prerequisites:** [chapter 14](@/tutorials/14-conditions). You should have a public HTTPS endpoint that can receive a POST (use [webhook.site](https://webhook.site) for quick testing). ## Step 1 — Add a webhook destination Webhook destinations live in the top-level `exports` array. Each export item has a `destinations` array; each destination has a `type` and the fields that type requires. ```json { "exports": [ { "destinations": [ { "type": "webhook", "endpoint": "https://your-app.example/json2video-callback" } ] } ] } ``` The renderer will POST to `endpoint` once when the movie finishes (success or failure). ## Step 2 — Include some correlation metadata When your endpoint receives the callback it needs to know which property / listing the video belongs to. The cleanest way is `client-data` — an arbitrary object echoed back verbatim in the callback. ```json { "client-data": { "listing_id": "L-4821", "property_address": "123 Oak Street", "agent_id": "AG-42" } } ``` Drop that at movie level. Whatever you put here, you'll receive in the webhook payload. ## Step 3 — What the webhook receives JSON2Video POSTs a JSON body when the render is done. Shape: ```json { "project": "abc123", "status": "done", "url": "https://assets.json2video.com/clients/.../abc123.mp4", "duration": 24, "size": 5421988, "client-data": { "listing_id": "L-4821", "property_address": "123 Oak Street", "agent_id": "AG-42" } } ``` On failure `status` is `"error"` and a `message` field replaces `url`. See [Webhooks reference](@/reference/webhooks) for the canonical payload contract. ## Step 4 — A minimal receiver A receiver verifies the payload and triggers your downstream logic — push to a CRM, send an email, update the listing record. :::tabs ::: node ```javascript import express from "express"; const app = express(); app.use(express.json()); app.post("/json2video-callback", (req, res) => { const { project, status, url, "client-data": cd } = req.body; if (status === "done") { console.log(`Listing ${cd.listing_id} video ready: ${url}`); // updateCRM(cd.listing_id, url); } else { console.error(`Listing ${cd.listing_id} failed: ${req.body.message}`); } res.status(200).send("ok"); }); app.listen(3000); ``` ::: ::: python ```python from flask import Flask, request app = Flask(__name__) @app.post("/json2video-callback") def callback(): body = request.get_json() cd = body.get("client-data", {}) if body.get("status") == "done": print(f"Listing {cd.get('listing_id')} ready: {body['url']}") else: print(f"Listing {cd.get('listing_id')} failed: {body.get('message')}") return "ok", 200 ``` ::: ::: php ```php # 16. Optimization & cost --- chapter: 16 title: Optimization & cost throughline: real-estate property listings requires_chapter: 15-webhooks last_reviewed: 2026-05-12 --- # 16. Optimization & cost Your CRM is now triggering renders automatically (chapter 15) — possibly hundreds per day. Each render consumes credits and takes wall-clock time. This final chapter covers four levers that bring down both: `cache`, scene granularity for parallel rendering, `quality`, and asset reuse. **Prerequisites:** [chapter 15](@/tutorials/15-webhooks). Optimization assumes you understand the rest of the pipeline. ## Lever 1 — Caching Every renderable thing in JSON2Video is cached by default. The cache key is "everything that affects the output": the JSON of the element, its sources, its settings. If you submit a movie with the same JSON twice, the second render is served from cache — essentially free and instant. To opt **out** for a specific element, set `cache: false`. Useful when: - A source URL has the same path but changing contents (e.g. a price tag served by your CMS). - You want to force a fresh voice render after tweaking voice settings. ```json { "type": "voice", "text": "Welcome to {{ address }}.", "voice": "en-US-EmmaMultilingualNeural", "cache": false } ``` In production, **leave caching on**. Each generated voiceover in the listing costs credits the first time — every subsequent render of the same listing is free. ## Lever 2 — Split into more scenes JSON2Video renders **scenes in parallel**. The total render time is roughly `max(scene durations)`, not the sum. Two effects: - Splitting one 30-second scene into 6×5-second scenes is significantly faster. - A monolithic scene with 30 elements is the worst case — it cannot parallelise. In our listing, the chapter-13 `iterate` pattern already produces N scenes — one per room. That's optimal. If you have a long "stats roll" with many beats, break it into one scene per beat. ## Lever 3 — Tune `quality` The movie-level `quality` field controls render fidelity: | Value | Use for | |---|---| | `"low"` | Internal previews, internal QA | | `"medium"` | Social previews, draft reviews | | `"high"` | Final delivery (default) | `low` renders ~3× faster than `high` and costs fewer credits. A typical workflow: render `low` while drafting, then re-render `high` once approved. ```json { "quality": "high" } ``` ## Lever 4 — Reuse heavy assets across renders The most expensive operations are voiceover synthesis and heavy remote downloads. Two strategies: **4a. Hoist heavy assets to `preload`.** The `preload` array generates or fetches an asset once and exposes its URL as `{{id_url}}`, ready for every element that references it. The generation happens before the main render and is cached. ```json { "preload": [ { "id": "intro", "type": "voice", "text": "Welcome to today's listing.", "voice": "en-US-EmmaMultilingualNeural" } ], "scenes": [ { "elements": [ { "type": "audio", "src": "{{intro_url}}" } ] } ] } ``` **4b. Upload the asset once via `/v2/media`.** If you want full control, upload your reusable assets to your own [Media library](@/reference/api-endpoints/media-list) and reference them by URL. No re-generation ever. ## The complete final JSON ```json { "resolution": "full-hd", "quality": "high", "cache": true, "client-data": { "listing_id": "L-4821" }, "exports": [ { "destinations": [ { "type": "webhook", "endpoint": "https://your-app.example/json2video-callback" } ] } ], "preload": [ { "id": "intro", "type": "voice", "text": "Welcome to {{ address }}.", "voice": "en-US-EmmaMultilingualNeural" } ], "variables": { "address": "123 Oak Street", "rooms": [ { "name": "Exterior", "image": "https://cdn.json2video.com/assets/images/sample-house-front.jpg" }, { "name": "Chef's Kitchen", "image": "https://cdn.json2video.com/assets/images/sample-house-kitchen.jpg" }, { "name": "Master Bedroom", "image": "https://cdn.json2video.com/assets/images/sample-house-bedroom.jpg" } ] }, "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-corporate.mp3", "volume": 0.4 }, { "type": "voice", "text": "Welcome to {{ address }}.", "voice": "en-US-EmmaMultilingualNeural", "start": 1.5 }, { "type": "subtitles", "language": "en", "settings": { "style": "boxed-word", "font-family": "Inter", "font-size": 90, "word-color": "#FFFFFF", "line-color": "#FFFFFF", "position": "bottom-center", "all-caps": true, "box-color": "#0E7C66" } } ], "scenes": [ { "duration": 4, "elements": [ { "type": "component", "component": "basic/000", "settings": { "headline": "FOR SALE", "subline": "{{address}}" } } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "iterate": "rooms", "elements": [ { "type": "image", "src": "{{ image }}" }, { "type": "text", "text": "{{ name }}", "position": "top-left", "x": 60, "y": 60 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 0.5 }, "elements": [ { "type": "html", "tailwind": true, "html": "
Open House Sunday
" } ] } ] } ``` ## Expected output Functionally the same listing as chapter 15 — but renders faster on first hit (parallel scenes), free on repeat hits (cache), with the voiceover hoisted into `preload` so it generates exactly once and is reused everywhere. Sample render: [tutorial-16.mp4](https://cdn.json2video.com/samples/tutorial-16.mp4) (placeholder). ## What you learned - `cache: true` (default) makes repeat renders of the same JSON essentially free. - Splitting work into more scenes lets the renderer parallelise — total time tracks the slowest scene, not the sum. - `quality` is a three-step dial; use `low` for drafts and `high` for delivery. - Hoist expensive generated assets into `preload` so they are produced once and reused across every element. ## You finished the tutorial You now have a production-grade real-estate listing pipeline: data-driven, conditional, narrated, captioned, cached, parallel, and webhook-delivered. Where to go next: - **[Reference](@/reference)** — every field and every endpoint. - **[Guides](@/guides)** — task-oriented walkthroughs (dashboards, no-code, advanced patterns). - **[For coding agents](@/help-for-ai-agents)** — set up an MCP server or hand these docs to your coding agent. ## Previous chapter [← 15. Webhooks](@/tutorials/15-webhooks) # Guides # Guides This page is being prepared. In the meantime, see [the overview](../) for an introduction to this section. # Dashboard # Dashboard The JSON2Video dashboard at [json2video.com/dashboard](https://json2video.com/dashboard) is the web UI for managing your account: rendering videos visually, saving reusable templates, configuring third-party connections, managing media assets, rotating API keys, and tracking billing. This section explains each dashboard surface and how to use it day-to-day. - [Visual editor](@/guides/dashboard/visual-editor) — build a movie JSON with drag-and-drop. - [Templates](@/guides/dashboard/templates) — save and reuse movie JSONs. - [Connections](@/guides/dashboard/connections) — store credentials for ElevenLabs, FTP, Replicate, etc. - [Media](@/guides/dashboard/media) — your CDN-backed asset library. - [API keys](@/guides/dashboard/api-keys) — project keys, roles, rotation. - [Billing](@/guides/dashboard/billing) — plans, credits, top-ups, usage history, invoices. # Visual editor # Visual editor The JSON2Video dashboard ships with a visual editor that lets you build a movie JSON without writing JSON by hand. It is the recommended starting point for non-developers and a useful prototyping tool for developers who want to scaffold a movie before exporting it to code. The editor lives at [json2video.com/dashboard/videos](https://json2video.com/dashboard/videos). > TODO: capture screenshot of the editor at /dashboard/videos/edit/new. ## Opening the editor 1. Sign in at [json2video.com/dashboard](https://json2video.com/dashboard). 2. Click **Videos** in the left sidebar. 3. Click **Add new video**. The editor opens on a blank canvas at `/dashboard/videos/edit/new`. To edit an existing video, click any row in the list. The editor URL is `/dashboard/videos/edit/`. ## The editor layout The visual editor splits into three regions: - **Canvas** (centre): live preview of the current scene at the configured resolution. - **Timeline** (bottom): one row per scene; each scene has stacked element bars showing duration and order. - **Properties panel** (right): the form for whatever you've selected — movie, scene, or element. Its **Variables** tab declares the movie's variables, and any property can be switched to an expression (the `fx` toggle on the row) to drive it from one, e.g. `{{ headline }}`. ## Adding scenes Click the **+** button at the end of the timeline to add a scene. Each scene is rendered sequentially in the final video. You can: - Drag scenes left/right to reorder. - Click a scene tab to select it; the canvas updates to show it. - Duplicate a scene from the scene context menu. - Set per-scene properties (duration, transition, background) in the right panel. ## Adding elements With a scene selected, click **+ Add element** in the right panel. Choose from: - **Image** — static image with optional Ken Burns motion. - **Video** — video clip with optional chroma key, trim, volume. - **Text** — styled text using one of the predefined styles. - **Component** — pre-built animated card from the [component catalog](https://json2video.com/components/). - **HTML** — custom HTML/CSS snippet (advanced). - **Audio** — music or sound effect. - **Voice** — text-to-speech voiceover. - **Audiogram** — animated waveform synced to the scene audio. - **Subtitles** — auto-generated subtitles from the scene voice / audio. Each element appears as a bar in the scene's timeline. Drag the bar to reposition in time; drag its edges to resize the duration. ## Drag and drop - **Move** an element on the canvas: click and drag. - **Resize** an element on the canvas: drag a corner handle. - **Reorder** elements in z-order: drag the timeline bars vertically. - **Reorder** scenes: drag scene tabs horizontally. The properties panel updates in real time as you drag. ## Repeating a scene A scene can be repeated once per item of an array variable, so one authored scene becomes many rendered ones — a product card per product, a room per room. Select the scene and open the **Repeat (iterate)** group in the properties panel: 1. Pick the array variable to repeat over. The picker lists only the movie's array variables and shows how many items each holds; use **Enter a path…** to type a dotted path such as `product.images`. 2. The panel confirms how many copies the scene will render, and warns inline when it will render none — an empty array, or an array of plain values rather than objects, silently removes the scene from the movie. 3. Optionally narrow the range with **From** / **To** / **Step**. `From` is inclusive and `To` is exclusive, both 1-based; negative values count from the end of the array. These fields appear only once the scene repeats. Each copy gets the item's own fields as local variables — reference them as `{{ name }}` exactly like movie-level variables — plus `{{ iteration }}`, `{{ first_iteration }}` and `{{ last_iteration }}`. The timeline shows the copies grouped under the authored scene, and the outline marks the scene with a repeat badge. If the movie has no array variable yet, the picker offers to create one and takes you to the **Variables** panel, where its rows are edited. See [scene reference](@/reference/json-syntax/scene) for the underlying `iterate` properties. ## Preview Click **Play** on the timeline to preview the entire movie inline at a reduced resolution. The preview uses a client-side renderer that gets close to but is not identical to the final server render — for the authoritative result, render and download. ## Saving and rendering The editor auto-saves your work as a draft. To trigger a server render: 1. Click **Render** in the top right. 2. The dashboard switches to a render-progress view showing percentage and a thumbnail as soon as one is available. 3. When the render finishes, the result appears in your video list at [json2video.com/dashboard/videos](https://json2video.com/dashboard/videos). You can also click **Save as template** to store the current JSON as a [template](@/guides/dashboard/templates) for later reuse. ## Export to JSON To take the JSON out of the editor and into code: 1. Click the **JSON** toggle in the top toolbar. 2. The editor switches to a code view with the full movie JSON. 3. Copy and paste into your codebase, or call the API with this body. This makes the visual editor a fast scaffolding tool — design visually, then export and parameterise in code. ## Limitations - The visual editor previews a simplified version of the timeline. TTS voice timing and some advanced motion (HTML elements, complex chroma keying) are only fully accurate in the server render. - `iterate` is editable on scenes only. To repeat an individual element, edit the JSON directly. - `condition` is shown on a scene but never evaluated in the editor: a conditioned scene stays visible and editable, and the preview decides whether it renders. ## See also - [Dashboard — Templates](@/guides/dashboard/templates) - [Tutorials](@/tutorials/01-your-first-video) - [Movie JSON reference](@/reference/json-syntax/movie) # Templates # Templates Templates let you save a movie JSON in your JSON2Video account and reuse it later — replacing only the parts that change per render (text, image URLs, prices, …). They are the fastest way to scale automated video rendering: design once, render thousands. Templates live at [json2video.com/dashboard/templates](https://json2video.com/dashboard/templates). > TODO: capture screenshot of the templates list and the template editor. ## When to use a template vs inline JSON | Use case | Recommended | |----------|-------------| | One-off render | Inline JSON | | Same video structure, varying content per render | **Template** | | Same video used by your team across multiple workflows (Make.com, internal app, Slack bot) | **Template** | | Templates shared with other JSON2Video users | **Template** (import by ID) | | Truly different videos every time (different scene counts, element types) | Inline JSON | ## Creating a template 1. Open [json2video.com/dashboard/templates](https://json2video.com/dashboard/templates). 2. Click **Add new template**. The template editor opens. 3. Give the template a **name** and an optional set of **tags** (categories for filtering in the list view). 4. Build the JSON in one of two modes: - **Visual** — drag-and-drop, same UI as the [visual editor](@/guides/dashboard/visual-editor). - **JSON** — raw editor with syntax highlighting and validation. 5. Use `{{variable_name}}` syntax in any string field to mark replaceable values. 6. Click **Save**. The new template appears in the list with a unique ID. Click the row menu → **Copy template ID** to grab it. ## The template ID Every template has a stable ID like `LerKrmBfiqaIgBuacLWn`. You'll reference this ID from: - Your own code via the API. - Make.com's *Create a Movie from my Template* module. - n8n's HTTP Request node pointed at the template endpoint. - Anyone you share the template with — they can import it into their own account. ## Rendering from a template The API endpoint to render a template is `POST /v2/movies`, with the template ID and variables: ```bash curl -X POST https://api.json2video.com/v2/movies \ -H "x-api-key: YOUR_API_KEY" \ -H "content-type: application/json" \ -d '{ "template": "LerKrmBfiqaIgBuacLWn", "variables": { "name": "Ana", "price": "$49" } }' ``` The engine loads the saved template, substitutes the variables, and renders. The response shape is the same as a regular render — you get a `project` ID and poll for status. You can also override individual properties at render time by passing a `movie` object alongside `template`; the override is deep-merged into the saved template. ## Editing JSON vs Visual The visual editor is the same one used for one-off videos — see [Visual editor](@/guides/dashboard/visual-editor) for the full walkthrough. The JSON editor offers: - Syntax highlighting and JSON validation. - Direct access to features the visual editor doesn't expose (e.g. `iterate`, `if`, complex expressions). - Side-by-side preview when you click **Preview**. Switch modes at any time using the toggle in the toolbar. The two views share state — changes made in one show up in the other. ## Naming and tagging Good names and tags pay off as your template library grows. Conventions we've seen work well: - **By workflow**: `intro-card`, `social-reel-vertical`, `product-launch-3min`. - **By customer / segment**: `acme-monthly-update`, `enterprise-quarterly-summary`. - **By status**: `prod-`, `draft-`, `experiment-`. Tags filter the list at the top of the templates page. Click a tag to scope the list. ## Sharing a template Two ways to share: 1. **Send the template ID** to anyone — they can import it into their account from the **Import template** button on the templates page. 2. **Send the JSON** — copy the JSON view to a Gist/file. The recipient pastes it into a new template. There is currently no per-template ACL — shared templates are independent copies in each account, not live-linked. ## Versions and rollback Each save creates a new version. The dashboard keeps the last N versions; you can roll back to any prior version from the template's history menu. For mission-critical production templates, treat the template like code: keep a copy of the JSON in your own git repository as a backup. ## See also - [Dashboard — Visual editor](@/guides/dashboard/visual-editor) - [Tutorial 11 — Templates](@/tutorials/11-templates) - [API endpoints — templates](@/reference/api-endpoints/templates-list) - [Tutorial 10 — Variables](@/tutorials/10-variables) # Connections # Connections A **Connection** is a saved set of credentials that JSON2Video can use to integrate with an external service — your ElevenLabs account, an FTP server, an Azure subscription. Instead of putting credentials in every API call, you save them once in the dashboard and reference them by ID in your movie JSON. Connections live at [json2video.com/dashboard/connections](https://json2video.com/dashboard/connections). > TODO: capture screenshot of the connections panel with the "Add new connection" dialog open. ## Why use Connections - **Security**: credentials never appear in API requests, logs, or your codebase. Sensitive fields are encrypted at rest. - **Reuse**: one connection serves any number of renders. - **Rotation**: rotate a key once in the dashboard; every workflow using the connection picks up the new key on the next render. - **Auditing**: see exactly what credentials your account has stored. ## Connection types Currently supported: - **ElevenLabs** — bring your own ElevenLabs API key for voice generation. - **Azure** — Azure Speech subscription credentials. - **FTP / SFTP** — host, port, username, password for export destinations. - **Webhook** — saved webhook endpoint with optional auth header. - **Email** — pre-configured email destination (sender + recipient). The list expands over time — check the dashboard for the current set. ## Creating a connection (ElevenLabs example) 1. Open [json2video.com/dashboard/connections](https://json2video.com/dashboard/connections). 2. Click **Add new connection**. 3. Choose **ElevenLabs** from the type dropdown. 4. Give it an **ID** — this is what you'll reference in your JSON (e.g. `my-elevenlabs`). Use lower-case, no spaces. 5. Paste your ElevenLabs API key in the **API key** field. 6. Click **Save**. The connection is encrypted and stored. The list view now shows your new connection. Click the row to view non-sensitive fields; the API key is masked. ## Using a connection in your movie JSON Reference the connection ID on the relevant element: ```json { "type": "voice", "model": "elevenlabs", "connection": "my-elevenlabs", "voice": "Adam", "text": "Hello!" } ``` For export destinations, reference the connection on the destination object: ```json { "exports": [{ "destinations": [{ "id": "my-sftp", "file": "render-__yyyy__-__mm__-__dd__.mp4" }] }] } ``` When the engine sees `connection` or destination `id`, it loads the credentials from the encrypted store at render time. Your API request body never contains the actual credentials. ## Overriding connection fields You can override any single field of a connection inline. The engine merges your override on top of the saved connection: ```json { "exports": [{ "destinations": [{ "id": "my-sftp", "remote-path": "/customer/acme/__yyyy__/" }] }] } ``` This uses `my-sftp`'s host, port, username, and password, but writes to a customer-specific subdirectory. Most fields can be overridden; the `type` field cannot. ## Best practices - **One connection per logical service**. Don't share an FTP connection between *production* and *staging*; create two. - **Use descriptive IDs**. `my-elevenlabs-prod` beats `conn1`. - **Rotate keys regularly**. If a Connection is compromised, delete it from the dashboard; every workflow using it fails fast. - **Don't put connection IDs in public templates**. If you share a template, the recipient will need to create their own connection with the same ID — or you'll need to instruct them to update the JSON. ## Editing and deleting From the connections list, click the row menu (`⋮`): - **Edit** — change non-sensitive fields and re-enter sensitive ones if you want to rotate. - **Delete** — removes the connection. Workflows referencing the ID will fail until you recreate it or update the JSON. ## See also - [ElevenLabs integration](@/guides/third-party/elevenlabs) - [FTP / SFTP delivery](@/guides/production/ftp-sftp) - [Webhooks (production)](@/guides/production/webhooks-advanced) - [Voice element reference](@/reference/json-syntax/element/voice) # Media # Media The Media panel is your account's CDN-backed asset library. Upload images, videos, and audio once; reference them by URL in any movie JSON. JSON2Video hosts them at a stable URL and serves them with the same edge-cached infrastructure used for rendered videos. Media lives at [json2video.com/dashboard/media](https://json2video.com/dashboard/media). > TODO: capture screenshot of the media panel in grid view with a folder expanded. ## When to use the Media panel - You have static brand assets (logos, intros, outros, background music) used across many videos. - Your source images live on a server with rate limits or that may go down — putting them on the JSON2Video CDN avoids transient fetch errors mid-render. - You don't want to host assets yourself or set up your own S3 bucket. If your assets are already on a public CDN (S3, Cloudflare, your own server), you can reference them directly by URL in the movie JSON. The Media panel is an option, not a requirement. ## Uploading 1. Open [json2video.com/dashboard/media](https://json2video.com/dashboard/media). 2. Either click **Upload** or drag-and-drop files into the panel. Both work. 3. Wait for the upload progress bar to finish. Each file gets a unique URL. Supported types: - **Images**: `png`, `jpg`, `jpeg`, `gif`, `bmp`, `tiff`, `webp`. - **Videos**: `mp4`, `mov`, `webm`, and any container with the codecs listed in the [errors reference](@/reference/errors). - **Audio**: `mp3`, `wav`, `flac`, `aac`, `opus`, `ogg`. Total storage and per-file size limits depend on your plan — see [credits & limits](@/reference/credits). ## Browsing The media panel has two views, toggleable in the top-right: - **Grid** — thumbnail tiles. Best for visual browsing. - **List** — table with name, size, type, and modified date. Best for finding by name. Sort by **Date** or **Name** with the sort dropdown. ## Organising with folders Click **New folder** to create a folder. Folders can be nested. Use folders to: - Separate clients (`/customer-a`, `/customer-b`). - Group by campaign (`/black-friday-2026`). - Split by asset type if you prefer (`/logos`, `/music`). To move files into a folder: select them (checkbox on grid view, row click on list view) and drag onto the target folder, or use the row menu → **Move to folder**. ## Copying URLs to use in API calls Each uploaded file gets a stable URL on the JSON2Video CDN: ``` https://cdn.json2video.com/clients/// ``` To copy the URL: 1. Click a file in the panel — the preview opens. 2. Click the **Copy URL** button. 3. Paste the URL as the `src` of an `image`, `video`, or `audio` element in your movie JSON. ```json { "type": "image", "src": "https://cdn.json2video.com/clients/abc123/logos/brand-mark.png" } ``` ## Previewing Click any file to preview: - **Images** — full-resolution preview. - **Videos** — inline player. - **Audio** — inline player with waveform. The preview view also shows metadata: dimensions, duration (for video/audio), codec, file size. ## Deleting files - **Single file**: row menu → **Delete**. - **Bulk**: select multiple files, then **Delete selected** in the action bar. Deleted files are removed immediately. Any movie JSON still referencing the URL will fail to render with a 404 on that element. The dashboard does not check for in-use references before delete — be careful with production assets. ## Retention Media files have no expiry date, unlike rendered videos (deleted after 7 days) and cached generated assets (3 days). They are kept as long as your account holds credits: if the balance stays at zero for 30 days, media files and rendered videos are permanently deleted. Topping up at any point during those 30 days cancels the deletion. See [File retention](@/reference/file-retention) for the full picture. ## Programmatic access The same files are accessible through the [media API](@/reference/api-endpoints/media-list): - `GET /v2/media` — list files. - `POST /v2/media` — upload a file. - `PUT /v2/media` — move / rename. - `DELETE /v2/media` — delete. This is useful for automating uploads from your own backend (e.g. push a new logo from your CI pipeline). ## Persisted generated assets The Drive can also hold assets produced by the JSON2Video AI / rendering pipeline — not just files you uploaded. Any generable element (`image`, `video`, `audio`, `voice`, `html`, `component`) can be persisted to the Drive by adding `save-to-media: true` to the source item, in `POST /v2/preloads`, in `movie.preload[]`, or inside `scene.elements[]`. See the [Save generated assets to Media](@/guides/advanced/save-to-media) guide for the full opt-in flow. Persisted generated assets land in a reserved `generated/` folder, sub-grouped by element type: ``` /generated/image/... /generated/video/... /generated/audio/... /generated/voice/... /generated/html/... /generated/component/... ``` The filename is derived from the source item's `id` (when it matches `^[a-zA-Z0-9_-]+$`) or from the first 12 characters of the asset hash. The extension comes from the real codec returned by the generator — a Flux model that produces WebP yields `.webp`, an MP3 voice yields `.mp3`. Once in the Drive, persisted generated assets behave exactly like uploaded files: same URLs, same management UI, same quota counters, same API endpoints. You can move them out of `/generated/` if you prefer a different layout — new persistence calls always land in `/generated/{type}/` first. ### Deletion Deleting a persisted generated asset from the Drive also clears the engine's internal backreference to it. The next time you preload the same prompt with `save-to-media: true`, a fresh file is created. ### Quota Persisted generated assets count against your Media storage quota exactly like uploaded files. If the Drive is blocked (over quota, billing hold), a save-to-media request degrades gracefully: the asset is still generated, the response carries `persistent: false` and `warning: "quota_blocked"`, and the URL points to the 3-day cache instead. The request never fails because of quota. ## See also - [Image element reference](@/reference/json-syntax/element/image) - [Video element reference](@/reference/json-syntax/element/video) - [Audio element reference](@/reference/json-syntax/element/audio) - [API endpoints — media](@/reference/api-endpoints/media-list) # API keys --- source: api/endpoints/apikeys/index.mjs last_reviewed: 2026-07-14 --- # API keys API keys are the credentials your code uses to call the JSON2Video API. Every request must include the key in the `x-api-key` header. The dashboard at [json2video.com/dashboard/apikeys](https://json2video.com/dashboard/apikeys) is where you create, manage, and rotate them. > TODO: capture screenshot of the API keys panel showing the list of project keys with their roles. ## Project API keys JSON2Video uses a single, role-based model: **Project API keys**. - A first project key is generated automatically when you sign up and emailed to you so you can start calling the API immediately. - You can create as many additional project keys as you need. - Each key has a configurable **role** that limits what it can do. - Each key can have an optional expiration date. - Use a dedicated key per environment (production, staging, development) and per third-party integration (Make.com, Zapier, n8n). > The previous *Primary* / *Secondary* split is deprecated: new keys are always project keys with a role. An existing Primary key still works — the dashboard lists it flagged with a warning — but you should migrate to project keys. ## Permission roles Every project key is assigned one of three roles: | Role | Can do | |------|--------| | **Render** | Render videos only. Cannot create / edit templates. Cannot manage connections. Best for production render workers and read-only integrations. | | **Editor** | Render + create / edit / manage templates. Use for Make.com / n8n flows that need to create templates. | | **Manager** | Editor + manage Connections. Use for tools that need to set up integrations on your behalf. | When you create a third-party integration, **start with the minimum role** (usually `Render`) and only escalate if the integration actually needs more. If a key is leaked, the blast radius is bounded by its role. ## Creating a project key 1. Open [json2video.com/dashboard/apikeys](https://json2video.com/dashboard/apikeys). 2. Click **Add new API key**. 3. Give it a **name** (e.g. *"Make.com — production"*). 4. Choose a **role**: `Render`, `Editor`, or `Manager`. 5. Optionally set an **expiration date**. 6. Click **Save**. The new key appears once in a dialog — copy it immediately. After you close the dialog, the dashboard only shows a masked preview. If you lose a key, delete it and create a new one. ## Using a key Send the key in the `x-api-key` header on every API request: ```bash curl -X POST https://api.json2video.com/v2/movies \ -H "x-api-key: YOUR_API_KEY" \ -H "content-type: application/json" \ -d '{ "resolution": "full-hd", "scenes": [ /* ... */ ] }' ``` Code samples for every SDK are in the [Quickstart](@/getting-started/quickstart). ## Rotating a key 1. Create a new project key with the same role. 2. Update the consuming service (Make.com, n8n, your code) with the new key. 3. Verify the service still works. 4. Delete the old key from the dashboard. This pattern avoids downtime — both keys are valid during the transition window. ## Security best practices - **Never commit keys to git**. Use environment variables (`JSON2VIDEO_API_KEY`) and a `.env` file ignored by git. - **Never paste keys into public templates, Slack channels, or GitHub issues**. Treat them like passwords. - **One key per environment**. Production, staging, and development should each have their own key with the minimum role. - **One key per third-party integration**. If Make.com is compromised, you should be able to revoke its key without touching anything else. - **Set expiration dates** on keys for time-bound automations. - **Rotate keys on team changes**. If someone with access to the dashboard leaves, rotate any keys they could have copied. ## Revocation Delete a key from the dashboard to immediately revoke it. Any in-flight render started with that key continues — revocation only affects new requests. ## See also - [Getting started — Quickstart](@/getting-started/quickstart) - [API endpoints reference](@/reference/api-endpoints) - [Errors reference](@/reference/errors) - [Make.com end-to-end](@/guides/no-code/makecom/end-to-end) # Billing --- source: api/endpoints/account/index.js last_reviewed: 2026-08-07 --- # Billing The dashboard's billing surfaces split across four pages: - [json2video.com/dashboard/account](https://json2video.com/dashboard/account) — profile, current plan, billing email. - [json2video.com/dashboard/credits](https://json2video.com/dashboard/credits) — credit balance, top-ups, usage history. - [json2video.com/dashboard/credits/billing](https://json2video.com/dashboard/credits/billing) — billing history: every past payment, with invoice downloads. - [json2video.com/dashboard/account/plans](https://json2video.com/dashboard/account/plans) — change plan, upgrade, downgrade. This guide explains the model and how to operate it day-to-day. > TODO: capture screenshots of the credits panel and the plan selector. ## How JSON2Video bills JSON2Video uses a **credit-based** model. Every render consumes credits based on duration and any generated assets included (resolution does not change the rendering cost). You get credits in two ways: 1. **A monthly allowance** included with your subscription plan. Resets each billing cycle. 2. **One-time top-ups** for additional credits that don't expire (subject to plan). The cost of a render is deterministic: a 30-second full-HD video with no extra generated assets always costs the same number of credits. Generated voiceovers add to the cost — the exact per-model rates are in the [credit consumption table](@/reference/credits/credit-consumption). ## Viewing your balance The credits panel at [json2video.com/dashboard/credits](https://json2video.com/dashboard/credits) shows: - **Current balance** — credits available right now. - **Monthly allowance** — what your plan grants, and what's left of this month. - **Top-up balance** — credits from one-time purchases. - **Usage history** — a chronological list of renders with the credits each consumed. Click any row in the usage history to see the underlying movie's project ID, status, and credit breakdown. ## Plans Plans are tiered by: - **Included credits per month**. - **Max concurrent renders** — how many movies can render in parallel. - **Max video duration** — the upper bound on a single render. - **Max resolution** — `sd`, `hd`, `full-hd`, `squared`, `instagram-*`, etc. - **Storage** — total size your media library can hold. - **Support tier** — community, email, priority. See the live [plans page](https://json2video.com/pricing) for the current tiers and prices. ## Upgrading To upgrade or change plan: 1. Open [json2video.com/dashboard/account/plans](https://json2video.com/dashboard/account/plans). 2. The current plan is highlighted. Click any plan card to see its details and price. 3. Click the action button on the new plan. 4. You'll be redirected to the payment provider's checkout (PayPro for new subscriptions; Paddle remains only for legacy subscriptions). Complete the payment. 5. The new plan activates immediately. Unused days of your previous plan are pro-rated as a credit on the next invoice. ## Downgrading Downgrades take effect at the end of your current billing period — you keep the higher-tier features until then. To downgrade: 1. Open [json2video.com/dashboard/account/plans](https://json2video.com/dashboard/account/plans). 2. Click the lower-tier plan you want to move to. 3. Confirm the downgrade. The dashboard shows the effective date. You can cancel the downgrade any time before the effective date. ## Top-ups If you need more credits in the middle of a billing cycle: 1. Open [json2video.com/dashboard/credits](https://json2video.com/dashboard/credits). 2. Click **Buy more credits** (or **Top up**). 3. Choose a top-up pack and complete checkout. 4. The credits appear in your balance immediately and **do not expire** when the next billing cycle starts. Top-up credits are consumed only after your monthly allowance is exhausted. ## Free credits and trial New accounts get a small free credit allowance to try the API end-to-end without entering payment details. The free tier limits resolution and duration; see [credits & limits](@/reference/credits) for the exact constraints. ## Invoices and receipts JSON2Video does not issue invoices. The payment provider that processed the charge does — PayPro for new subscriptions, Paddle for legacy ones — because it is the merchant of record and owns the invoice, its tax lines and the billing details printed on it. Your payment history lives at [json2video.com/dashboard/credits/billing](https://json2video.com/dashboard/credits/billing) (**Credits → Billing history**). It lists every subscription renewal and one-time top-up back to your first payment, newest first, with: - **Date** — the day the charge was made. - **Description** — the plan for a subscription renewal, or the number of credits for a top-up. Multi-month and annual charges show the period they cover as a single entry, not one per month. - **Amount** — what was charged, in the currency it was charged in. - **Status** — **Paid**, or **Refunded** if the payment was refunded afterwards. - **Invoice** — a download button that opens the invoice hosted by the provider. Free credits and manually granted credits are not payments and don't appear here. The same invoice link is also in the receipt email the provider sends after each charge. ### Adding tax details or changing the card Editing an invoice — company name, billing address, VAT/tax ID — and updating the payment method happen at the provider, not in the dashboard. Both are reached from **Credits → Subscriptions**: - **Paddle** takes you straight to its hosted billing page, where you can correct the details and download a revised invoice. - **PayPro** emails you a one-time login link, valid for about 15 minutes, that opens your PayPro portal. If the link stops working, request a fresh one — old links expire rather than break. Your **billing email** is the exception: it's ours, not the provider's. Edit it in the account panel. ## Cancelling To cancel your subscription: 1. Open the **Account** panel at [json2video.com/dashboard/account](https://json2video.com/dashboard/account). 2. Scroll to the subscription section and click **Cancel subscription**. 3. The cancellation takes effect at the end of the current billing period. You keep access until then. > **Note**: deleting your JSON2Video account does **not** cancel an active subscription — in fact, while a subscription is active and not cancelled, the deletion request is blocked and you receive an email explaining why. Cancel the subscription first, then delete the account if you wish. ## Running out of credits mid-render If your account hits zero credits while a render is in progress, the in-flight render completes (you are not charged extra). Subsequent render requests fail with an `Insufficient credits` error until you top up or your monthly allowance resets. The errors reference documents the exact response shape: see [errors → quota and plan](@/reference/errors). ## See also - [Credits & limits reference](@/reference/credits) - [Credit consumption table](@/reference/credits/credit-consumption) - [Plans](@/reference/credits/plans) - [Pricing FAQ](@/reference/credits/faq) # No-code integrations # No-code integrations You don't need to write code to use JSON2Video. Any automation platform that can make an HTTP request to a JSON API can drive a render. The most popular options have detailed walkthroughs in this section. - [Make.com end-to-end](@/guides/no-code/makecom/end-to-end) — full walkthrough including the dedicated JSON2Video app. - [n8n end-to-end](@/guides/no-code/n8n/end-to-end) — HTTP-based pattern for self-hosted n8n. - [Generic HTTP](@/guides/no-code/generic-http) — primitives for Zapier, Pipedream, Power Automate, and anything else that speaks HTTP. For backend-driven workflows with code, start with the [Quickstart](@/getting-started/quickstart). # Make.com end-to-end # Make.com end-to-end [Make.com](https://www.make.com/) (formerly Integromat) is a visual automation platform. It is the most common way to integrate JSON2Video with other tools (Google Sheets, Airtable, Slack, YouTube, …) without writing code. This guide walks through a complete end-to-end scenario, from creating a connection to publishing a finished video. > TODO: capture screenshot of the Make.com scenario canvas with the JSON2Video module placed. ## 1. Prerequisites - A Make.com account (the free tier is enough to start). - A JSON2Video account with at least one API key. Sign in at [json2video.com/dashboard](https://json2video.com/dashboard). - A clear idea of what the video should look like — at minimum, a [Quickstart-style](@/getting-started/quickstart) JSON. ## 2. Create a dedicated API key for Make.com Open [json2video.com/dashboard/apikeys](https://json2video.com/dashboard/apikeys) and create an **API key** specifically for Make.com. Best-practice settings: - **Role**: `Render` if Make.com only needs to render videos, `Editor` if it also creates / updates templates. - **Expiration**: optional, but recommended for production keys. Use a dedicated key per integration rather than reusing the same key everywhere. If the Make.com side is ever compromised, you can revoke just this key without rotating everything else. ## 3. Create a JSON2Video Connection in Make.com 1. In a Make.com scenario, add any **JSON2Video** module. All modules require a Connection. 2. When prompted, click **Add** to create a new Connection. 3. Give it a name (e.g. *"JSON2Video — production"*). 4. Paste the API key from step 2. 5. Save. The Connection is now available across every scenario in your Make.com workspace. ## 4. Pick the right module JSON2Video's Make.com app exposes these modules: | Module | Use it for | |--------|------------| | **Create a Movie from JSON** | Full control: paste any movie JSON, render it. | | **Create a Movie from my Template** | Reuse a template stored in your JSON2Video account. Replace variables only. | | **Create a Movie from a Template ID** | Same as above, but for templates you've imported from someone else. | | **Create Slideshow with Audio** | One-click slideshow from an array of image URLs + a music track. | | **Add Automatic Subtitles to a Video** | Generate burned-in subtitles from an audio source. | | **Concat a List of Videos with Background Audio** | Stitch multiple clips into one with shared music. | | **Toolbox** | Sub-module: voiceover-to-image, merge audio + video, trim, quote card, social reel. | | **Wait for a Movie to Render** | Pause the scenario until the movie finishes. | | **Check a Movie Status** | Poll once for status; useful inside `If` branches. | For most workflows, the pattern is: 1. **Trigger** (e.g. *Google Sheets — Watch new rows*). 2. **Create a Movie** module (any of the create flavours). 3. **Wait for a Movie to Render** module. 4. **Downstream action** (upload to YouTube, post to Slack, email the link, store the URL in a CRM, …). ## 5. Submit a render The simplest scenario: a new row in a Google Sheet triggers a personalised video. 1. Add a *Google Sheets → Watch new rows* trigger pointed at your sheet. 2. Add the *JSON2Video — Create a Movie from JSON* module. 3. Map the input JSON to a body that uses Google Sheets columns as Make.com variables. Example: ```json { "comment": "Personalised intro for {{1.name}}", "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "text", "text": "Hello {{1.name}}!", "duration": 5, "style": "002" } ] } ] } ``` `{{1.name}}` is the Make.com variable from the trigger (module #1, column *name*). Make.com replaces it at runtime. 4. Save and run once with test data. ## 6. Wait for the render to finish Drag in the *Wait for a Movie to Render* module after the create-movie step. Map its **Project ID** input to the *project* field returned by the create step. This module pauses the scenario until the render is `done` (or `error`). On success, it returns the full `movie` object including `url`, `thumbnail`, and `duration`. For long-running renders (60s+), consider switching to a webhook-based pattern: have JSON2Video call back into Make.com using a *Webhooks → Custom webhook* trigger. See [Webhooks (production)](@/guides/production/webhooks-advanced) for the receiver side. ## 7. Use the rendered video downstream Once the *Wait* module returns, the rest of the scenario can act on the video. Typical patterns: - **Upload to YouTube** — *YouTube → Upload a video*, map the URL. - **Post to Slack** — *Slack → Create a message*, embed the URL. - **Update the original sheet** — *Google Sheets → Update a row*, store the URL in a "Video URL" column. - **Save to Google Drive** — *Google Drive → Upload a file from URL*. ## 8. Common pitfalls ### "Source URL is required for video element in Scene X, Element Y" You're using a Make.com variable for an `src` that didn't resolve. Check the variable mapping — if the upstream column is empty, the value reaches JSON2Video as an empty string. Add a filter in Make.com to skip rows with missing values, or set a fallback. ### Movie duration cannot be zero The total movie length must be at least 1 second. Causes: - An image element with no `duration` and no other timed element in the scene. - An empty `scenes` array. Fix by adding `"duration": N` on the scene or on an image element. ### "Source URL is not a [video/audio/image] file" The codec or container isn't supported. Allowed formats: - **Video**: `h264`, `hevc`, `mpeg4`, `wmv3`, `vp8`, `vp9`, `mpeg2video`, `av1`, `theora` - **Audio**: `aac`, `flac`, `mp3`, `opus`, `vorbis`, plus video containers with audio - **Image**: `png`, `jpg`, `jpeg`, `gif`, `mjpeg`, `bmp`, `tiff`, `webp` ### Make.com variable serialization Make.com sends string values. If you map a variable into a JSON property that expects a number or a boolean, wrap it appropriately in Make.com (use the `parseNumber()` and `parseJSON()` functions). For nested JSON inputs, use Make.com's `parseJSON()` to convert a stringified payload. ### API key issues Double-check the Connection's API key has the right role (`Render` at minimum, `Editor` if the scenario creates templates). Re-paste from the dashboard if in doubt — copy/paste sometimes drops trailing characters. ### Webhook receiver If you set up a webhook destination in your movie JSON, the receiver URL must be publicly hosted over HTTPS with a valid TLS certificate. Make.com's own *Custom webhook* trigger works as a receiver. ### Large batches A scenario that fires a large batch at once gains nothing — the renders queue and run at the same pace either way. Add a Make.com *Sleep* or *Iterator* with a short delay between renders to keep the scenario readable and your request volume low. What a big batch does consume is [credits](@/reference/credits/credit-consumption); see [Rate limits & quotas](@/reference/credits/limits). ## Debugging tips - **Execution history**: Make.com keeps a per-run log of every module's input and output. Inspect it to see the exact JSON sent to JSON2Video and the response received. - **Test with the API directly**: copy the exact JSON Make.com is sending and POST it with `curl` to confirm whether the issue is in the JSON or in the Make.com mapping. - **Sub-module errors**: when *Wait for a Movie to Render* returns an error, the `message` field on its output shows the JSON2Video error message verbatim. - **Reference the [errors catalog](@/reference/errors)** for known error messages. ## See also - [n8n end-to-end](@/guides/no-code/n8n/end-to-end) - [Generic HTTP integration](@/guides/no-code/generic-http) - [Dashboard — API keys](@/guides/dashboard/api-keys) - [Quickstart](@/getting-started/quickstart) # n8n end-to-end # n8n end-to-end [n8n](https://n8n.io/) is a fair-code workflow automation tool. It is the most common self-hostable alternative to Make.com / Zapier and pairs well with JSON2Video for production-grade pipelines you want to run on your own infrastructure. JSON2Video publishes an **official n8n node**, `n8n-nodes-json2video`, built and maintained by our team. It is a **verified community node**, so it is available on n8n Cloud and self-hosted alike. It gives you 22 operations across movies, templates and Drive storage, with the template's variables rendered as real input fields — no hand-built HTTP requests, no JSON string wrangling. If you are on an old self-hosted version, or your instance has community nodes disabled, skip to [Appendix: the HTTP Request fallback](#appendix-the-http-request-fallback). ## 1. Prerequisites - An n8n instance (self-hosted, n8n Cloud, or local). - A JSON2Video account with an API key. Get one at [json2video.com/dashboard/apikeys](https://json2video.com/dashboard/apikeys). - Familiarity with n8n's expression editor (`{{ }}` syntax) and credentials. ## 2. Install the node **n8n Cloud:** search for **JSON2Video** in the nodes panel and drag it onto the canvas. Verified nodes install without leaving the editor. **Self-hosted:** 1. Go to **Settings → Community Nodes**. 2. Select **Install**. 3. Enter `n8n-nodes-json2video`. 4. Agree to the risks and select **Install**. The package is published from GitHub Actions with an npm provenance attestation, so the tarball you install is cryptographically traceable to the public source commit it was built from — this is what n8n's verification process requires. ## 3. Create the credential 1. In n8n, add a **JSON2Video** node to a workflow. 2. Under **Credential to connect with**, select **Create new credential**. 3. Paste your API key and save. Best practice is a dedicated key per integration rather than reusing one key everywhere — if the n8n side is ever compromised you revoke just this key. The **Render** role is enough to render movies and use the Drive; **Editor** is needed to create, update or delete templates. See [API keys](@/guides/dashboard/api-keys) for what each role can do. The key is sent as the `x-api-key` header on every request, stored encrypted by n8n, and never echoed back in error messages. ## 4. Pick the right operation | Resource | Operation | Use it for | |---|---|---| | **Movie** | Create | Submit a render and return immediately with a project ID | | | Render and Wait | Submit a render and poll until it finishes, then return the video URL | | | Get Status | Check one movie by project ID | | | Get Many | List the account's renders within a date range | | | Delete | Delete a rendered file before its 7-day expiry | | **Template** | Get Many / Get Library | List your templates, or the public gallery | | | Get Variables | Discover a template's `{{placeholder}}` inputs at runtime | | | Create / Update / Duplicate / Delete | Manage templates from a workflow | | **Storage** | Upload File | Push binary data from a previous node and get a public URL | | | List Folder / Get File / Move / Delete | Manage the JSON2Video Drive | | | Get Storage Usage | Bytes used, free allowance, upload-blocked flag | The full list of all 22 operations is in the [node's README](https://github.com/JSON2Video/n8n-nodes-json2video#operations). For most workflows the pattern is: 1. **Trigger** (e.g. *Google Sheets — Watch new rows*). 2. **JSON2Video → Movie → Render and Wait**. 3. **Downstream action** (upload to YouTube, post to Slack, store the URL). ## 5. Render a template The common case is a saved template whose placeholders come from workflow data. 1. Add a **JSON2Video** node, resource **Movie**, operation **Render and Wait**. 2. Set **Source** to *Template*. 3. Pick the template from the **Template** dropdown — it lists the templates in your account. 4. The **Variables** section then fills in with that template's own variables, one labelled field each. Type a literal value, or drop in an expression: ``` Headline {{ $json.property_title }} Image URL {{ $json.photo_url }} Price {{ $json.price }} ``` Because the fields are generated from the template itself, renaming a variable in the template surfaces here rather than silently rendering an empty video. To build the movie JSON directly instead, set **Source** to *Movie JSON* and paste any [movie document](@/reference/json-syntax). Both sources accept overrides for resolution, quality, frame rate, cache, client data and the webhook destination. ## 6. Wait for the render A render typically takes 10–90 seconds. Two options: ### Option A: Render and Wait **Movie → Render and Wait** polls for you and emits one item when the render reaches a terminal state. Configure the poll timeout to sit comfortably under your n8n **workflow timeout** — if the workflow times out first, the render still completes on our side, but the workflow loses the result. This is the simplest correct choice for renders that finish in a couple of minutes. ### Option B: Webhook callback For long renders, or to avoid holding an execution open: 1. Use **Movie → Create** and set the **Webhook URL** field to an n8n Webhook trigger URL. 2. In n8n, create a second workflow starting with a **Webhook** trigger node. n8n shows you the public URL to paste in step 1. 3. The trigger fires with the full movie object as the body when the render finishes. Webhook-based flows scale better and don't burn n8n executions on polling. See [Webhooks](@/reference/webhooks). ## 7. Use the rendered video With **Simplify** on (the default), the node drops the response envelope and emits the movie object as the item itself: - `{{ $json.url }}` — direct MP4 URL on the JSON2Video CDN. `null` until the render is `done`, and `null` again once the file expires after 7 days. - `{{ $json.duration }}` — duration in seconds. - `{{ $json.size }}` — file size in bytes. - `{{ $json.width }}` / `{{ $json.height }}` — output dimensions in pixels. - `{{ $json.rendering_time }}` — how long the render took, in seconds. - `{{ $json.status }}` — `pending` | `running` | `done` | `error` | `timeout`. - `{{ $json.message }}` — the failure reason when `status` is `error`. - `{{ $json.project }}` — the 16-character project ID. - `{{ $json.ass }}` — the subtitles file, when the movie generated one. Turn **Simplify** off to get the raw envelope, where the same fields sit under `movie` and the response also carries `remaining_quota`. Connect downstream nodes: - **YouTube** → upload the rendered MP4. - **Slack / Discord** → notify a channel with the URL. - **HTTP Request** → send the URL to your own backend. - **Postgres / MySQL** → store the URL in your database. Rendered files are deleted 7 days after the render. Copy the MP4 to your own storage if you need it longer — see [File retention](@/reference/file-retention). ## 8. Error handling In production, wire up an error branch: - A render that fails comes back with `status: "error"` and a human-readable `message` — surface that string, it is the one that tells you what went wrong. - `status: "timeout"` means the render went 15 minutes without a heartbeat. - Network failures should retry with exponential backoff (n8n has a built-in *Retry on Fail* option). See [Error handling](@/guides/production/error-handling) for the full pattern. ## 9. Use the node as an AI Agent tool The node is marked `usableAsTool`, so an n8n **AI Agent** can call it directly. Pair **Template → Get Variables** with **Movie → Render and Wait** and the agent can discover a template's inputs at runtime and fill them itself, instead of having the variable names hard-coded in the prompt. ## Appendix: the HTTP Request fallback If community nodes are disabled on your instance, you can still call the REST API directly with the built-in **HTTP Request** node. **Store the API key as a credential:** open **Credentials → New**, choose **Header Auth**, set **Name** = `x-api-key` and **Value** = your API key. **Submit a render:** | Field | Value | |-------|-------| | Method | `POST` | | URL | `https://api.json2video.com/v2/movies` | | Authentication | *Generic Credential Type → Header Auth → JSON2Video API key* | | Send Body | enabled, Body Content Type: *JSON*, Specify Body: *Using JSON* | | Body | the movie JSON | A minimal body that produces a 5-second video: ```json { "comment": "Generated by n8n", "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "text", "text": "Hello from n8n!", "duration": 5, "style": "002" } ] } ] } ``` The response carries the project ID: ```json { "success": true, "project": "WAEE8PohgVwv2teP", "timestamp": "2025-05-28T14:57:34.393Z" } ``` **Poll for the result:** 1. Add a **Wait** node (e.g. 5 seconds). 2. Add a second **HTTP Request** node, method `GET`, URL `https://api.json2video.com/v2/movies?project={{ $node["Create movie"].json.project }}`, same credential. 3. Add an **IF** node on `{{ $json.movie.status }}`: `done` → continue, `error` or `timeout` → error handler, anything else → loop back to the Wait node. Cap the maximum number of loop iterations so a stuck render can't loop forever. On this path the movie fields are nested under `movie` — `{{ $json.movie.url }}`, `{{ $json.movie.duration }}`, and so on. ## See also - [n8n-nodes-json2video on npm](https://www.npmjs.com/package/n8n-nodes-json2video) - [Make.com end-to-end](@/guides/no-code/makecom/end-to-end) - [Generic HTTP integration](@/guides/no-code/generic-http) - [Webhooks (production)](@/guides/production/webhooks-advanced) - [JSON Syntax reference](@/reference/json-syntax) # Generic HTTP # Generic HTTP integration Any automation tool that can make an authenticated HTTP request can call JSON2Video. This includes [Zapier](https://zapier.com/), [Pipedream](https://pipedream.com/), [Activepieces](https://www.activepieces.com/), [Power Automate](https://make.powerautomate.com/), or your own custom backend. The recipe is always the same: `POST /v2/movies` to start a render, then `GET /v2/movies?project=...` to check status. This guide gives you the cURL primitives — translate them to your tool's HTTP action. ## Authentication Every request needs an `x-api-key` header containing your API key. Get one from the dashboard at [json2video.com/dashboard/apikeys](https://json2video.com/dashboard/apikeys). For any third-party tool, create a **dedicated API key** with the **Render** role so you can revoke it independently if the tool is compromised. ## Submit a render ```bash curl -X POST https://api.json2video.com/v2/movies \ -H "x-api-key: YOUR_API_KEY" \ -H "content-type: application/json" \ -d '{ "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "text", "text": "Hello from HTTP!", "duration": 5 } ] } ] }' ``` Response: ```json { "success": true, "project": "WAEE8PohgVwv2teP", "timestamp": "2025-05-28T14:57:34.393Z" } ``` Save the `project` value — you'll need it to check status. ## Poll for status ```bash curl https://api.json2video.com/v2/movies?project=WAEE8PohgVwv2teP \ -H "x-api-key: YOUR_API_KEY" ``` Response (in progress): ```json { "success": true, "movie": { "project": "WAEE8PohgVwv2teP", "status": "running", "progress": 42 } } ``` Response (finished): ```json { "success": true, "movie": { "project": "WAEE8PohgVwv2teP", "status": "done", "url": "https://assets.json2video.com/clients/.../movie.mp4", "thumbnail": "https://assets.json2video.com/clients/.../movie.jpg", "duration": 12.5 } } ``` Poll every 5-10 seconds until `status` is one of: `done`, `error`, `timeout`. ## Tool-specific notes ### Zapier - Use the *Webhooks by Zapier → Custom Request* action with method `POST`, the JSON above, and the `x-api-key` header. - Zapier's free tier executions a step every 15 minutes, so polling-based flows can be slow. Use a webhook destination on the render request and a *Webhooks by Zapier → Catch Hook* trigger to react instantly. ### Pipedream - Use the *HTTP / Webhook → Custom Request* action. - Pipedream supports JavaScript code steps — drop in a polling loop with `setTimeout` between requests. ### Power Automate - Use the *HTTP* action (premium connector). Set the URI, method, headers, and body inline. - Use the *Until* control to poll until `body('GET_status').movie.status` is `done`. ### Bubble / Webflow / other low-code - Use the platform's HTTP integration (usually called *API Connector* or similar). - Configure two endpoints: one POST (create movie), one GET (status). Reuse the API key as a shared header. ## Use a webhook to avoid polling For backends and tools that can receive HTTP, configure a webhook destination on the render request so JSON2Video calls you when the video is ready: ```json { "resolution": "full-hd", "scenes": [ /* ... */ ], "exports": [{ "destinations": [{ "type": "webhook", "endpoint": "https://your-app.example.com/api/json2video-done" }] }] } ``` Your endpoint receives a POST with the full movie object as the body when the render finishes. See [Webhooks (production)](@/guides/production/webhooks-advanced) for the full receiver pattern. ## See also - [Quickstart](@/getting-started/quickstart) — the same flow with code in Node / Python / PHP / cURL. - [Make.com end-to-end](@/guides/no-code/makecom/end-to-end) — full Make.com walkthrough. - [n8n end-to-end](@/guides/no-code/n8n/end-to-end) — full n8n walkthrough. - [API endpoints reference](@/reference/api-endpoints) # Third-party integrations # Third-party integrations JSON2Video integrates with external providers for text-to-speech voiceovers. You can use them through JSON2Video's account (credits billed from your JSON2Video balance) or bring your own provider account via a Connection. - [ElevenLabs](@/guides/third-party/elevenlabs) — high-quality text-to-speech voiceovers. - [Azure](@/guides/third-party/azure) — Microsoft's text-to-speech service. For storing third-party API keys, see [Dashboard — Connections](@/guides/dashboard/connections). # ElevenLabs # ElevenLabs [ElevenLabs](https://elevenlabs.io/) provides one of the highest-quality text-to-speech engines available. JSON2Video integrates with ElevenLabs out of the box so you can add realistic voiceovers to videos with a single element. You can use ElevenLabs in two modes: - **Through JSON2Video** (default): credits come from your JSON2Video balance. No ElevenLabs account required. - **Bring your own key**: connect your ElevenLabs account and pay ElevenLabs directly. Useful for high-volume use cases or for accessing custom cloned voices. ## Basic usage Add a `voice` element with `model: "elevenlabs"`: ```json { "type": "voice", "model": "elevenlabs", "voice": "Adam", "text": "Hello, I am an ElevenLabs generated voiceover." } ``` The `voice` field accepts either a voice name (`"Adam"`, `"Rachel"`) or an ElevenLabs voice ID. ## The Flash v2.5 model For shorter latency at the same quality tier, use `elevenlabs-flash-v2-5`: ```json { "type": "voice", "model": "elevenlabs-flash-v2-5", "voice": "Adam", "text": "This is a Flash v2.5 voiceover." } ``` Credit consumption is the same as the standard ElevenLabs model. ## Bring your own ElevenLabs API key If you have an ElevenLabs subscription with custom voices or need higher rate limits, connect your account: ### Step 1 — Get an ElevenLabs API key 1. Sign in at [elevenlabs.io](https://elevenlabs.io/). 2. Open your profile menu → **API Keys**. 3. Create a new key and copy it. ### Step 2 — Create a JSON2Video Connection 1. Go to [json2video.com/dashboard/connections](https://json2video.com/dashboard/connections). 2. Click **Add new connection**. 3. Choose **ElevenLabs** as the type. 4. Paste the API key. 5. Give the connection an ID you'll remember (e.g. `my-elevenlabs`) and save. ### Step 3 — Reference the connection in your movie JSON ```json { "type": "voice", "model": "elevenlabs", "connection": "my-elevenlabs", "voice": "p16ZaTyG1Ks9FQ9LpSun", "text": "Hello, world!" } ``` When `connection` is set, JSON2Video calls ElevenLabs with your API key. You pay ElevenLabs for the generation; JSON2Video charges a small handling fee for the orchestration. See the [credits reference](@/reference/credits) for current rates. ## Picking a voice ElevenLabs offers two voice categories: - **Pre-built voices** — available to everyone. Names like *Adam, Rachel, Domi, Bella, Antoni, …* - **Cloned / custom voices** — created in your ElevenLabs account and only accessible with your own API key (via a Connection). For pre-built voices, see the [ElevenLabs voices by language](https://json2video.com/ai-voices/elevenlabs/languages/) catalog — it lists every voice JSON2Video supports, with its name, voice ID and an audio sample. For custom voices, find the voice ID in your ElevenLabs dashboard. ## Customising voice settings Pass any ElevenLabs API parameter through `model-settings`: ```json { "type": "voice", "model": "elevenlabs", "voice": "Adam", "text": "Hello, world!", "model-settings": { "language_code": "en", "voice_settings": { "stability": 0.75, "similarity_boost": 0.85, "style": 0.2, "speed": 1.0 } } } ``` Common parameters: - `voice_settings.speed` — playback speed, `0.7` to `1.2` (default `1.0`). - `voice_settings.stability` — `0.0` to `1.0`. Higher = more consistent but less expressive. - `voice_settings.similarity_boost` — `0.0` to `1.0`. Higher = closer to the source voice. - `voice_settings.style` — `0.0` to `1.0`. Higher = more emotional / dramatic. - `language_code` — ISO 639-1 code (`en`, `es`, `fr`, …) to force a specific language. See [ElevenLabs API docs](https://elevenlabs.io/docs/api-reference/text-to-speech/convert#request) for the full list. ## Cost Without a Connection, ElevenLabs usage consumes credits from your JSON2Video balance — see the [credit consumption table](@/reference/credits/credit-consumption). With a Connection, you pay ElevenLabs directly for the audio generation and JSON2Video charges a much smaller orchestration fee per voice element. ## See also - [Voice element reference](@/reference/json-syntax/element/voice) - [Dashboard — Connections](@/guides/dashboard/connections) - [Tutorial 7 — Text-to-speech voiceover](@/tutorials/07-ai-voiceover) - [Credits & limits](@/reference/credits) # Azure # Azure [Azure Speech](https://azure.microsoft.com/products/ai-services/ai-speech) is Microsoft's text-to-speech service. JSON2Video integrates with Azure for high-quality voiceovers, particularly when you need: - Languages and neural voices not covered by ElevenLabs. - Enterprise / compliance requirements that mandate Azure as the TTS provider. - SSML support for fine-grained pronunciation, breaks, and prosody control. ## How it appears in JSON2Video Reference Azure on a `voice` element by setting `model` to `azure`: ```json { "type": "voice", "model": "azure", "voice": "en-US-JennyNeural", "text": "Hello, this is an Azure-generated voiceover." } ``` The `voice` field expects an Azure voice short name. See the [Azure voices by language](https://json2video.com/ai-voices/azure/languages/) for the complete list. ## Bring your own Azure subscription To use your own Azure subscription (and pay Microsoft directly): 1. In the Azure portal, create or pick a *Speech* resource. Note the region and the API key. 2. Create a Connection at [json2video.com/dashboard/connections](https://json2video.com/dashboard/connections) of type *Azure*, paste the key, and set the region. 3. Reference the Connection on the voice element: ```json { "type": "voice", "model": "azure", "connection": "my-azure", "voice": "en-US-JennyNeural", "text": "Hello, world!" } ``` ## SSML support Azure voices accept SSML input for tags like ``, ``, ``. Pass SSML via the `text` field. You do not need to write the `` wrapper or the `` element — the engine adds them, using the voice from the `voice` field. Both of these work: ```json { "type": "voice", "model": "azure", "voice": "en-US-JennyNeural", "text": "A short pause then more text." } ``` ```json { "type": "voice", "model": "azure", "voice": "en-US-JennyNeural", "text": "Slightly slower narration." } ``` If you do supply a `` wrapper, its attributes are replaced with the ones Azure requires and only the markup inside it is kept. Supply a `` element yourself only when you want to override the `voice` field. ## Cost JSON2Video-billed usage is documented in the [credit consumption table](@/reference/credits/credit-consumption). With a Connection, Azure bills your subscription per the [Speech service pricing](https://azure.microsoft.com/pricing/details/cognitive-services/speech-services/). ## See also - [Voice element reference](@/reference/json-syntax/element/voice) - [Dashboard — Connections](@/guides/dashboard/connections) - [Tutorial 7 — Text-to-speech voiceover](@/tutorials/07-ai-voiceover) - [ElevenLabs integration](@/guides/third-party/elevenlabs) # Advanced video patterns # Advanced video patterns Task-oriented guides for the more advanced parts of the video engine. - [Audiograms](@/guides/advanced/audiograms) — animated waveforms synced to audio. - [Chroma key](@/guides/advanced/chroma-key) — green-screen compositing. - [HTML & screenshot rendering](@/guides/advanced/html-rendering) — render any HTML/CSS/JS inside your video. - [Components deep-dive](@/guides/advanced/components) — pre-built animated templates from the catalog. - [Caching deep-dive](@/guides/advanced/caching-deep-dive) — how the engine reuses work to save time and credits. - [Save generated assets to Media](@/guides/advanced/save-to-media) — keep AI-generated assets in the Drive instead of the 3-day cache. - [Use public templates](@/guides/advanced/public-templates) — render from the public template library, or duplicate a template into your account. For the conceptual basics (movie / scene / element, duration, layering, positioning) see [Core concepts](@/getting-started/core-concepts) and the [JSON Syntax reference](@/reference/json-syntax). # Audiograms # Audiograms An audiogram is a video that visualises an audio file as an animated waveform on top of a static or animated background. It is the standard format used to share podcast snippets, voice clips, and other audio-first content on platforms that require video. JSON2Video has a first-class `audiogram` element that renders a waveform synchronised to any audio source. You can combine it with images, text, and subtitles to build a complete share-ready video. ## Minimum audiogram The smallest possible audiogram needs an audio source and an audiogram element: ```json { "resolution": "instagram-portrait", "scenes": [ { "elements": [ { "type": "audio", "src": "https://example.com/clip.mp3" }, { "type": "audiogram", "color": "white", "amplitude": 5, "y": 800 } ] } ] } ``` The audiogram element does not own an audio source — it visualises whatever audio is playing in the scene. Add one `audio` element (file, podcast clip…) or one `voice` element (text-to-speech) and the audiogram tracks it automatically. ## Common pattern: podcast share clip A typical share clip combines: - A vertical 1080×1920 (9:16) or square 1080×1080 frame. - A still cover image (album art, episode artwork). - The host name or episode title as text. - The audiogram element overlaid on top. - Automatic subtitles for accessibility. ```json { "resolution": "instagram-portrait", "scenes": [ { "elements": [ { "type": "image", "src": "https://example.com/cover.jpg", "zoom": 1 }, { "type": "audio", "src": "https://example.com/episode-clip.mp3" }, { "type": "text", "text": "Episode 42 — Building in public", "y": 100, "style": "002", "settings": { "font-size": "70px", "color": "white" } }, { "type": "audiogram", "color": "#FF6B00", "amplitude": 6, "y": 1500, "height": 200 }, { "type": "subtitles", "settings": { "style": "classic", "position": "bottom-center" } } ] } ] } ``` ## Customising the look The audiogram element accepts: - `color` — hex or named colour for the bars. - `amplitude` — how much the bars react to the audio (default ~5). - `height` — visual height of the waveform. - `x`, `y` — position (same coordinate system as every other element). - `width` — horizontal width of the waveform. For the full property list, see the [audiogram element reference](@/reference/json-syntax/element/audiogram). ## See also - [Audiogram example with full JSON](@/guides/examples/audiogram) - [Automatic subtitles example](@/guides/examples/automatic-subtitles) - [Audio element reference](@/reference/json-syntax/element/audio) # Chroma key # Chroma key Chroma keying — also called "green screen" — removes a uniform background colour from a video so you can composite the foreground over another scene. JSON2Video supports chroma keying directly on `video` elements. ## When to use chroma key - An on-camera presenter shot against a green or blue background. - Talking-head clips that come with a removable backdrop. - Stock footage of objects or VFX shot for compositing. If the source video already has an alpha channel (e.g. WebM with transparency, or ProRes 4444), you do not need chroma key — just stack it as a regular `video` element. ## Configuring chroma key Add a `chroma-key` object to the video element: ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "image", "src": "https://example.com/backdrop.jpg" }, { "type": "video", "src": "https://example.com/presenter-greenscreen.mp4", "chroma-key": { "color": "#00B140", "similarity": 0.4, "blend": 0.1 } } ] } ] } ``` The properties: - `color` — the colour to remove. Use a hex value matched to your source footage. Common chroma green is `#00B140`; chroma blue is `#0047AB`. - `similarity` — how aggressive the removal is. `0.0` is exact match, higher values catch more of the background but risk biting into the subject. Start at `0.3-0.4`. - `blend` — how soft the edge is between kept and removed pixels. Higher values produce a smoother edge but can introduce a colour halo. Start at `0.1`. ## Tips for a clean key - Use video shot with **even, flat lighting** on the backdrop. Shadows and wrinkles cause uneven colour and ragged edges. - Avoid reflective surfaces (glasses, glossy hair products) — they pick up the backdrop colour. - If you control the shoot, prefer **chroma blue** when the subject has green tones in their wardrobe or skin. - Test `similarity` in small increments (0.05). The sweet spot is narrow. ## See also - [Video element reference](@/reference/json-syntax/element/video) - [Tutorial 2 — Images, videos & audios](@/tutorials/02-images-videos-audios) # HTML & screenshot rendering # HTML & screenshot rendering The `html` element renders any HTML/CSS/JS snippet inside a real headless browser and composites the result onto the video. This unlocks anything you can build with web technologies: animated cards, code highlighting, animated charts, web fonts, custom layouts. ## Two flavours: inline HTML and URL You can either provide an HTML string inline or point at a URL the engine will load. ### Inline HTML ```json { "type": "html", "html": "
Hello world
", "duration": 5 } ``` The engine renders the HTML in a transparent-background browser at the element's width/height, then composites it onto the scene. ### External URL ```json { "type": "html", "src": "https://example.com/my-template.html?title=Hello&accent=ff6b00", "duration": 5 } ``` This is useful for complex templates hosted on your own server. JSON2Video maintains a set of pre-built components at `https://cdn.json2video.com/components/` — these are HTML templates accepted as `component` elements (see [component reference](@/reference/json-syntax/element/component)). ## Sizing and DPI By default the HTML is rendered at the element's `width` × `height` (in pixels). To improve crispness on smaller elements, set a higher `dpi`: ```json { "type": "html", "html": "
...
", "width": 600, "height": 200, "dpi": 2 } ``` `dpi: 2` doubles the render resolution and scales back down — equivalent to a Retina rendering pass. Higher values cost more render time. ## Animations The HTML element captures **frame by frame** for `duration` seconds. CSS animations and JS-driven animations are recorded in real time. Common patterns: - CSS keyframe animations: work out of the box. - `requestAnimationFrame`-driven JS: works, but make sure your script does not rely on user interaction. - Web fonts: the engine waits for fonts to load before capturing. ## When to use HTML rendering vs components | Use case | Recommended element | |----------|--------------------| | Pre-built logo reveals, lower-thirds, end cards from our library | `component` | | Your own reusable HTML template | `html` with `src` | | One-off custom styled card | `html` with inline `html` | | Anything text-heavy (paragraphs, code blocks) | `html` — `text` doesn't wrap rich content | ## See also - [HTML element reference](@/reference/json-syntax/element/html) - [Tutorial 6 — HTML elements](@/tutorials/06-html-elements) - [Components deep-dive](@/guides/advanced/components) # Components deep-dive # Components deep-dive Components are pre-built HTML templates rendered as part of your video. They are the fastest way to add polished, animated elements — lower-thirds, intros, outros, social cards — without writing HTML yourself. The catalog lives at `https://json2video.com/components/`. Each component has a unique ID and a set of parameters you can pass via the `settings` object. ## Anatomy of a `component` element ```json { "type": "component", "component": "advanced/intro-001", "settings": { "title": "Welcome", "subtitle": "to JSON2Video", "background-color": "#0a0a0a", "color": "#ffffff" }, "duration": 5 } ``` - `component` — the component ID from the catalog. - `settings` — a key-value object whose accepted keys are documented on the component's page in the catalog. - `duration` — how long the component plays, in seconds. Some components have built-in animations that loop or freeze when `duration` is longer than the animation. ## Discovering components The component catalog is grouped by section: - **Text cards** — headline + body cards with positioning controls. - **Lower-thirds** — name/title overlays for the bottom of the frame. - **Text animations** — stand-alone animated text (zoom, falling) beyond the basic [text styles](@/reference/text-styles). - **Items** — buttons, counters, ratings, images, tables. - **Shapes** — animated rectangles, polygons, cutouts, custom HTML/SVG. - **Backgrounds** — full-screen animated or patterned backgrounds. - **Effects** — vignettes, wipes, transitions. - **Textboxes** — positioned text boxes with backgrounds and borders. Browse the full catalog at the [component library reference](@/reference/components); each component links to a detail page with the accepted settings and a video preview. ## Combining components and other elements Components are regular elements — you can stack them with images, videos, voice, etc. A typical pattern: ```json { "scenes": [ { "elements": [ { "type": "video", "src": "https://example.com/background.mp4" }, { "type": "component", "component": "advanced/lower-third-001", "settings": { "name": "Ana López", "title": "Product Manager" }, "duration": 8 } ] } ] } ``` The video plays as background; the lower-third animates in on top. ## Building your own components Components are HTML/CSS/JS templates hosted on the JSON2Video CDN. If you need a reusable template that's specific to your product or brand: 1. Build the HTML/CSS yourself (any framework — vanilla, Tailwind, React-compiled). 2. Host it on your own server. 3. Use the [`html` element](@/reference/json-syntax/element/html) with `src` pointing at your URL. Pass parameters as query string variables in the URL; your HTML reads them with `URLSearchParams`. For higher-volume use cases, contact [support@json2video.com](mailto:support@json2video.com) about adding a private component to the catalog. ## See also - [Component element reference](@/reference/json-syntax/element/component) - [Tutorial 5 — Component library](@/tutorials/05-component-library) - [HTML & screenshot rendering](@/guides/advanced/html-rendering) # Caching deep-dive # Caching deep-dive JSON2Video uses a multi-layer cache to make rendering as cheap and fast as possible. Identical inputs produce identical outputs, so the engine reuses previously rendered material whenever it can detect that nothing has changed. Understanding the cache lets you: - Reduce render time on iterative workflows. - Save credits on partial re-renders. - Force a fresh render when an upstream asset changed silently. ## What gets cached The engine maintains caches at three granularities: - **Movie cache**: when an identical movie JSON is submitted again, the existing rendered video is returned without re-running the pipeline. - **Scene cache**: when a scene's content is unchanged but the surrounding movie changed, the engine reuses the rendered scene and only re-stitches the final movie. - **Element cache**: when an individual element (image, video, voice, …) was downloaded or rendered before, the source asset is reused. This is especially valuable for generated voices and large remote assets. Cache lookups are performed on a content fingerprint of the JSON tree at that scope. Anything that affects the visual or audio result (size, position, source URL, voice text, model parameters) is part of the fingerprint. ## The `cache` property Every level of the JSON tree accepts a `cache` boolean: ```json { "resolution": "full-hd", "cache": false, "scenes": [ { "cache": true, "elements": [ { "type": "image", "src": "https://example.com/poster.jpg", "cache": false } ] } ] } ``` The semantics: - `true` (or omitted): use the cache when available. - `false`: bypass the cache at this level; force a fresh render or download. The flag is **scoped**. Setting `cache: false` on a scene forces the scene to be re-stitched but does not re-download its elements. Setting `cache: false` on an element forces that element to be re-fetched / re-generated and also implies the parent scene must be re-stitched, but sibling elements in that scene stay cached. ## When to disable the cache The cache is correct most of the time. Cases where you want to bypass it: | Situation | Where to set `cache: false` | |-----------|----------------------------| | The remote URL still points to the same path but the file changed (e.g. you re-uploaded `poster.jpg` to the same S3 key) | On the affected element | | You changed a voice element's `model-settings` and want to re-generate audio | On the voice element | | You want a known-fresh end-to-end render for a release | On the movie root | ## When to keep the cache For high-volume production with deterministic inputs (e.g. templates filled from a database), leave caching on. The same row in the database yields the same JSON and the cache returns immediately — you only pay for the first render of each unique combination. For prototyping, the cache also helps: you can iterate on text/positioning of a single element without re-running expensive operations elsewhere in the movie. ## Cache and voice elements Generated voiceovers are among the most expensive cached items in terms of credits. If you re-submit the same `voice` element with the same `text`, `voice`, `model`, and `model-settings`, the audio is served from the cache and **does not consume voice credits again**. This is a deliberate optimisation for template-driven pipelines, where the same intro / outro voice line appears in every render. Keep the wording identical and you only pay once. ## See also - [Tutorial 16 — Optimization & cost](@/tutorials/16-optimization-and-cost) - [Credits & limits](@/reference/credits) - [Movie JSON reference](@/reference/json-syntax/movie) # Save generated assets to Media # Save generated assets to Media Assets returned by `POST /v2/preloads`, by `movie.preload[]` inside a render, or generated by elements in `scene.elements[]` are normally stored in a temporary cache that expires **3 days** after generation. That works fine for one-shot renders, but breaks workflows where you generate an asset once and reuse it for weeks — long-form templates, voiceover libraries, hero images bound to a campaign that ships next month. To keep a generated asset, set `save-to-media: true` on the item or element. The URL returned in the response then points to your Media library (the Drive), where files have no expiry date — they are kept as long as your account holds credits. See [File retention](@/reference/file-retention). ## Why ephemeral by default Most preloaded assets are throwaway: a one-off image baked into a single render. Auto-persisting every asset would silently fill the Drive with assets you don't recognize a month later and burn storage quota. Opt-in keeps the Drive curated by you. If you do want everything persisted, simply set the flag on each item — there is no global toggle by design. ## Opt-in Add `"save-to-media": true` to any preload item whose type is generable (`image`, `video`, `audio`, `voice`, `html`, `component`). External URLs in `image` / `video` items are also supported — the file is downloaded once and stored in the Drive. ```json { "preload": [ { "id": "campaign_hero", "type": "image", "model": "flux-schnell", "prompt": "a paper plane flying over a city skyline, flat illustration", "save-to-media": true }, { "id": "intro_voice", "type": "voice", "model": "elevenlabs", "voice": "Adam", "text": "Welcome to our weekly digest.", "save-to-media": true }, { "id": "throwaway_thumb", "type": "image", "model": "flux-schnell", "prompt": "abstract gradient background" } ] } ``` The first two items are persisted; the third stays in the temporary cache. ## Response shape `GET /v2/preloads/{preload_id}` reports the result per item: ```json { "items": [ { "id": "campaign_hero", "type": "image", "status": "ready", "url": "https://media.json2video.com/{client_id}/files/generated/image/campaign_hero.webp", "persistent": true, "expires_at": null, "width": 1024, "height": 1024, "size": 37330 }, { "id": "throwaway_thumb", "status": "ready", "url": "https://json2video-cdn1.s3.amazonaws.com/tmp/assets/{client_id}/...", "persistent": false, "expires_at": "2026-05-30T10:00:00Z" } ] } ``` - `persistent: true` — the URL has no expiry date and is stable. Safe to embed in scheduled renders, downstream pipelines, public pages. - `persistent: false` — the URL is from the temporary cache. `expires_at` tells you when it stops working (approximately 3 days after generation). ## File path & naming Persisted assets land at: ``` https://media.json2video.com/{client_id}/files/generated/{type}/{filename}.{ext} ``` - `{filename}` is the element `id` if it is a safe identifier (`^[a-zA-Z0-9_-]+$`). Otherwise, the first 12 characters of the asset hash. - `{ext}` is the real container/codec returned by the generator. A Flux model that yields WebP produces `.webp`, not `.png` — set your downstream consumers accordingly or inspect the returned URL. - If a different asset already occupies that path, the new file is suffixed with `-{hash[:6]}` to avoid overwriting. The file is visible in the dashboard at `Media → generated → {type}` and is manageable via `/v2/media` like any uploaded asset. ## Quota & graceful degradation If the Drive is blocked for the account (storage quota, billing hold, etc.), the asset is still generated and served from the temporary cache — the request never fails because of save-to-media. The response is: ```json { "status": "ready", "url": "https://json2video-cdn1.s3.amazonaws.com/tmp/assets/...", "persistent": false, "expires_at": "2026-05-30T10:00:00Z", "warning": "quota_blocked" } ``` Check the `warning` field if you need to alert on quota. ## Cache promotion (no extra cost) If you already preloaded an asset without `save-to-media`, asking again with `save-to-media: true` and the same prompt/source promotes the cached binary into the Media library without re-running the generator. **No credits are deducted for the promotion.** If the cache has already purged the binary, JSON2Video re-generates the asset from the stored source request — still at zero credit cost, as compensating for the cache miss is on us. ## Lifecycle Persisted assets stay in your Drive until you delete them. Removing the file via `DELETE /v2/media/file` (or from the dashboard) automatically clears the backreference. A subsequent preload with `save-to-media: true` re-persists it as a fresh file. ## See also - [Media panel — persisted generated assets](@/guides/dashboard/media) - [Caching deep-dive](@/guides/advanced/caching-deep-dive) # Use public templates # Use public templates JSON2Video ships a **public library** of ready-made templates. Each template is a reusable Movie JSON blueprint with `{{variable}}` placeholders, so you only supply the values that change. There are two ways to work with a public template from the API: - **Render it directly** — pass your own values to its variables and get a one-off video. Nothing is saved to your account. See [Guide 1](#guide-1--render-a-video-from-a-public-template). - **Duplicate it into your account** — get your own editable copy that you can customize and manage. See [Guide 2](#guide-2--duplicate-a-public-template-into-your-account). Both start the same way: browse the library and pick a template ID. Every request needs the `x-api-key` header. Reading templates and rendering need at least the `render` role; duplicating writes to your account and needs `editor` or above. Get a key from the [dashboard](https://json2video.com/dashboard/apikeys). ## Browse the library and pick a template Call [`GET /v2/templates/library`](@/reference/api-endpoints/templates-library) to list the public gallery. Add `?tags=intro,social` to filter. ```bash curl -X GET "https://api.json2video.com/v2/templates/library" \ -H "x-api-key: YOUR_API_KEY" ``` Response (trimmed): ```json { "success": true, "count": 24, "templates": [ { "id": "LerKrmBfiqaIgBuacLWn", "name": "Real estate listing", "tags": ["real-estate", "published"], "width": 1080, "height": 1920, "aspect_ratio": 0.56, "video_url": "https://json2video-cdn2.s3.amazonaws.com/templates/LerKrmBfiqaIgBuacLWn/example.mp4", "thumbnail_url": "https://json2video-cdn2.s3.amazonaws.com/templates/LerKrmBfiqaIgBuacLWn/thumbnail.jpg" } ] } ``` Preview candidates with `video_url` / `thumbnail_url`, then copy the `id` of the one you want (here `LerKrmBfiqaIgBuacLWn`). You'll use it in both guides below. ## Guide 1 — Render a video from a public template You don't need to own a template to render from it. Pass its `id` plus your values, and JSON2Video renders the video. Nothing is stored in your account. ### 1. Discover the variables it exposes Read the template by ID with `scopes=variables` to see the variables it declares and their default values: ```bash curl -X GET "https://api.json2video.com/v2/templates?id=LerKrmBfiqaIgBuacLWn&scopes=variables" \ -H "x-api-key: YOUR_API_KEY" ``` ```json { "success": true, "template": { "id": "LerKrmBfiqaIgBuacLWn", "name": "Real estate listing", "owner": false, "variables": { "address": "123 Oak Street", "price": "$849,000", "bedrooms": "4", "agent_name": "Jordan Lee" } } } ``` You can read any public template this way even though you don't own it — `owner` simply comes back `false`. If you're building a form or need types, request [`GET /v2/templates?id=...&format=jsonschema`](@/reference/api-endpoints/templates-list) to get a JSON Schema of the variables instead. ### 2. Render, overriding the variables you want Submit a movie that references the template and supplies your values. Any variable you omit keeps the template's default. ```bash curl -X POST "https://api.json2video.com/v2/movies" \ -H "x-api-key: YOUR_API_KEY" \ -H "content-type: application/json" \ -d '{ "template": "LerKrmBfiqaIgBuacLWn", "variables": { "address": "47 Cedar Avenue, Seattle, WA", "price": "$1,200,000" } }' ``` Response: ```json { "success": true, "project": "JkGxEoPRF9EgRb32", "timestamp": "2026-07-08T10:49:52.924Z" } ``` You can also override `resolution`, `quality` or `exports` at the top level of the body alongside `template` and `variables`. Save the `project` value to check status. ### 3. Poll until it's done Poll [`GET /v2/movies?project=...`](@/reference/api-endpoints/movies-status) until `movie.status` is `done` (or `error` / `timeout`). When it's `done`, `movie.url` holds the rendered video. ```bash curl -X GET "https://api.json2video.com/v2/movies?project=JkGxEoPRF9EgRb32" \ -H "x-api-key: YOUR_API_KEY" ``` ## Guide 2 — Duplicate a public template into your account Rendering directly is ideal for one-off videos. If instead you want to **customize the template itself** — edit its scenes, change the default values, or manage it from the dashboard — duplicate it into your account first. You get a brand-new template ID that you own. ### 1. Duplicate it Call [`POST /v2/templates`](@/reference/api-endpoints/templates-create) with `action=duplicate` and the public template's `id`. Optionally set a `name` and pre-fill some `variables` in the body. This needs the `editor` role or above. ```bash curl -X POST "https://api.json2video.com/v2/templates?id=LerKrmBfiqaIgBuacLWn&action=duplicate" \ -H "x-api-key: YOUR_API_KEY" \ -H "content-type: application/json" \ -d '{ "name": "My real estate listing", "variables": { "agent_name": "Jordan Lee" } }' ``` Response — note the **new** `templateId`, which now belongs to your account: ```json { "success": true, "templateId": "xyz987uvw654rst321qp", "name": "My real estate listing", "timestamp": "2026-07-08T10:49:52.924Z" } ``` ### 2. Use your copy The duplicate is a fully independent template you own — later changes to the public original don't affect it. With your new ID you can: - **Render it** the same way as Guide 1 (`POST /v2/movies` with `"template": "xyz987uvw654rst321qp"` and your `variables`). - **Edit it** with [`POST /v2/templates?id=xyz987uvw654rst321qp`](@/reference/api-endpoints/templates-create), or from the dashboard — see [Dashboard — Templates](@/guides/dashboard/templates). ## Which approach should I use? | Render directly (Guide 1) | Duplicate first (Guide 2) | |---------------------------|---------------------------| | Quick, one-off videos | You want to customize the template | | Nothing saved to your account | Editable copy you own | | Needs the `render` role | Needs the `editor` role | ## See also - [Template library (GET /v2/templates/library)](@/reference/api-endpoints/templates-library) — the public gallery endpoint. - [List templates (GET /v2/templates)](@/reference/api-endpoints/templates-list) — read a template and its variables. - [Create / update template (POST /v2/templates)](@/reference/api-endpoints/templates-create) — duplicate, edit and save templates. - [Create movie (POST /v2/movies)](@/reference/api-endpoints/movies-create) — render from a template. - [Dashboard — Templates](@/guides/dashboard/templates) — manage templates in the UI. - [Tutorial 10 — Variables](@/tutorials/10-variables) and [Tutorial 11 — Templates](@/tutorials/11-templates). # Production & operations # Production & operations Patterns for running JSON2Video in production: how to deliver finished videos, how to be notified when a render finishes, and how to handle the inevitable failures. - [Webhooks (production)](@/guides/production/webhooks-advanced) — receive a callback when a render completes. - [FTP / SFTP delivery](@/guides/production/ftp-sftp) — upload finished videos to your own server. - [Email notifications](@/guides/production/email-notifications) — email the video URL to a recipient. - [Error handling](@/guides/production/error-handling) — synchronous, async, and delivery errors. - [Retries & idempotency](@/guides/production/retries-idempotency) — safely retry without doubling your bill. For the full error catalog, see the [errors reference](@/reference/errors). # Webhooks --- source: api/endpoints/destinations/index.js last_reviewed: 2026-07-14 --- # Webhooks (production patterns) Webhooks are the recommended way to learn when a render finishes — they replace polling and let your backend react the moment a video is ready. This guide covers the production-grade patterns that go beyond the basic [webhooks reference](@/reference/webhooks). ## When to use webhooks vs polling | Situation | Recommended | |-----------|-------------| | Backend service with a public HTTPS endpoint | Webhooks | | Local script or interactive CLI tool | Polling | | Long-running batch job (hundreds of renders / minute) | Webhooks | | Render duration < 30 seconds and you can afford a sync wait | Polling (simpler) | ## Configure a webhook destination Add a webhook entry to the `exports[].destinations` array: ```json { "resolution": "full-hd", "scenes": [ /* ... */ ], "exports": [{ "destinations": [{ "type": "webhook", "endpoint": "https://api.example.com/json2video/done" }] }] } ``` When the movie finishes (or fails), JSON2Video sends an HTTP POST with a flat payload describing the render outcome. ## What the payload looks like The payload is a **flat** JSON object — it does not mirror `GET /v2/movies` and there is no nested `movie` wrapper (see the [webhooks reference](@/reference/webhooks) for the field-by-field contract): ```json { "success": true, "project": "WAEE8PohgVwv2teP", "url": "https://assets.json2video.com/clients/.../movie.mp4", "id": "your-movie-id", "width": "1920", "height": "1080", "duration": "12.5", "size": "2451234", "client-data": { /* anything you sent in the original request */ } } ``` The `client-data` field is the most important field for production. Set it on the original `POST /v2/movies` to anything that helps your backend identify *which* business object this render belongs to (an order ID, a user ID, a campaign slug, a row ID in your database). It is echoed back verbatim. ## Receiving webhooks safely Your endpoint must: 1. **Be publicly reachable over HTTPS.** Self-signed certs are not accepted; use a real CA (Let's Encrypt, Cloudflare, etc.). 2. **Respond quickly.** Aim for `< 5 seconds`. If you need to do heavy work, queue it and return immediately. 3. **Be idempotent.** The same movie payload may arrive more than once if your endpoint times out. De-duplicate on `project`. 4. **Tolerate unknown fields.** New fields may be added over time; your parser should not reject them. A minimal Node/Express handler: ```javascript import express from "express"; const app = express(); app.use(express.json({ limit: "1mb" })); app.post("/json2video/done", async (req, res) => { res.status(200).end(); // ack first, work after const payload = req.body; if (!payload?.project) return; // Lookup the order in your DB by payload["client-data"].orderId await handleRender(payload); }); ``` ## Error handling If the render fails, JSON2Video still calls your webhook. Check the flat payload: - `success: true` — render succeeded; `url` is set. - `success: false` — render failed; the `error` field describes why and `url` is empty or absent. There is no `status` field in the webhook payload. To get the full status object (`done` / `error` / `timeout`), call [`GET /v2/movies?project={project}`](@/reference/api-endpoints/movies-status) — cross-checking there is also the recommended way to verify an unauthenticated webhook. ## Local development with webhooks Public webhook receivers are hard during local development. Use: - [ngrok](https://ngrok.com/) — tunnels a public HTTPS URL to `localhost`. - [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) — free, no time-out. - [webhook.site](https://webhook.site/) — quick request inspection without writing any code. ## See also - [Webhooks reference](@/reference/webhooks) - [Retries & idempotency](@/guides/production/retries-idempotency) - [Error handling](@/guides/production/error-handling) - [Tutorial 15 — Webhooks](@/tutorials/15-webhooks) # FTP / SFTP # FTP / SFTP delivery JSON2Video can upload rendered videos straight to your FTP or SFTP server, removing the need to download from our CDN and re-upload. This is the standard way to deliver finished assets to a media management system, a CMS staging area, or a customer's drop folder. ## Configuration Add an FTP or SFTP destination to the `exports[].destinations` array. We strongly recommend storing credentials in a [Dashboard Connection](https://json2video.com/dashboard/connections) and referencing them by `id`: ```json { "resolution": "full-hd", "scenes": [ /* ... */ ], "exports": [{ "destinations": [{ "id": "my-sftp-connection", "file": "promo-__yyyy__-__mm__-__dd__.mp4" }] }] } ``` Sensitive fields stored in Connections are encrypted at rest. Override any field at request time by re-declaring it — for example, set `remote-path` per render while reusing the Connection's host / credentials. ## Inline credentials (not recommended) If you must pass credentials inline, use the same JSON shape without an `id`: ```json { "exports": [{ "destinations": [{ "type": "sftp", "host": "sftp.example.com", "port": 22, "username": "uploader", "password": "...", "remote-path": "/incoming/videos/", "file": "__random__.mp4" }] }] } ``` This appears in your API logs and in your codebase. Prefer Connections. ## Properties | Property | Type | Required | Notes | |----------|------|----------|-------| | `type` | string | yes | `"ftp"` or `"sftp"` | | `host` | string | yes | hostname or IP | | `port` | number | yes | typically `21` for FTP, `22` for SFTP | | `username` | string | yes | account username | | `password` | string | yes | account password / SSH password | | `remote-path` | string | no | directory path; defaults to `./` | | `file` | string | no | filename; defaults to a unique name | ## Filename macros Both `remote-path` and `file` accept macros that are expanded at upload time. The most common: - `__yyyy__`, `__mm__`, `__dd__` — date components - `__hh__`, `__nn__`, `__ss__` — time components - `__random__` — a random number - `__filename__` — the original filename JSON2Video assigned Example dynamic path: ```json { "remote-path": "/customers/acme/__yyyy__/__mm__/", "file": "acme-promo-__yyyy__-__mm__-__dd__-__random__.mp4" } ``` ## Operational notes - **Timeout**: the full export step (all destinations combined) must finish within 5 minutes. Very large videos to slow servers may time out. - **Order**: when multiple destinations are listed, they are processed sequentially. The webhook destination (if also present) fires only after all destinations succeed or after the timeout. - **Failure mode**: if the upload fails, the movie's `status` reports `done` (the render succeeded) but the export error is logged on the movie object. Monitor for upload failures separately. ## See also - [Email notifications](@/guides/production/email-notifications) - [Webhooks](@/guides/production/webhooks-advanced) - [Dashboard connections guide](@/guides/dashboard/connections) # Email notifications # Email notifications When a render finishes, JSON2Video can send an email containing the video URL. This is useful for low-volume workflows that don't justify wiring up a webhook receiver — typical examples are internal team notifications, ad-hoc client deliveries, or "leave-it-running overnight" batch jobs. ## Configuration Add an email destination to `exports[].destinations`: ```json { "resolution": "full-hd", "scenes": [ /* ... */ ], "exports": [{ "destinations": [{ "type": "email", "to": "team@example.com", "subject": "Your video is ready!", "message": "Download it from: __video_url__" }] }] } ``` | Property | Required | Notes | |----------|----------|-------| | `type` | yes | must be `"email"` | | `to` | yes | recipient address | | `subject` | yes | email subject line | | `message` | yes | plain-text body; supports macros | The sender is a JSON2Video address; replies do not return to your account. For workflows that need the recipient to reply, include your own email in the body text. ## Macros Both `subject` and `message` accept the macros documented in the [exports macros table](@/reference/json-syntax/movie#exports). The most useful for emails: - `__video_url__` — direct CDN URL to the rendered MP4 - `__filename__` — auto-generated filename - `__yyyy__-__mm__-__dd__` — render date Example using macros for a personalised subject line: ```json { "type": "email", "to": "ops@example.com", "subject": "[__yyyy__-__mm__-__dd__] New render: __filename__", "message": "Video: __video_url__\nFile: __filename__" } ``` ## When NOT to use email destinations Email is fine for human notifications. For programmatic workflows — anything where a server needs to act on the rendered video — use a [webhook](@/guides/production/webhooks-advanced) instead. Parsing video URLs out of inbound emails is brittle and adds latency. Email is also subject to: - **Recipient spam filters**: messages can land in spam, especially under high volume. - **Send limits**: rate-limited per account to prevent abuse. - **No retries**: if the recipient mail server rejects, the email is dropped (the render is unaffected). ## Failure handling If the email cannot be sent, the render itself is unaffected — the video is still available at the URL returned by `GET /v2/movies?project=...`. The email failure is logged on the movie object as part of the export status. ## See also - [Webhooks](@/guides/production/webhooks-advanced) - [FTP / SFTP delivery](@/guides/production/ftp-sftp) - [Movie reference](@/reference/json-syntax/movie) # Error handling # Error handling Production code that calls JSON2Video must handle three different failure modes: synchronous API errors, async render errors, and delivery / export errors. This guide explains what to expect for each, with concrete code patterns. ## 1. Synchronous API errors When you `POST /v2/movies`, the API immediately validates the JSON. If anything is wrong with the request, you get an error response within milliseconds: ```json { "success": false, "message": "Source URL is required for video element in Scene 1, Element 2" } ``` Common synchronous error categories: - **Authentication**: invalid or missing `x-api-key` header. - **Validation**: malformed JSON, missing required fields, invalid enum values. - **Quota**: account out of credits, request rate-limited. Handle synchronous errors by: ```javascript const response = await fetch("https://api.json2video.com/v2/movies", { method: "POST", headers: { "x-api-key": API_KEY, "content-type": "application/json" }, body: JSON.stringify(movieJson) }); const data = await response.json(); if (!data.success) { // Log data.message, surface a 4xx to your user, do NOT retry blindly throw new Error(`JSON2Video rejected the movie: ${data.message}`); } ``` Most synchronous errors are non-retryable — the JSON is wrong and re-sending the same payload yields the same error. The exception is rate-limiting (HTTP 429), which IS retryable with backoff. See the [errors reference](@/reference/errors) for the full catalog of error messages. ## 2. Async render errors If the request was accepted, the API returns a `project` ID and the render runs in the background. The render itself can still fail. When that happens, `GET /v2/movies?project=...` returns: ```json { "success": true, "movie": { "project": "WAEE8PohgVwv2teP", "status": "error", "message": "Failed to download element: HTTP 404 at https://example.com/missing.jpg" } } ``` Note `success: true` at the top level — the *request* succeeded, but the *render* failed. Always check `movie.status` against the expected enum: `pending`, `running`, `done`, `error`, `timeout`. Common async error categories: - **Asset fetch failures**: an `src` URL returned 4xx/5xx, an element source was an unsupported format. - **Upstream provider failures**: a TTS provider (e.g. ElevenLabs) returned an error mid-render. - **Render timeouts**: a single element exceeded the per-element timeout, or the full movie exceeded the per-movie timeout. Retryable cases: - Upstream provider transient failures (rate limits, brief outages). - Source asset URL that's intermittently unreachable. Non-retryable cases: - Permanent 404 on `src`. - Unsupported codec or container. - Out-of-credits errors. ## 3. Export / delivery errors Once the render is done, exports (FTP, SFTP, email, webhook) run. If a destination fails, the render itself is still considered `done` and the video URL is available — but the export error is reported on the movie object. Best practice: don't rely on email or FTP as the only delivery channel for mission-critical workflows. Always check `movie.status === "done"` and `movie.url` directly via the API. ## A robust polling loop ```javascript async function waitForRender(project, { timeout = 600_000, interval = 5_000 } = {}) { const start = Date.now(); while (Date.now() - start < timeout) { const r = await fetch(`https://api.json2video.com/v2/movies?project=${project}`, { headers: { "x-api-key": API_KEY } }); const { success, movie } = await r.json(); if (!success) throw new Error("Status check failed"); if (movie.status === "done") return movie; if (movie.status === "error") throw new Error(`Render failed: ${movie.message}`); if (movie.status === "timeout") throw new Error("Render timed out"); await new Promise(res => setTimeout(res, interval)); } throw new Error("Client-side polling timed out"); } ``` ## See also - [Errors reference (full catalog)](@/reference/errors) - [Retries & idempotency](@/guides/production/retries-idempotency) - [Webhooks](@/guides/production/webhooks-advanced) # Retries & idempotency # Retries & idempotency Production workflows must be safe to re-run. Network blips, server restarts, and worker crashes happen, and your code may end up calling `POST /v2/movies` more than once for the same business event. This guide explains how to build that safety in. ## Why naive retries are unsafe `POST /v2/movies` is not idempotent. Every successful call returns a new `project` ID and consumes credits for any fresh asset generation that occurs (e.g. voiceover synthesis). A retry-on-network-error loop without de-duplication can double or triple your billing. The good news: the engine's [caching system](@/guides/advanced/caching-deep-dive) deduplicates identical generations across runs. The same `voice` element with the same text is rendered once and re-used. But the *render orchestration* still runs, so retry storms still cost you in scene-stitching + final encoding work. ## The right pattern: client-side idempotency keys Generate a stable key from your business event and store the resulting `project` ID against it. Before submitting a render, check whether you already have a project for this key. ```javascript async function submitOnce(idempotencyKey, movieJson) { // 1. Check your own DB for an existing project tied to this key const existing = await db.movies.findOne({ idempotency_key: idempotencyKey }); if (existing) return existing.project; // 2. Submit const r = await fetch("https://api.json2video.com/v2/movies", { method: "POST", headers: { "x-api-key": API_KEY, "content-type": "application/json" }, body: JSON.stringify(movieJson) }); const { success, project, message } = await r.json(); if (!success) throw new Error(message); // 3. Persist before returning await db.movies.insert({ idempotency_key: idempotencyKey, project }); return project; } ``` Use the same idempotency key on every retry of the same business event (e.g. `order:12345:promo-video`). The first call creates the render; subsequent calls return the existing project ID without re-submitting. ## Idempotency key conventions Good keys are: - **Deterministic** — derived from the business object, not random. - **Unique per logical render** — different rendered variants should have different keys. - **Reasonably stable across retries** — re-derive the same key from the same inputs. Examples: - `order-12345-thumbnail` — render a thumbnail for order #12345. - `user-42-onboarding-video-v3` — version embedded for explicit invalidation. - `campaign-summer-2026-ad-spanish` — multi-dimensional. ## Server-side retries (between submission and rendering) JSON2Video automatically retries transient failures it sees while producing a render (upstream provider rate limits, brief asset fetch failures). You do not need to retry on your side just because the first attempt's status was briefly `error` — but **do** check the final status after the configured timeout. Element-level cache means transient upstream failures cost almost nothing on retry: only the failed element is re-attempted; everything else is served from cache. ## Polling and idempotency `GET /v2/movies?project=...` is naturally idempotent. Poll it as often as you need — once per 5-10 seconds is the recommended cadence. The response is the same regardless of how many times you call it. For long-running renders (60s+), webhooks are preferable to polling — see [webhooks (production)](@/guides/production/webhooks-advanced). ## Webhook delivery and de-duplication Webhook receivers should de-duplicate on `movie.project` because: - A timed-out webhook delivery may be retried (today, this is not the case but it is a likely future behaviour). - You may receive a duplicate from a re-submitted render that hit the movie-level cache. A simple de-dup pattern at the receiver: ```javascript app.post("/json2video/done", async (req, res) => { res.status(200).end(); const { movie } = req.body; const seenBefore = await db.processed.upsert({ project: movie.project }); if (seenBefore) return; // de-duplicate await handleRender(movie); }); ``` ## See also - [Caching deep-dive](@/guides/advanced/caching-deep-dive) - [Webhooks (production)](@/guides/production/webhooks-advanced) - [Error handling](@/guides/production/error-handling) # Examples by use case # Examples by use case Complete, copy-paste-ready movie JSONs for common use cases. Each example includes a full JSON, a 2-3 paragraph explanation of how the moving parts fit together, and links to the reference pages for each element. - [Slideshow](@/guides/examples/slideshow) — images + music + crossfades. - [Social reel](@/guides/examples/social-reel) — vertical 9:16 with quick cuts and captions. - [Quote video](@/guides/examples/quote-video) — text over a video background with attribution. - [Audiogram](@/guides/examples/audiogram) — waveform with cover image and subtitles. - [Automatic subtitles](@/guides/examples/automatic-subtitles) — video with auto-generated burned-in subtitles. For the full progressive course, work through the [Tutorials](@/tutorials/01-your-first-video). # Slideshow # Slideshow A classic photo slideshow with background music and crossfade transitions between images. Each image holds for 4 seconds with a 1-second crossfade between scenes. ## Complete JSON ```json { "comment": "Photo slideshow with crossfades and background music", "resolution": "full-hd", "quality": "high", "scenes": [ { "duration": 4, "transition": { "style": "fade", "duration": 1 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/samples/slideshow-01.jpg", "zoom": 1 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 1 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/samples/slideshow-02.jpg", "zoom": 1 } ] }, { "duration": 4, "transition": { "style": "fade", "duration": 1 }, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/samples/slideshow-03.jpg", "zoom": 1 } ] }, { "duration": 4, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/samples/slideshow-04.jpg", "zoom": 1 } ] } ], "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/uplifting-cinematic.mp3", "fade-out": 2, "volume": 0.6 } ] } ``` ## How it works Each image gets its own scene with a 4-second `duration`. A `transition` block of `{ "style": "fade", "duration": 1 }` on each scene tells the renderer to crossfade out into the next scene over the last second. The `zoom: 1` on each image adds a subtle Ken Burns zoom that keeps the slideshow visually alive. Background music is a movie-level `audio` element — placing it at the movie root (in the top-level `elements` array, not inside any scene) makes it span the whole render. The `fade-out: 2` smooths the end of the music so it doesn't cut abruptly when the last image fades to black. `volume: 0.6` reduces the music so it sits comfortably in the mix when you later add voice or subtitles. To produce a longer slideshow, add more scenes — every scene with the same `duration` keeps the rhythm consistent. To produce a faster-paced slideshow, drop scene `duration` to 2 and transition `duration` to 0.5. ## Try it Replace the `src` URLs with your own image URLs (uploaded via the [Media dashboard](@/guides/dashboard/media), or from any public HTTPS URL) and POST the JSON to `POST /v2/movies`. See the [Quickstart](@/getting-started/quickstart) for the full API call. ## See also - [Tutorial 2 — Images, videos & audios](@/tutorials/02-images-videos-audios) - [Tutorial 3 — Multiple scenes & transitions](@/tutorials/03-multiple-scenes-and-transitions) - [Image element reference](@/reference/json-syntax/element/image) - [Audio element reference](@/reference/json-syntax/element/audio) # Social reel # Social reel A vertical 9:16 social-media reel with quick cuts, on-screen captions, and music. Designed for Instagram Reels, TikTok, and YouTube Shorts. Total duration is 15 seconds — the sweet spot for early-feed retention. ## Complete JSON ```json { "comment": "Vertical social reel with quick cuts and captions", "resolution": "instagram-portrait", "quality": "high", "scenes": [ { "duration": 3, "elements": [ { "type": "video", "src": "https://cdn.json2video.com/assets/samples/reel-clip-01.mp4", "fit": "cover" }, { "type": "text", "text": "3 hacks that\nchanged everything", "y": 1500, "style": "008", "settings": { "font-size": "90px", "color": "white", "font-weight": "900" } } ] }, { "duration": 4, "elements": [ { "type": "video", "src": "https://cdn.json2video.com/assets/samples/reel-clip-02.mp4", "fit": "cover" }, { "type": "text", "text": "1.", "x": 100, "y": 200, "style": "001", "settings": { "font-size": "250px", "color": "#FF6B00", "font-weight": "900" } }, { "type": "text", "text": "Wake up at 5am", "y": 1500, "style": "008", "settings": { "font-size": "70px", "color": "white" } } ] }, { "duration": 4, "elements": [ { "type": "video", "src": "https://cdn.json2video.com/assets/samples/reel-clip-03.mp4", "fit": "cover" }, { "type": "text", "text": "2.", "x": 100, "y": 200, "style": "001", "settings": { "font-size": "250px", "color": "#FF6B00", "font-weight": "900" } }, { "type": "text", "text": "Cold shower\nevery morning", "y": 1500, "style": "008", "settings": { "font-size": "70px", "color": "white" } } ] }, { "duration": 4, "elements": [ { "type": "video", "src": "https://cdn.json2video.com/assets/samples/reel-clip-04.mp4", "fit": "cover" }, { "type": "text", "text": "3.", "x": 100, "y": 200, "style": "001", "settings": { "font-size": "250px", "color": "#FF6B00", "font-weight": "900" } }, { "type": "text", "text": "Read 10 pages\nbefore phone", "y": 1500, "style": "008", "settings": { "font-size": "70px", "color": "white" } } ] } ], "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/upbeat-energetic.mp3", "volume": 0.7, "fade-out": 1 } ] } ``` ## How it works The `resolution: "instagram-portrait"` sets the canvas to 1080×1920 (9:16). Each scene plays a short clip (3-4 seconds) with `fit: "cover"` so the video fills the vertical frame regardless of source aspect ratio. The opening scene is shorter (3s) — short hooks retain better than long ones on social feeds. Each step gets two stacked text elements: a big orange number anchored in the upper-left and a caption near the bottom. `font-weight: "900"` plus the bold sans-serif style makes the text legible against busy video backgrounds. Adjust `y` values to move captions if your subject lives in the lower third. A single movie-level `audio` track plays underneath. `volume: 0.7` keeps the music present but not overwhelming; combine with the [automatic subtitles](@/guides/examples/automatic-subtitles) pattern if you want spoken voiceover layered on top. ## Try it Replace the 4 `src` video URLs with your own clips. For a different format (TikTok, Reels), the resolution stays the same (`instagram-portrait`). For YouTube Shorts use the same — it's the standard 9:16. ## See also - [Tutorial 4 — Text & styling](@/tutorials/04-text-and-styling) - [Tutorial 3 — Multiple scenes & transitions](@/tutorials/03-multiple-scenes-and-transitions) - [Video element reference](@/reference/json-syntax/element/video) - [Automatic subtitles example](@/guides/examples/automatic-subtitles) # Quote video # Quote video A short video that overlays an inspirational or product quote on a looping video background, with attribution and a soft music bed. Designed for social feeds and email signatures. ## Complete JSON ```json { "comment": "Quote over a video background with attribution", "resolution": "squared", "quality": "high", "scenes": [ { "duration": 8, "elements": [ { "type": "video", "src": "https://cdn.json2video.com/assets/samples/abstract-loop.mp4", "fit": "cover", "volume": 0 }, { "type": "component", "component": "basic/004", "settings": { "background-color": "rgba(0,0,0,0.4)" } }, { "type": "text", "text": "“The best way to predict\nthe future is to invent it.”", "y": 350, "style": "002", "settings": { "font-size": "70px", "color": "white", "font-weight": "600", "text-align": "center", "line-height": "90px" } }, { "type": "text", "text": "— Alan Kay", "y": 700, "style": "002", "settings": { "font-size": "40px", "color": "#FFB85C", "font-style": "italic" } } ] } ], "elements": [ { "type": "audio", "src": "https://cdn.json2video.com/assets/audios/soft-piano.mp3", "volume": 0.5, "fade-out": 2 } ] } ``` ## How it works The single scene runs for 8 seconds — the read-time for a short quote without losing momentum on social feeds. The background `video` has `volume: 0` so its native audio doesn't fight the music bed, and `fit: "cover"` ensures it fills the square frame regardless of the source aspect. A semi-transparent dark overlay (the `basic/004` component with a 40% black background) sits between the video and the text. Without it, the quote can be hard to read against a busy background. Adjust the alpha (`rgba(0,0,0,0.4)`) up for darker, more legible text or down for a more cinematic look. Two `text` elements give the quote and the attribution. Using two elements (rather than one with embedded line breaks for the author) lets you style them independently — the quote in a larger, plain white; the author in a smaller italic accent colour. The `“ ”` curly quotes are deliberate — they look professional. Match the colour of the attribution (`#FFB85C`) to your brand accent. The `squared` resolution (1080×1080) works for Instagram feed, LinkedIn, and Twitter inline previews. For Stories / Reels switch to `instagram-portrait`. ## Try it Plug in your own quote and author. For brand quotes (a testimonial from a customer, your own brand promise), match the background video and accent colour to your brand. The structure stays the same — only the strings change. This is a great candidate for a [template](@/guides/dashboard/templates) where `quote` and `author` are variables. ## See also - [Tutorial 4 — Text & styling](@/tutorials/04-text-and-styling) - [Tutorial 11 — Templates](@/tutorials/11-templates) - [Text element reference](@/reference/json-syntax/element/text) - [Component element reference](@/reference/json-syntax/element/component) # Audiogram # Audiogram A podcast-style audiogram: a static cover image, an animated waveform synced to the audio, episode title text, and burned-in subtitles for accessibility. Vertical 9:16 — the standard share format for Reels, Stories, and Shorts. ## Complete JSON ```json { "comment": "Podcast audiogram with cover, waveform, title, subtitles", "resolution": "instagram-portrait", "quality": "high", "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/samples/podcast-cover.jpg", "fit": "cover" }, { "type": "audio", "src": "https://cdn.json2video.com/assets/samples/podcast-clip.mp3" }, { "type": "text", "text": "Episode 42\nBuilding in public", "y": 150, "style": "002", "settings": { "font-size": "75px", "color": "white", "font-weight": "800", "text-align": "center", "line-height": "85px" } }, { "type": "audiogram", "color": "#FF6B00", "amplitude": 6, "y": 1400, "height": 200, "width": 1000, "x": 40 }, { "type": "subtitles", "settings": { "style": "classic", "position": "bottom-center", "max-words-per-line": 4, "font-size": 50, "all-caps": false } } ] } ] } ``` ## How it works The scene has no explicit `duration` — it inherits the length of the longest element, which is the audio clip. Whatever your `podcast-clip.mp3` is, the video matches it exactly. The `image` element fills the canvas as the visual backdrop. `fit: "cover"` ensures the cover art scales to the 1080×1920 frame; if your cover is square (1080×1080), it crops the top and bottom. For square cover art, position it explicitly with `y: 420` and constrain its height instead. The `audiogram` element renders an animated waveform synced to whatever audio is playing in the scene — it doesn't own the audio, it visualises it. Properties: - `color` — bar colour. Match your brand accent. - `amplitude` — how reactive the bars are to audio loudness. Start at 5-6; increase for quiet recordings, decrease for loud / compressed ones. - `height`, `width`, `x`, `y` — position and dimensions in pixels. The `subtitles` element generates timed text from the audio via JSON2Video's automatic transcription. `style: "classic"`, `max-words-per-line: 4`, and `position: "bottom-center"` produce the standard reel-friendly caption look. The model defaults are good — `whisper` runs on the audio source automatically. ## Cost note Subtitles consume credits for the transcription pass. If you'll re-render the same audio multiple times during development, the cache means you only pay for transcription once — see the [caching deep-dive](@/guides/advanced/caching-deep-dive). ## See also - [Audiograms guide](@/guides/advanced/audiograms) - [Audiogram element reference](@/reference/json-syntax/element/audiogram) - [Subtitles element reference](@/reference/json-syntax/element/subtitles) - [Tutorial 8 — Automatic subtitles](@/tutorials/08-automatic-subtitles) # Automatic subtitles # Automatic subtitles A video with auto-generated subtitles burned in. The transcription is generated from a text-to-speech voiceover, then displayed as styled captions synced to the audio. No manual transcription required. ## Complete JSON ```json { "comment": "Video with auto-generated subtitles from TTS voice", "resolution": "instagram-portrait", "quality": "high", "scenes": [ { "elements": [ { "type": "video", "src": "https://cdn.json2video.com/assets/samples/loop-bg.mp4", "fit": "cover", "volume": 0 }, { "type": "voice", "model": "elevenlabs-flash-v2-5", "voice": "Rachel", "text": "JSON2Video makes it easy to add subtitles to any video. The transcription runs automatically — you just pick the style, and the engine handles the rest." }, { "type": "subtitles", "settings": { "style": "classic-progressive", "position": "bottom-center", "max-words-per-line": 3, "all-caps": true, "font-size": 65, "font-weight": "900", "line-color": "white", "word-color": "#FF6B00", "outline-color": "black", "outline-width": 4 } } ] } ] } ``` ## How it works The scene contains three layered elements: 1. A `video` background with `volume: 0` to mute its native audio (since the voice element will be the audio track). 2. A `voice` element with a text-to-speech voiceover. The scene's duration auto-matches the voice clip length. 3. A `subtitles` element that automatically transcribes whatever audio is in the scene — in this case, the voice element above. The subtitles transcription runs on the rendered audio, so it works equally well for TTS voice, uploaded narration, or any audio source. The engine uses Whisper-grade transcription by default; the result is highly accurate for clear English audio and supports many languages. ### Subtitle style The `style: "classic-progressive"` shows words one-by-one with a highlight on the current word — the high-engagement caption style used by major creators. Alternative styles: - `"classic"` — fixed multi-word lines, no per-word highlight. - `"karaoke"` — colour fills each word as it's spoken. - `"subtitle"` — minimalist bottom-of-frame text, no styling. ### Style settings - `max-words-per-line: 3` keeps each caption short — easier to read on a vertical phone screen. - `all-caps: true` is a stylistic choice popular in social content. - `outline-color`, `outline-width` make the text legible against busy video backgrounds. - `highlight-color: "#FF6B00"` colours the currently-spoken word — set to your brand accent. For the full property list, see the [subtitles element reference](@/reference/json-syntax/element/subtitles). ## Translating to a different language The transcription detects the language of the audio automatically. To force a specific language or translate, pass a `language` setting: ```json { "type": "subtitles", "language": "es", "settings": { "translate": "en", "style": "classic-progressive" } } ``` ## See also - [Subtitles element reference](@/reference/json-syntax/element/subtitles) - [Voice element reference](@/reference/json-syntax/element/voice) - [Tutorial 7 — Text-to-speech voiceover](@/tutorials/07-ai-voiceover) - [Tutorial 8 — Automatic subtitles](@/tutorials/08-automatic-subtitles) - [ElevenLabs integration](@/guides/third-party/elevenlabs) # Reference # Reference This page is being prepared. In the meantime, see [the overview](../) for an introduction to this section. # API Endpoints --- section: api-endpoints source: api/endpoints/ last_reviewed: 2026-05-12 --- # API endpoints > These are the only public endpoints. Other observable endpoints are internal and may break without notice. The JSON2Video API exposes three public resources under `https://api.json2video.com/v2/`: | Resource | Methods | Purpose | |----------|---------|---------| | `/movies` | `POST`, `GET` | Submit a render job, poll its status. | | `/templates` | `GET`, `POST`, `DELETE` | Manage reusable Movie JSON blueprints. | | `/media` | `GET`, `POST`, `PUT`, `DELETE` | Manage account-owned media assets. | All requests must include the `x-api-key` header. Get an API key from the [dashboard](https://json2video.com/dashboard). ## Movies - [Create movie (POST /v2/movies)](@/reference/api-endpoints/movies-create) - [Get movie status (GET /v2/movies)](@/reference/api-endpoints/movies-status) - [Delete movie (DELETE /v2/movies)](@/reference/api-endpoints/movies-delete) ## Templates - [List templates (GET /v2/templates)](@/reference/api-endpoints/templates-list) - [Create / update template (POST /v2/templates)](@/reference/api-endpoints/templates-create) - [Delete template (DELETE /v2/templates)](@/reference/api-endpoints/templates-delete) - [Template library (GET /v2/templates/library)](@/reference/api-endpoints/templates-library) ## Media - [List media (GET /v2/media)](@/reference/api-endpoints/media-list) - [Upload media (POST /v2/media/file)](@/reference/api-endpoints/media-upload) - [Move media (PUT /v2/media/file)](@/reference/api-endpoints/media-move) - [Delete media (DELETE /v2/media/file)](@/reference/api-endpoints/media-delete) ## Authentication Every request must carry the `x-api-key` header. Requests without a valid key return HTTP `403`. Keys are scoped to a single client account; never embed an API key in a client-side application. ## Base URL All endpoints live under `https://api.json2video.com/v2/`. This is the only public base URL. # Create movie (POST) --- endpoint: /v2/movies method: POST source: api/endpoints/movies-post/ last_reviewed: 2026-05-12 --- # Create movie `POST https://api.json2video.com/v2/movies` Submits a Movie JSON payload for rendering. The endpoint returns immediately with a project ID. The render runs asynchronously; clients poll [`GET /v2/movies`](@/reference/api-endpoints/movies-status) for completion. ## Request ### Headers | Header | Required | Value | |--------|----------|-------| | `x-api-key` | yes | API key issued from the dashboard. | | `Content-Type` | yes | `application/json` | ### Query parameters None. ### Body A JSON object that follows the [Movie JSON syntax](@/reference/json-syntax/movie). The minimum valid body has a single `scenes` array (or, equivalently, a movie-level `elements` array with no scenes); everything else has defaults. ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "video", "src": "https://example.com/video.mp4" } ] } ] } ``` The body may also reference a saved template: ```json { "template": "your-template-id", "variables": { "headline": "Hello" } } ``` ### Idempotency `POST /v2/movies` is **not** idempotent. Each call creates a new project, even with the same body. To deduplicate at the asset layer, set `cache: true` (default) on elements and on the movie — identical assets and identical movies are served from cache without re-rendering. ## Response ### 200 OK ```json { "success": true, "project": "JkGxEoPRF9EgRb32", "timestamp": "2026-05-12T10:49:52.924Z" } ``` | Field | Type | Description | |-------|------|-------------| | `success` | boolean | Always `true` on 200. | | `project` | string | 16-character project identifier. Use it to poll status. | | `timestamp` | string | ISO-8601 timestamp of the submission. | ## Errors | Status | Message | Cause | |--------|---------|-------| | `400` | `No movie JSON received` | Empty request body. | | `400` | `Error parsing movie JSON or the movie was empty` | Body is not valid JSON. | | `400` | `No valid movie JSON received` | Body parsed to `null` or non-object. | | `401` | `You exceeded the quota of movies in your plan. Please upgrade your plan to continue.` | Render quota exhausted. | | `401` | `Movie is larger ({w}x{h}) than your plan allowance ({w}x{h})` | Resolution exceeds account plan. | | `404` | `Template not found` | Body references a `template` ID that does not exist or belongs to another account. | | `500` | `Error creating movie: …` | Server error. Retry with exponential backoff. | | `500` | `Error starting subprocess` | The render could not be started. Retry with exponential backoff. | ## Examples ### cURL ```bash curl --location --request POST 'https://api.json2video.com/v2/movies' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data-raw '{ "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "text", "text": "Hello", "duration": 5 } ] } ] }' ``` ### Node.js ```javascript const res = await fetch("https://api.json2video.com/v2/movies", { method: "POST", headers: { "x-api-key": process.env.J2V_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ resolution: "full-hd", scenes: [{ elements: [{ type: "text", text: "Hello", duration: 5 }] }] }) }); const { project } = await res.json(); ``` ### Polling pattern After submission, poll [`GET /v2/movies?project={id}`](@/reference/api-endpoints/movies-status) every 5–10 seconds until `status` is `done`, `error`, or `timeout`. ```javascript async function waitForRender(projectId) { while (true) { const r = await fetch(`https://api.json2video.com/v2/movies?project=${projectId}`, { headers: { "x-api-key": process.env.J2V_API_KEY } }); const { movie } = await r.json(); if (["done", "error", "timeout"].includes(movie.status)) return movie; await new Promise(r => setTimeout(r, 5000)); } } ``` For production workflows, prefer a webhook destination over polling. See [Webhooks](@/reference/webhooks). # Get movie status (GET) --- endpoint: /v2/movies method: GET source: api/endpoints/movies-get/ last_reviewed: 2026-08-16 --- # Get movie status `GET https://api.json2video.com/v2/movies` Retrieves the status and metadata of a single project, or lists all projects for the authenticated account. ## Request ### Headers | Header | Required | Value | |--------|----------|-------| | `x-api-key` | yes | API key issued from the dashboard. | ### Query parameters | Parameter | Type | Description | |-----------|------|-------------| | `project` | string | 16-character project identifier. When present, returns a single movie. | | `id` | string | Alias for `project`. | | `format` | string | `simple` removes the original `json` payload from the response. | | `date_start` / `from` | ISO-8601 | List mode: start of date range. Default: first day of current month. | | `date_end` / `to` | ISO-8601 | List mode: end of date range. Default: end of current day. Maximum range: 93 days. | | `limit` | integer | List mode: page size, 1–100. Default: 100. | | `next_token` | string | List mode: pagination cursor returned in the previous response. | ### Body None. ## Response ### 200 OK — single project ```json { "success": true, "movie": { "success": true, "status": "done", "message": "", "project": "JkGxEoPRF9EgRb32", "url": "https://assets.json2video.com/clients/xxxxxxxx/renders/2026-05-12-36066.mp4", "thumbnail": "https://assets.json2video.com/clients/xxxxxxxx/renders/2026-05-12-36066.jpg", "ass": "https://assets.json2video.com/clients/xxxxxxxx/renders/2026-05-12-36066.ass", "created_at": "2026-05-12T07:41:41.946Z", "ended_at": "2026-05-12T07:44:57.108Z", "duration": 108.2, "size": 6876189, "width": 1080, "height": 1920, "rendering_time": 195, "client-data": {}, "consumed_credits": [] }, "remaining_quota": { "movies": 0, "drafts": 0, "time": 1807 } } ``` ### 200 OK — list of projects ```json { "success": true, "from": "2026-05-01T00:00:00.000Z", "to": "2026-05-12T23:59:59.999Z", "count": 25, "limit": 100, "has_next": false, "has_prev": false, "next_token": null, "movies": [ { "...": "movie object as above" } ], "remaining_quota": { "time": 1807 } } ``` ### Status enum | Status | Meaning | |--------|---------| | `pending` | Job is queued. No worker has picked it up yet. | | `running` | Render is in progress. | | `done` | Render finished successfully. `url` is the final MP4. | | `error` | Render failed. `message` carries the reason. | | `timeout` | Computed client-side when `status=running` and `created_at` is older than 15 minutes. The server still keeps `status=running` internally; clients should treat `timeout` the same as a fatal error. | ### Movie object fields | Field | Type | Notes | |-------|------|-------| | `success` | boolean | `true` iff the render itself succeeded. | | `status` | string | See status enum. | | `message` | string | Error message when `success=false`; informational otherwise. | | `project` | string | 16-character project ID. | | `url` | string\|null | Public URL of the rendered MP4. `null` until done, and again once the video file is gone. | | `thumbnail` | string\|null | Public URL of the movie thumbnail — a single frame taken 2 seconds in, or at the timecode set by the movie's [`thumbnail`](@/reference/json-syntax/movie) property. JPEG, or PNG for movies with a transparent background. `null` until done, and again once the file is gone. | | `ass` | string\|boolean | Public URL of the generated `.ass` subtitle file, if any; `false` if none. | | `created_at` | string | ISO-8601 submission time. | | `ended_at` | string\|null | ISO-8601 completion time. `null` until done. | | `deleted_at` | string\|null | ISO-8601 time the video file was deleted through [`DELETE /v2/movies`](@/reference/api-endpoints/movies-delete); `null` otherwise. The entry itself, including `consumed_credits`, is kept. | | `duration` | number | Output duration in seconds. | | `size` | number | Output size in bytes. | | `width` / `height` | integer | Output pixel dimensions. | | `rendering_time` | integer\|null | Seconds between `created_at` and `ended_at`. | | `client-data` | object | The `client-data` from the submitted Movie JSON, returned verbatim. | | `consumed_credits` | array | Per-step credit consumption breakdown. Present for movies created after 2025-07-27. | ## Errors | Status | Message | Cause | |--------|---------|-------| | `400` | `Project ID must be a 16-character string. Received ID: '…' (length: N)` | `project` / `id` is wrong shape. | | `400` | `Invalid start date` | `date_start` / `from` is not parseable. | | `400` | `Invalid end date` | `date_end` / `to` is not parseable. | | `400` | `Maximum date range is 3 months.` | Range exceeds 93 days. | | `403` | `Invalid token` | Admin-only query parameter without a valid token. | A project that has not been found by the database returns a 200 with `movie.status = "error"` and a descriptive `message`. ## Examples ### Poll a single project ```bash curl --location --request GET \ 'https://api.json2video.com/v2/movies?project=JkGxEoPRF9EgRb32' \ --header 'x-api-key: YOUR_API_KEY' ``` ### List the last 50 projects ```bash curl --location --request GET \ 'https://api.json2video.com/v2/movies?limit=50&date_start=2026-04-12&date_end=2026-05-12' \ --header 'x-api-key: YOUR_API_KEY' ``` ### Paginate ```bash # First page curl 'https://api.json2video.com/v2/movies?limit=100' -H 'x-api-key: …' # Subsequent pages: pass the previous response's `next_token` curl 'https://api.json2video.com/v2/movies?limit=100&next_token=AAA…' -H 'x-api-key: …' ``` # Delete movie (DELETE) --- endpoint: /v2/movies method: DELETE source: api/endpoints/movies-get/ last_reviewed: 2026-07-27 --- # Delete movie `DELETE https://api.json2video.com/v2/movies` Deletes the rendered video file right away, instead of waiting for the automatic 7-day expiry. Use it to release storage as soon as you have downloaded or forwarded the video. The movie entry itself is **kept**: its status, timings and consumed credits stay in your render history, so your usage record is not rewritten. Only the video file is removed — `url` becomes `null` and a `deleted_at` timestamp is added. Both are visible through [`GET /v2/movies`](@/reference/api-endpoints/movies-status). The operation is idempotent: deleting the same movie twice, or deleting one whose file already expired, returns success. ## Request ### Headers | Header | Required | Value | |--------|----------|-------| | `x-api-key` | yes | API key issued from the dashboard. Requires role `render`, `editor`, `manager`, or `admin`. | ### Query parameters | Parameter | Type | Description | |-----------|------|-------------| | `project` | string | Required. The 16-character project ID returned by `POST /v2/movies`. | | `id` | string | Alias for `project`. | ### Body None. ## Response ### 200 OK ```json { "success": true, "project": "AbCdEfGhIjKlMnOp", "deleted_at": "2026-07-27T16:04:11.312Z", "timestamp": "2026-07-27T16:04:11.480Z" } ``` ## Errors Every failure comes back as HTTP `400` with `success: false` and a `message` describing the cause. | Message | Cause | |---------|-------| | `Error: API Key not provided` | Missing `x-api-key` header. | | `A 16-character project ID must be provided in the 'project' query parameter` | Missing or malformed `project`. | | `Insufficient permissions` | API key role is below `render`. | | `Movie {project} not found` | No such movie on this account. Movies belonging to another account also report as not found. | | `Movie {project} is still rendering. Wait until it finishes before deleting it.` | The render is `pending` or `running`. Deleting cannot cancel a render — poll until it reaches `done`, `error` or `timeout`, then delete. | | `Error deleting the movie files. Please try again.` | Storage error. Nothing was marked as deleted; retrying is safe. | ## Examples ### Delete a rendered movie ```bash curl --location --request DELETE \ 'https://api.json2video.com/v2/movies?project=AbCdEfGhIjKlMnOp' \ --header 'x-api-key: YOUR_API_KEY' ``` ### Render, download, then delete ```bash # 1. Poll until the render is finished curl -s 'https://api.json2video.com/v2/movies?project=AbCdEfGhIjKlMnOp' \ --header 'x-api-key: YOUR_API_KEY' # 2. Download the video from movie.url curl -o movie.mp4 'https://json2video-cdn1.s3.amazonaws.com/clients/.../movie.mp4' # 3. Release the storage immediately curl --location --request DELETE \ 'https://api.json2video.com/v2/movies?project=AbCdEfGhIjKlMnOp' \ --header 'x-api-key: YOUR_API_KEY' ``` # List templates (GET) --- endpoint: /v2/templates method: GET source: api/endpoints/templates/ last_reviewed: 2026-05-12 --- # List templates `GET https://api.json2video.com/v2/templates` Retrieves a single template by ID, or lists all templates owned by the authenticated account. Templates are reusable Movie JSON blueprints with variable placeholders. ## Request ### Headers | Header | Required | Value | |--------|----------|-------| | `x-api-key` | yes | API key issued from the dashboard. Requires role `render`, `editor`, `manager`, or `admin`. | ### Query parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | string | Template ID. When present, returns a single template. | | `scopes` | string | Comma-separated list of scopes to fetch. Defaults to `movie`. Only used with `id`. | | `format` | string | When fetching a single template: `make` returns variables shaped for Make.com modules; `jsonschema` returns a JSON Schema describing the variables. | | `tag` | string | List mode: filter by tag. | ### Body None. ## Response ### 200 OK — single template ```json { "success": true, "template": { "id": "abc123def456ghi789jk", "name": "Product showcase", "tags": ["showcase"], "movie": "{\"resolution\":\"full-hd\",\"scenes\":[]}", "created_at": "2026-04-01T12:00:00.000Z", "updated_at": "2026-04-15T09:30:00.000Z" }, "timestamp": "2026-05-12T10:49:52.924Z" } ``` When `format=jsonschema` the `template.variables` field contains a JSON Schema document derived from the template's `variables` object. When `format=make` the field contains Make.com module field descriptors. ### 200 OK — list ```json { "success": true, "count": 12, "templates": [ { "id": "abc123def456ghi789jk", "name": "Product showcase", "tags": ["showcase"], "created_at": "2026-04-01T12:00:00.000Z", "updated_at": "2026-04-15T09:30:00.000Z" } ], "timestamp": "2026-05-12T10:49:52.924Z" } ``` The list omits each template's `movie` payload; fetch a single template by `id` to get the full body. The list is sorted by `updated_at` descending. ## Errors | Status | Message | Cause | |--------|---------|-------| | `403` | `Insufficient permissions` | API key role is below `render`. | | `404` | `Template {id} not found` | Unknown template ID. | | `404` | `Error retrieving templates` | Server error. Retry with exponential backoff. | ## Examples ### List all templates ```bash curl --location \ --request GET 'https://api.json2video.com/v2/templates' \ --header 'x-api-key: YOUR_API_KEY' ``` ### Fetch a single template ```bash curl --location \ --request GET 'https://api.json2video.com/v2/templates?id=abc123def456ghi789jk' \ --header 'x-api-key: YOUR_API_KEY' ``` ### Fetch a template's variables as JSON Schema ```bash curl --location \ --request GET 'https://api.json2video.com/v2/templates?id=abc123def456ghi789jk&format=jsonschema' \ --header 'x-api-key: YOUR_API_KEY' ``` # Create / update template (POST) --- endpoint: /v2/templates method: POST source: api/endpoints/templates/ last_reviewed: 2026-05-12 --- # Create or update template `POST https://api.json2video.com/v2/templates` Creates a new template or updates an existing one. Templates store a reusable Movie JSON document with variable placeholders. A separate sub-action duplicates an existing template into the caller's account. ## Request ### Headers | Header | Required | Value | |--------|----------|-------| | `x-api-key` | yes | API key issued from the dashboard. Requires role `editor`, `manager`, or `admin`. | | `Content-Type` | yes | `application/json` | ### Query parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | string | Template ID. Present → update. Absent → create with a generated 20-character ID. | | `action` | string | When set to `duplicate`, copies the referenced template into the caller's account. Requires `id`. | ### Body — create / update ```json { "name": "Product showcase", "tags": ["showcase", "demo"], "movie": { "resolution": "full-hd", "scenes": [] } } ``` | Field | Type | Description | |-------|------|-------------| | `name` | string | Required on create. Maximum 100 characters. | | `tags` | string\|array | Either a comma-separated string or an array of strings. Tags are trimmed; no length limit per tag. | | `movie` | object\|string | Movie JSON. Either a parsed object or a stringified JSON. Up to ~100 KB. If `movie.template` is set, the referenced template's body is loaded and the supplied `variables` are deep-merged on top. | ### Body — duplicate ```json { "name": "My copy of Product showcase", "variables": { "headline": "Hello" } } ``` `name` defaults to the original name with `" (custom)"` appended. `variables` are deep-merged into the source template's variables. ## Response ### 200 OK — create / update ```json { "success": true, "templateId": "abc123def456ghi789jk", "timestamp": "2026-05-12T10:49:52.924Z" } ``` ### 200 OK — duplicate ```json { "success": true, "templateId": "xyz987uvw654rst321qp", "name": "Product showcase (custom)", "timestamp": "2026-05-12T10:49:52.924Z" } ``` ## Errors | Status | Message | Cause | |--------|---------|-------| | `400` | `No payload provided` | Empty body. | | `400` | `Tags must be a string or an array` | `tags` is the wrong type. | | `400` | `Payload movie must be a JSON string or JSON object` | `movie` is the wrong type. | | `400` | `No template ID provided` | `action=duplicate` without `id`. | | `403` | `Insufficient permissions` | API key role is below `editor`. | | `403` | `Template {id} is not owned by you` | Update attempt against another account's template. | | `404` | `Template {id} not found` | Unknown source template (update or duplicate). | | `500` | `Template movie is not valid JSON or it's too large` | Stringified `movie` failed to parse. | | `500` | `Source template movie is not valid JSON` | Duplicate source has a corrupted body. | | `500` | `Error saving template` | Server error. Retry with exponential backoff. | ## Examples ### Create ```bash curl --location --request POST 'https://api.json2video.com/v2/templates' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "Product showcase", "tags": ["demo"], "movie": { "resolution": "full-hd", "variables": { "headline": "Sample" }, "scenes": [ { "elements": [{ "type": "text", "text": "{{headline}}" }] } ] } }' ``` ### Update ```bash curl --location --request POST \ 'https://api.json2video.com/v2/templates?id=abc123def456ghi789jk' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data-raw '{"name": "Product showcase v2"}' ``` ### Duplicate ```bash curl --location --request POST \ 'https://api.json2video.com/v2/templates?id=abc123def456ghi789jk&action=duplicate' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data-raw '{"variables": {"headline": "Hello"}}' ``` # Delete template (DELETE) --- endpoint: /v2/templates method: DELETE source: api/endpoints/templates/ last_reviewed: 2026-05-12 --- # Delete template `DELETE https://api.json2video.com/v2/templates` Removes a template owned by the authenticated account. Both the database row and the S3-backed movie payload are deleted. Templates in use by running jobs are not protected — those jobs continue to use their already-resolved movie body. ## Request ### Headers | Header | Required | Value | |--------|----------|-------| | `x-api-key` | yes | API key issued from the dashboard. Requires role `editor`, `manager`, or `admin`. | ### Query parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | string | Required. The template ID to delete. | ### Body None. ## Response ### 200 OK ```json { "success": true, "timestamp": "2026-05-12T10:49:52.924Z" } ``` ## Errors | Status | Message | Cause | |--------|---------|-------| | `400` | `No template ID provided` | Missing `id` query parameter. | | `403` | `Insufficient permissions` | API key role is below `editor`. | | `500` | `Error deleting template` | Server error. Retry with exponential backoff. | ## Examples ### Delete a template ```bash curl --location --request DELETE \ 'https://api.json2video.com/v2/templates?id=abc123def456ghi789jk' \ --header 'x-api-key: YOUR_API_KEY' ``` # Template library (GET) --- endpoint: /v2/templates/library method: GET source: api/endpoints/templates-library/ last_reviewed: 2026-07-08 --- # Template library `GET https://api.json2video.com/v2/templates/library` Lists the public template library — the curated gallery of ready-to-use templates published by JSON2Video. Each entry includes an example video, a thumbnail and the template's dimensions. Copy any of them into your own account with [`POST /v2/templates`](@/reference/api-endpoints/templates-create) using `action=duplicate` and the template `id` returned here. ## Request ### Headers | Header | Required | Value | |--------|----------|-------| | `x-api-key` | yes | API key issued from the dashboard. Requires role `render`, `editor`, `manager`, or `admin`. | ### Query parameters | Parameter | Type | Description | |-----------|------|-------------| | `tags` | string | Optional. Comma-separated list of tags. In addition to every `published` template, the response includes any template tagged with one of these values. | ### Body None. ## Response ### 200 OK ```json { "success": true, "count": 24, "templates": [ { "id": "abc123def456ghi789jk", "name": "Product showcase", "tags": ["showcase", "published"], "width": 1920, "height": 1080, "aspect_ratio": 1.78, "prompt": true, "created_at": "2026-04-01T12:00:00.000Z", "updated_at": "2026-04-15T09:30:00.000Z", "video_url": "https://json2video-cdn2.s3.amazonaws.com/templates/abc123def456ghi789jk/example.mp4", "thumbnail_url": "https://json2video-cdn2.s3.amazonaws.com/templates/abc123def456ghi789jk/thumbnail.jpg" } ], "timestamp": "2026-07-08T10:49:52.924Z" } ``` The list is sorted by `updated_at` descending. Each template's fields: | Field | Type | Description | |-------|------|-------------| | `id` | string | Template ID. Use it to render the template, or to duplicate it into your account. | | `name` | string | Human-readable template name. | | `tags` | string[] | Tags assigned to the template. | | `width` | integer | Frame width in pixels. | | `height` | integer | Frame height in pixels. | | `aspect_ratio` | number | `width / height`, rounded to 2 decimals. | | `prompt` | boolean | Whether the template ships with an AI prompt. | | `created_at` | string | ISO 8601 creation timestamp. | | `updated_at` | string | ISO 8601 last-update timestamp. | | `video_url` | string | URL of an example render (MP4). | | `thumbnail_url` | string | URL of a preview thumbnail (JPG). | The library never returns the template's `movie` payload. To fetch the full body, duplicate the template into your account and then read it with [`GET /v2/templates`](@/reference/api-endpoints/templates-list). ## Errors | Status | Message | Cause | |--------|---------|-------| | `400` | `Tags query parameter must be a string with tags separated by commas` | The `tags` parameter could not be parsed. | | `403` | `Insufficient permissions` | API key role is below `render`. | | `404` | `Error retrieving templates` | Server error. Retry with exponential backoff. | ## Examples ### List the whole library ```bash curl --location \ --request GET 'https://api.json2video.com/v2/templates/library' \ --header 'x-api-key: YOUR_API_KEY' ``` ### Filter by tags ```bash curl --location \ --request GET 'https://api.json2video.com/v2/templates/library?tags=intro,social' \ --header 'x-api-key: YOUR_API_KEY' ``` # List media (GET) --- endpoint: /v2/media method: GET source: api/endpoints/media/ last_reviewed: 2026-05-12 --- # List media `GET https://api.json2video.com/v2/media` The media resource has three sub-paths: - `GET /v2/media` — storage info (used bytes, free allowance, billing state). - `GET /v2/media/file` — single file metadata. - `GET /v2/media/folder` — folder listing (contents or tree). All sub-paths require the `x-api-key` header with role `render`, `editor`, `manager`, or `admin`. ## Request — storage info ### Path `GET /v2/media` ### Query parameters None. ## Response — storage info ### 200 OK ```json { "success": true, "storage": { "used_bytes": 12582912, "free_allowance": 52428800, "credits_per_week": 0, "blocked": false, "blocked_at": null } } ``` | Field | Type | Description | |-------|------|-------------| | `used_bytes` | integer | Total bytes used by the account's non-temporary files. | | `free_allowance` | integer | Bytes included for free. Currently 50 MB. | | `credits_per_week` | integer | Weekly credit consumption based on usage above the free allowance (10 credits per GiB-week, rounded up). | | `blocked` | boolean | When `true`, uploads are rejected. | | `blocked_at` | string\|null | ISO-8601 timestamp at which storage was blocked. | ## Request — single file ### Path `GET /v2/media/file` ### Query parameters | Parameter | Type | Description | |-----------|------|-------------| | `path` | string | Required. Path of the file, e.g. `videos/clip.mp4`. | ## Response — single file ### 200 OK ```json { "success": true, "file": { "name": "clip.mp4", "folder": "videos", "type": "video", "contentType": "video/mp4", "size": 3145728, "url": "https://media.json2video.com/{client_id}/files/videos/clip.mp4", "thumbnailUrl": null, "status": "uploaded", "temporary": false, "created_at": "2026-04-12T08:15:00.000Z" } } ``` `type` is one of `image`, `video`, `audio`, `other`. `status` is `pending` (awaiting upload) or `uploaded`. ## Request — folder ### Path `GET /v2/media/folder` ### Query parameters | Parameter | Type | Description | |-----------|------|-------------| | `path` / `folder` | string | Folder to list. Default: `/`. | | `tree` | string | `true` returns a flat list of folders with per-folder stats; default returns folder contents. | | `type` | string | Filter by media type: `image`, `video`, `audio`, `other`. | | `q` | string | Filename substring filter (case-insensitive). | | `page` | integer | Zero-based page index. Default: 0. | | `page_size` | integer | Items per page. Default: 20. | ## Response — folder contents ### 200 OK — contents ```json { "success": true, "path": "videos", "total_size": 8388608, "total_files": 4, "total": 4, "page": 0, "page_size": 20, "folders": ["raw"], "files": [ { "name": "clip.mp4", "folder": "videos", "type": "video", "contentType": "video/mp4", "size": 3145728, "url": "https://media.json2video.com/{client_id}/files/videos/clip.mp4", "thumbnailUrl": null, "status": "uploaded", "created_at": "2026-04-12T08:15:00.000Z" } ] } ``` ### 200 OK — tree ```json { "success": true, "tree": [ { "path": "/", "files": 2, "size": 1048576 }, { "path": "temp", "files": 0, "size": 0 }, { "path": "videos", "files": 4, "size": 8388608 } ] } ``` ## Errors | Status | Message | Cause | |--------|---------|-------| | `400` | `path is required` | Missing `path` on `GET /v2/media/file`. | | `400` | `Invalid path: no filename` | `path` does not contain a filename. | | `403` | `Insufficient permissions` | API key role is below `render`. | | `404` | `File not found` | Path does not exist in the account. | ## Examples ### Storage usage ```bash curl --location --request GET 'https://api.json2video.com/v2/media' \ --header 'x-api-key: YOUR_API_KEY' ``` ### List a folder ```bash curl --location --request GET \ 'https://api.json2video.com/v2/media/folder?path=videos&page=0&page_size=50' \ --header 'x-api-key: YOUR_API_KEY' ``` ### Folder tree ```bash curl --location --request GET \ 'https://api.json2video.com/v2/media/folder?tree=true' \ --header 'x-api-key: YOUR_API_KEY' ``` ### Single file ```bash curl --location --request GET \ 'https://api.json2video.com/v2/media/file?path=videos/clip.mp4' \ --header 'x-api-key: YOUR_API_KEY' ``` # Upload media (POST) --- endpoint: /v2/media/file method: POST source: api/endpoints/media/ last_reviewed: 2026-05-12 --- # Upload media `POST https://api.json2video.com/v2/media/file` Requests a presigned upload URL for a new media file. The endpoint registers the file in `pending` state and returns a presigned `PUT` URL that the client uses to upload the bytes directly. Files larger than 500 MB are rejected. There is also `POST /v2/media/folder` for creating folder markers. ## Request — upload URL ### Path `POST /v2/media/file` ### Headers | Header | Required | Value | |--------|----------|-------| | `x-api-key` | yes | API key issued from the dashboard. Requires role `render`, `editor`, `manager`, or `admin`. | | `Content-Type` | yes | `application/json` | ### Body ```json { "name": "clip.mp4", "contentType": "video/mp4", "size": 3145728, "folder": "videos" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | yes | Filename. Non-`[a-zA-Z0-9._-]` characters are replaced with `_`. | | `contentType` | string | yes | MIME type. Determines the `type` (`image`/`video`/`audio`/`other`) shown by `GET /v2/media`. | | `size` | integer | yes | File size in bytes. Must be `> 0` and `<= 500 MB`. | | `folder` | string | no | Target folder. If empty, the file lands at the root. The special folder `temp` flags the file as temporary (auto-deleted by lifecycle rules). | ## Response — upload URL ### 200 OK ```json { "success": true, "uploadUrl": "https://json2video-media.s3.amazonaws.com/...&X-Amz-Signature=...", "fileUrl": "https://media.json2video.com/{client_id}/files/videos/clip.mp4", "expiresIn": 120 } ``` The client must immediately `PUT` the file bytes to `uploadUrl`. The URL expires 120 seconds after issue. After a successful upload, the record's `status` transitions to `uploaded` (asynchronously confirmed by the server). ## Request — create folder ### Path `POST /v2/media/folder` ### Body ```json { "folder": "videos/raw" } ``` Folder names are sanitised to `[a-zA-Z0-9/_-]+`. Existing folders return `200` with `message: "Folder already exists"`. ## Response — create folder ### 200 OK ```json { "success": true, "timestamp": "2026-05-12T10:49:52.924Z" } ``` ## Errors | Status | Message | Cause | |--------|---------|-------| | `400` | `name is required` | Missing `name`. | | `400` | `contentType is required` | Missing `contentType`. | | `400` | `size is required and must be a positive number` | Missing or invalid `size`. | | `400` | `folder is required` | Missing `folder` on `POST /v2/media/folder`. | | `400` | `Invalid folder name` | Folder name sanitises to empty. | | `403` | `Storage is blocked. Add credits to continue uploading.` | Account storage is blocked. | | `403` | `Insufficient permissions` | API key role is below `render`. | | `409` | `A file with this name already exists. Delete it first.` | Filename collision. | | `413` | `File exceeds maximum size of 500 MB` | `size` > 500 MB. | ## Examples ### Two-step upload ```bash # 1. Ask for a presigned URL RESP=$(curl -s --location --request POST 'https://api.json2video.com/v2/media/file' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "clip.mp4", "contentType": "video/mp4", "size": 3145728, "folder": "videos" }') UPLOAD_URL=$(echo "$RESP" | jq -r .uploadUrl) # 2. Upload the bytes directly to S3 curl --location --request PUT "$UPLOAD_URL" \ --header 'Content-Type: video/mp4' \ --upload-file ./clip.mp4 ``` ### Create a folder ```bash curl --location --request POST 'https://api.json2video.com/v2/media/folder' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data-raw '{"folder": "videos/raw"}' ``` # Move media (PUT) --- endpoint: /v2/media/file method: PUT source: api/endpoints/media/ last_reviewed: 2026-05-12 --- # Move media `PUT https://api.json2video.com/v2/media/file` Moves a single file from one folder to another within the authenticated account. The file is copied server-side in S3, the database record is rewritten, and the source is deleted. Moves to `temp/` mark the file as temporary (auto-deleted by lifecycle rules). ## Request ### Headers | Header | Required | Value | |--------|----------|-------| | `x-api-key` | yes | API key issued from the dashboard. Requires role `render`, `editor`, `manager`, or `admin`. | | `Content-Type` | yes | `application/json` | ### Body ```json { "name": "clip.mp4", "folder": "videos", "destination": "videos/raw" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | yes | Filename. | | `folder` | string | no | Source folder. Empty means root. | | `destination` | string | yes | Destination folder. Empty string moves to root. The special folder `temp` flags the file as temporary. | ## Response ### 200 OK ```json { "success": true, "timestamp": "2026-05-12T10:49:52.924Z" } ``` ## Errors | Status | Message | Cause | |--------|---------|-------| | `400` | `name is required` | Missing `name`. | | `400` | `destination is required` | Missing `destination`. | | `403` | `Insufficient permissions` | API key role is below `render`. | | `404` | `File not found` | Source path does not exist. | | `409` | `A file with this name already exists in the destination folder` | Destination collision. | ## Examples ### Move a file ```bash curl --location --request PUT 'https://api.json2video.com/v2/media/file' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "clip.mp4", "folder": "videos", "destination": "videos/raw" }' ``` ### Move to root ```bash curl --location --request PUT 'https://api.json2video.com/v2/media/file' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data-raw '{"name": "clip.mp4", "folder": "videos", "destination": ""}' ``` # Delete media (DELETE) --- endpoint: /v2/media/file method: DELETE source: api/endpoints/media/ last_reviewed: 2026-05-12 --- # Delete media `DELETE https://api.json2video.com/v2/media/file` Removes a single file from S3 and from the account's media index. `DELETE /v2/media/folder` removes empty folder markers; non-empty folders must be cleared first. ## Request — delete file ### Path `DELETE /v2/media/file` ### Headers | Header | Required | Value | |--------|----------|-------| | `x-api-key` | yes | API key issued from the dashboard. Requires role `render`, `editor`, `manager`, or `admin`. | | `Content-Type` | yes | `application/json` | ### Body ```json { "name": "clip.mp4", "folder": "videos" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | yes | Filename. | | `folder` | string | no | Folder containing the file. Empty means root. | ## Request — delete folder ### Path `DELETE /v2/media/folder` ### Body ```json { "folder": "videos/raw" } ``` The folder must be empty. The `temp` folder cannot be deleted. ## Response ### 200 OK ```json { "success": true, "timestamp": "2026-05-12T10:49:52.924Z" } ``` ## Errors | Status | Message | Cause | |--------|---------|-------| | `400` | `name is required` | Missing `name` on `DELETE /v2/media/file`. | | `400` | `folder is required` | Missing `folder` on `DELETE /v2/media/folder`. | | `400` | `Cannot delete root folder` | Attempted to delete `/`. | | `400` | `Cannot delete the temp folder` | Attempted to delete `temp`. | | `400` | `Folder is not empty. Delete all files first.` | Folder still contains files or sub-folders. | | `403` | `Insufficient permissions` | API key role is below `render`. | | `404` | `File not found` | File path does not exist. | ## Examples ### Delete a file ```bash curl --location --request DELETE 'https://api.json2video.com/v2/media/file' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data-raw '{"name": "clip.mp4", "folder": "videos"}' ``` ### Delete a folder ```bash curl --location --request DELETE 'https://api.json2video.com/v2/media/folder' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data-raw '{"folder": "videos/raw"}' ``` # JSON Syntax --- section: json-syntax source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-05-12 --- # JSON syntax This section documents every field accepted in the Movie JSON submitted to [`POST /v2/movies`](@/reference/api-endpoints/movies-create). ## Top-level structures - [Movie](@/reference/json-syntax/movie) — the root object. - [Scene](@/reference/json-syntax/scene) — one segment of the movie's timeline. - [Element](@/reference/json-syntax/element) — the renderable units that sit inside scenes. ## Elements The element type is set by the `type` discriminator. Documented types: - [`image`](@/reference/json-syntax/element/image) — static image. - [`video`](@/reference/json-syntax/element/video) — video clip. - [`text`](@/reference/json-syntax/element/text) — styled text overlay. - [`html`](@/reference/json-syntax/element/html) — HTML snippet or full webpage capture. - [`component`](@/reference/json-syntax/element/component) — animated component from the library. - [`audio`](@/reference/json-syntax/element/audio) — audio track. - [`voice`](@/reference/json-syntax/element/voice) — text-to-speech voiceover. - [`audiogram`](@/reference/json-syntax/element/audiogram) — audio waveform visualisation. - [`subtitles`](@/reference/json-syntax/element/subtitles) — automatic or manual subtitles. > The `template` element type is deprecated. Use the [`template` movie-level field](@/reference/json-syntax/movie#template) to reference a saved template instead. ## Conventions - Property names use kebab-case where they predate JSON Schema strict naming (`client-data`, `fade-in`, `pan-distance`, …). Other names are flat camelCase or simple identifiers. - Variable interpolation: `{{name}}` substitutes the value of a variable found in the nearest enclosing scope (element → scene → movie). - Boolean defaults: every `cache` flag defaults to `true`. Set it to `false` to bypass the render cache. - Time values: all durations and offsets are in seconds; supports decimals. # Movie --- type: movie source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-07-14 --- # Movie **Type:** object The `movie` object is the top-level structure submitted to [`POST /v2/movies`](@/reference/api-endpoints/movies-create). It defines the canvas, the playback timeline (scenes), any global elements that overlay every scene, the export destinations, and arbitrary client-side metadata. ## Required properties - `scenes` ## Properties ### cache If `true`, the rendered output is reused when the same input has been rendered before. If `false`, the render runs from scratch. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | ### client-data Key-value pairs returned verbatim in `GET /v2/movies` responses and in webhook payloads. Use it to correlate a render with your own record IDs. Values can be any valid JSON type. > **Property name.** The hyphenated `client-data` is the correct form. `client_data` (underscore) is not accepted. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### comment Free-form note attached to the movie. Ignored by the renderer. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### elements Elements that overlay every scene. Stacking order in the array determines layering — later items render on top. If the movie has no `scenes`, these movie-level elements are rendered on their own as a single scene. | | | |--------------|-------------| | **Type** | array | | **Required** | No | #### Array items Each item is one of the element types defined in [Element](@/reference/json-syntax/element). The discriminator is the `type` field. | `type` value | Schema | |--------------|--------| | `image` | [Image element](@/reference/json-syntax/element/image) | | `video` | [Video element](@/reference/json-syntax/element/video) | | `text` | [Text element](@/reference/json-syntax/element/text) | | `html` | [HTML element](@/reference/json-syntax/element/html) | | `component` | [Component element](@/reference/json-syntax/element/component) | | `audio` | [Audio element](@/reference/json-syntax/element/audio) | | `voice` | [Voice element](@/reference/json-syntax/element/voice) | | `audiogram` | [Audiogram element](@/reference/json-syntax/element/audiogram) | | `subtitles` | [Subtitles element](@/reference/json-syntax/element/subtitles) | ### exports Destinations for the rendered movie. Each item describes one export pipeline; multiple items run sequentially. | | | |--------------|-------------| | **Type** | array | | **Required** | No | #### Array items | Property | Type | Description | |----------|------|-------------| | `destinations` | array | One or more destination objects. Each destination has a `type` (`webhook`, `ftp`, `sftp`, `email`) and the fields it requires, OR an `id` referencing a Dashboard connection. See [Webhooks](@/reference/webhooks) for the webhook destination contract. | ### fps Frames per second of the output video. | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Default Value** | `25` | ### height Height of the movie in pixels. Required and applicable only when `resolution` is `custom`. Range: 50–3840. Bound by the account plan's maximum resolution. | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Default Value** | `360` | | **Minimum Value** | 50 | | **Maximum Value** | 3840 | ### id Unique identifier for the movie. Auto-generated when omitted. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"@randomString"` | ### preload List of assets to fetch or generate before the main render. Each preload item must have a unique `id` referenced from elements as `{{id_url}}`, `{{id_duration}}`, `{{id_width}}`, or `{{id_height}}`. | | | |--------------|-------------| | **Type** | array | | **Required** | No | ### quality Render quality. Lower quality renders faster. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"high"` | | **Enum Values** | `low`, `medium`, `high` | ### resolution Movie size. `custom` requires `width` and `height`. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"custom"` | | **Enum Values** | `sd`, `hd`, `full-hd`, `squared`, `instagram-story`, `instagram-feed`, `twitter-landscape`, `twitter-portrait`, `custom` | ### scenes Ordered sequence of scenes that make up the movie. Optional — if omitted or empty, any movie-level `elements` are rendered on their own as a single scene. | | | |--------------|-------------| | **Type** | array | | **Required** | No | #### Array items Each item is a [Scene](@/reference/json-syntax/scene) object. ### template ID of a saved [template](@/reference/api-endpoints/templates-list) to use as the movie body. When set, the template's movie is loaded and the request's `variables` are merged on top. `resolution`, `width`, `height`, `quality`, `exports`, and `client-data` from the request override the template. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### thumbnail Timecode, in seconds, of the frame used as the movie thumbnail. Every render produces one automatically — set this property only to pick a different frame. A **negative** value counts back from the end of the movie: `-2` means 2 seconds before the end. The value is clamped to the movie duration, so a thumbnail at `2` on a 1.5-second movie, or at `-30` on a 5-second movie, still returns a real frame. The image is written next to the rendered video at full resolution, and its URL is returned as `thumbnail` by [`GET /v2/movies`](@/reference/api-endpoints/movies-status) and in the [webhook payload](@/reference/webhooks). It is a JPEG, except for movies rendered with a transparent background, which produce a PNG that keeps the alpha channel — read the URL rather than assuming the extension. The thumbnail is deleted together with the video: after 7 days, or immediately on [`DELETE /v2/movies`](@/reference/api-endpoints/movies-delete). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `2` | ### variables Key-value pairs used to interpolate `{{name}}` placeholders in strings throughout the movie. Variable names should use only letters, numbers, and underscores — other characters are silently replaced with underscores, and names starting with `?`, `$`, or `@` are silently dropped (no validation error is raised). | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### width Width of the movie in pixels. Required and applicable only when `resolution` is `custom`. Range: 50–3840. Bound by the account plan's maximum resolution. | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Default Value** | `640` | | **Minimum Value** | 50 | | **Maximum Value** | 3840 | ### draft Deprecated. Ignored by the renderer; watermarking is now controlled automatically by the account plan. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Deprecated** | Yes | # Scene --- type: scene source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-08-06 --- # Scene **Type:** object A scene is a distinct, sequential segment of the movie. Scenes play in the order they appear in the parent movie's `scenes` array. Scenes cannot overlap in time. Each scene holds its own elements, optional transition, and local variables. ## Properties ### background-color Background color of the scene. Hexadecimal value (e.g. `#FF0000`) or `transparent`. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"#000000"` | ### cache If `true`, a previously rendered version of this scene is reused when its inputs match. If `false`, the scene is rendered from scratch. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | ### comment Free-form note attached to the scene. Ignored by the renderer. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### condition Expression evaluated at render time. The scene is included only if the expression is truthy. Empty strings and falsy values cause the scene to be skipped. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### duration Scene duration in seconds. `-1` makes the scene as long as needed to contain all its elements. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `-1` | | **Format** | float | ### elements Elements rendered inside the scene. Stacking order in the array determines layering — later items render on top. | | | |--------------|-------------| | **Type** | array | | **Required** | No | #### Array items Each item is one of the element types defined in [Element](@/reference/json-syntax/element). The discriminator is the `type` field. | `type` value | Schema | |--------------|--------| | `image` | [Image element](@/reference/json-syntax/element/image) | | `video` | [Video element](@/reference/json-syntax/element/video) | | `text` | [Text element](@/reference/json-syntax/element/text) | | `html` | [HTML element](@/reference/json-syntax/element/html) | | `component` | [Component element](@/reference/json-syntax/element/component) | | `audio` | [Audio element](@/reference/json-syntax/element/audio) | | `voice` | [Voice element](@/reference/json-syntax/element/voice) | | `audiogram` | [Audiogram element](@/reference/json-syntax/element/audiogram) | | `subtitles` | [Subtitles element](@/reference/json-syntax/element/subtitles) | ### id Unique identifier for the scene. Auto-generated when omitted. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"@randomString"` | ### import ID of another scene to import. Imported scenes are merged into the current scene by appending their `elements` array. Useful with templates. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### iterate Name of a movie-level variable holding an **array of objects**. Before expressions are evaluated, the scene is duplicated once per array item. Each item's own fields are added to the copy's local `variables` as flat names — an item `{ "name": "Kitchen" }` is read as `{{ name }}`, not `{{ item.name }}` — together with three automatic variables: * `iteration` — 1-based counter, incremented once per visited array position. * `first_iteration` — `true` on the copy for the first position of the range. * `last_iteration` — `true` on the copy for the last position of the range. Used to generate slideshow-style sequences from data. Dot notation reaches an array nested inside an object variable, e.g. `"product.images"`. The path is walked from the movie's variables until an array is found. | | | |--------------|-------------| | **Type** | string | | **Required** | No | Things worth knowing: - **`iterate` also works on elements**, both inside `scene.elements` and in the movie-level `elements` array — not just on scenes. - **Array items that are not objects produce no copy.** An array of plain strings or numbers (`["a", "b"]`) expands to zero scenes, and the scene disappears from the movie with no error. Mixed arrays still advance the `iteration` counter for the skipped positions, so the counter is not necessarily consecutive. - **An empty array removes the scene** from the movie entirely. - **A variable that is missing, or that does not resolve to an array, fails the render** with `"iterate" property in scene #N does not point to an array variable in global variables`. - **Every copy keeps the template's `id`.** All copies of a scene share the same identifier, so an `id` cannot be used to address one specific iteration. - **There is no `iterate-as` property** and no `item` scope; adding one fails with `Property 'iterate-as' is not allowed`. - **`last_iteration` never fires when `iterate-step` skips the final position.** With 10 items and `"iterate-step": 2` the last copy is item 9, not item 10, so no copy is flagged as last. - Iterating a whole array has no practical limit. Sub-ranges do: when `iterate-from` / `iterate-to` select fewer than 100 items, iteration stops at array position 100, so a narrow range beginning past position 100 of a long array produces no copies at all. ### iterate-from First array item to iterate, 1-based and **inclusive**. A negative value counts from the end of the array (`-2` is the second-to-last item). Values of `0` or lower are treated as `1`. Must be lower than `iterate-to`, otherwise the render fails with `iterate-from must be lower than iterate-to`. Only used when `iterate` is set. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `1` | ### iterate-step Number of array items to advance between iterations — `2` takes every other item. Values lower than `1` are treated as `1`. Only used when `iterate` is set. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `1` | ### iterate-to Last array item to iterate, 1-based and **exclusive**: the item at this position is *not* included. Defaults to one past the end of the array, so the whole array is iterated. A negative value counts from the end of the array. Only used when `iterate` is set. For example, `"iterate-from": 2` with `"iterate-to": 5` renders items 2, 3 and 4. | | | |--------------|-------------| | **Type** | number | | **Required** | No | ### preload Assets to fetch or generate before this scene's elements render. Same shape as `movie.preload`. | | | |--------------|-------------| | **Type** | array | | **Required** | No | ### transition Transition between this scene and the next. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **type**: (string, optional) — Transition family. Currently `xfade`. Default `xfade`. * **style**: (string, optional) — Transition style. Default `fade`. Examples: `fade`, `wipeleft`, `slideup`, `circleopen`, `dissolve`, `pixelize`, … * **duration**: (number, optional) — Length of the transition in seconds. ### variables Scene-local variables. Names follow the same rules as `movie.variables`. Local variables override parent (movie) values inside the scene. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | # Element --- type: element source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-05-12 --- # Element An element is the smallest renderable unit in a movie. Elements live inside a scene's `elements` array or on the movie's top-level `elements` array (overlaying every scene). The shape of an element depends on its `type` field. ## Element types | Type | Description | Reference | |------|-------------|-----------| | `image` | Static image. | [Image element](@/reference/json-syntax/element/image) | | `video` | Video clip. | [Video element](@/reference/json-syntax/element/video) | | `text` | Styled text overlay. | [Text element](@/reference/json-syntax/element/text) | | `html` | HTML snippet or full webpage screenshot/recording. | [HTML element](@/reference/json-syntax/element/html) | | `component` | Pre-built animated component from the library. | [Component element](@/reference/json-syntax/element/component) | | `audio` | Audio track. | [Audio element](@/reference/json-syntax/element/audio) | | `voice` | Text-to-speech voiceover. | [Voice element](@/reference/json-syntax/element/voice) | | `audiogram` | Audio waveform visualisation. | [Audiogram element](@/reference/json-syntax/element/audiogram) | | `subtitles` | Automatic or manual subtitles. | [Subtitles element](@/reference/json-syntax/element/subtitles) | > The `template` element type is deprecated. Use the [`template` movie-level field](@/reference/json-syntax/movie#template) to reference a saved template instead. ## Common properties All element types share the following base properties: - `id` — unique identifier. - `type` — element discriminator. Required on every element. - `condition` — string expression; element is rendered only when truthy. - `variables` — element-local variables. - `comment` — free-form note. - `duration` — element length in seconds. `-1` auto-calculates from the asset; `-2` matches the container. - `start` — start time in seconds relative to the parent container. - `extra-time` — additional time after the element ends. - `z-index` — stacking order override (-99 to 99). - `cache` — reuse the cached render when inputs match. - `fade-in`, `fade-out` — opacity envelope, in seconds. Visual elements (`image`, `video`, `text`, `html`, `component`, `audiogram`) additionally share: - `position`, `x`, `y` — placement. - `width`, `height`, `resize` — sizing. - `rotate`, `crop`, `zoom`, `pan`, `pan-distance`, `pan-crop` — transformations. - `chroma-key`, `correction`, `flip-horizontal`, `flip-vertical`, `mask` — visual effects. Audio-producing elements (`video`, `audio`, `voice`, `audiogram`) additionally share: - `muted` — silence the element. - `volume` — gain (0–10, `1` = unity). ## Positioning and sizing values `x`, `y`, `width` and `height` accept a number of pixels, and also these string forms: | Value | Meaning | Example | |-------|---------|---------| | A number | Pixels. | `"x": 250` | | `"250px"` | Pixels, written as a string. | `"x": "250px"` | | `"30%"` | A percentage of the canvas — width for `x`/`width`, height for `y`/`height`. Can be negative. | `"x": "30%"` | | `"left"`, `"center"`, `"right"` | On `x` only: flush left, centred, or flush right. | `"x": "center"` | | `"top"`, `"center"`, `"bottom"` | On `y` only: flush top, centred, or flush bottom. | `"y": "bottom"` | Three rules are worth committing to memory: **Percentages measure to the element's top-left corner**, exactly like CSS `left` and `top`. On a 1920-wide canvas, `"x": "30%"` puts the element's LEFT EDGE at 576px — it does not centre it on the 30% mark. **Named values carry no margin.** `"x": "left"` is `0`. This is different from the [`position` presets](@/reference/json-syntax/element/image#position), which inset edge and corner placements by 5% of the canvas. Use `position` when you want that breathing room, and a named `x`/`y` when you want the element flush against the edge. Named values need `"position": "custom"`, which is the default. **`text`, `component`, `html` and `audiogram` elements are as large as the canvas unless you set `width`/`height`.** Their box has no natural size, so each unset axis becomes the full canvas — and `"x": "center"` on a canvas-sized box resolves to `0`, because a box that wide is already centred. Give the element a `width` first, then centre it: ```json { "type": "text", "text": "Hello", "width": "50%", "x": "center", "y": "70%" } ``` A value that cannot be understood — `"x": "auto"`, `"width": "wide"` — fails the render with a message naming the element and the property. Two exceptions to the named values. They are not accepted inside [`keyframes`](@/reference/json-syntax/element/image#keyframes), because an animated value has to be interpolated and a name has no number to interpolate. And they are not accepted on the [`subtitles`](@/reference/json-syntax/element/subtitles) element, which is placed by `settings.position` / `settings.x` / `settings.y` instead — its top-level `x`/`y` are inherited but never read. Percentages work in keyframes; on subtitles use the `settings` coordinates, which are plain pixels. ## Example ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "image", "src": "https://example.com/photo.png" }, { "type": "text", "text": "Hello", "style": "001" } ] } ] } ``` Elements placed at the movie level (instead of inside a scene) render above every scene for the entire movie: ```json { "scenes": [/* … */], "elements": [ { "type": "image", "src": "https://example.com/logo.png", "position": "top-right" } ] } ``` # Image --- type: element element_type: image source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-05-22 --- # Image element **Type:** object Defines an image element to be included in the video. The image source is specified using a URL, supporting common image formats like JPG, PNG, and GIF. This element supports properties for visual positioning and sizing. ## Working with the Image element ### Using image files To include an image in your video, you need to provide a direct URL to a file in a common format such as JPEG or PNG. This URL should be assigned to the `src` property and must be publicly accessible to ensure the image loads correctly during video rendering. If the image is hosted on a restricted server or requires authentication, it may not be retrievable by JSON2Video. Ensure that the URL is valid and does not require login credentials. **Example** This example creates an horizontal video showing an image (flower-bee.jpg) during 10 seconds. ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/flower-bee.jpg", "duration": 10 } ] } ] } ``` #### Google Drive and Dropbox JSON2Video also supports images stored in Google Drive and Dropbox, but they must be set to public access. If the file is private or requires special permissions, the system will be unable to fetch it for video creation. ## Properties ### cache If `true`, the system will attempt to retrieve and use a previously rendered (cached) version of this element, if an identical version is available. This can significantly reduce processing time. If `false`, a new render of the element will always be performed, regardless of whether a cached version exists. The default value is `true`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | | **Format** | boolean | ### chroma-key Allows you to define a color or a range of colors within the element that will be rendered as transparent. This effect is commonly known as chroma keying or 'green screen'. The `color` property specifies the base color to be made transparent, while the optional `tolerance` property adjusts the sensitivity of the transparency, allowing you to define a range of similar colors to also be included in the transparency effect. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **color**: (string, required) - Set the color for which alpha will be set to 0 (full transparency) - Example: `"#00b140"` * **tolerance**: (integer, optional) - Makes the selection more or less sensitive to changes in color. A value of 1 will select only the provided color. A value of 100 will select all colors, so the full canvas - Default: `25` - Minimum: `1` - Maximum: `100` ### comment A field for adding descriptive notes or internal memos related to the element. This comment is for your reference and does not affect the rendering process. It can be used to keep notes about the element like describing the content or the purpose of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### condition A string containing an expression that determines whether the element will be rendered. The element is rendered only if the condition evaluates to true. If the condition is false or an empty string, the element will be skipped and not included in the scene or movie. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### correction Defines image and video correction settings, allowing you to adjust the visual characteristics of the element. This includes properties for adjusting contrast, brightness, saturation, and gamma, enabling fine-tuning of the element's appearance. Values in the edge of the range may result in the element being irrecognizable. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **brightness**: (number, optional) - Adjust the brightness - Default: `0` - Minimum: `-1` - Maximum: `1` * **contrast**: (number, optional) - Adjust the contrast - Default: `1` - Minimum: `-1000` - Maximum: `1000` * **gamma**: (number, optional) - Adjust the gamma - Default: `1` - Minimum: `0.1` - Maximum: `10` * **saturation**: (number, optional) - Adjust the saturation - Default: `1` - Minimum: `0` - Maximum: `3` ### crop Defines the cropping area of the element. It allows you to specify a rectangular region of the element to display, effectively cropping the external parts of the provided area. The `x` and `y` properties define the top-left corner of the cropping rectangle, while the `width` and `height` properties determine the dimensions of the cropped area. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **height**: (integer, required) - Sets the height of the cropping area * **width**: (integer, required) - Sets the width of the cropping area * **x**: (integer, optional) - Sets the left point of cropping - Default: `0` * **y**: (integer, optional) - Sets the top point of cropping - Default: `0` ### duration Defines the duration of the element in seconds. Use a positive value to specify the element's length. A value of -1 instructs the system to automatically set the duration based on the intrinsic length of the asset or file used by the element. A value of -2 sets the element's duration to match that of its parent scene (if it's inside a scene) or the movie (if it's in the movie elements array). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `-1` | | **Format** | float | ### extra-time The amount of time, in seconds, to extend the element's duration beyond its natural length. This allows the element to linger on screen after its content has finished playing or displaying. For example, setting `extra-time` to 0.5 will keep the element visible for an additional half-second. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### fade-in The duration, in seconds, of the fade-in effect applied to the element's appearance. A value of `0` means no fade-in effect. Larger values result in a longer fade-in duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### fade-out The duration, in seconds, of the fade-out effect applied to the element's disappearance. A value of `0` means no fade-out effect. Larger values result in a longer fade-out duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### flip-horizontal If `true`, the element will be flipped horizontally, creating a mirror image effect. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### flip-vertical If `true`, the element will be flipped vertically, creating an upside-down image. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### height Sets the height of the element in pixels, scaling the element up or down as needed to fit the specified height. A value of -1 maintains the element's original aspect ratio when resizing based on the width property. If 'resize' is set, the 'height' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas height (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### id A unique identifier for the element within the movie. This string allows you to reference and manage individual elements. If not provided, the system will automatically generate a random string. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"@randomString"` | ### keyframes Animates the element's properties over time. Each keyframe defines a target value at a given time, and the engine interpolates between consecutive keyframes using the specified easing function. Animatable properties are `x`, `y`, `width`, `height` and `zoom`. If the first keyframe time is not `0`, an implicit keyframe at time `0` is prepended using the element's current values; an implicit keyframe at the element's end time is appended if missing. `zoom` keyframes are only valid for `image` and `video` elements, and cannot be combined with `width` / `height` keyframes or with the `pan` property. | | | |--------------|-------------| | **Type** | array | | **Required** | No | Each item in the array is a `keyframe` object with these properties: * **time** (number or string, required) — Time of the keyframe in seconds, relative to the element's start. Accepts: - A positive number — seconds from the element's start. - A negative number — seconds before the element ends (e.g. `-1` is 1 second before the end). - A percentage string — e.g. `"50%"` is 50% of the element's duration. - Times beyond the element's duration are clamped; keyframes with a time earlier than the previous one are skipped. * **x** (number or string, optional) — Target horizontal position: pixels, `"250px"`, or a percentage of the canvas width (`"30%"`). Named values like `center` are NOT accepted in a keyframe — the value has to be interpolated, and a name has no number. Only meaningful when `position` is `custom` or has been resolved to custom by an `x`/`y` keyframe. * **y** (number or string, optional) — Target vertical position: pixels, `"250px"`, or a percentage of the canvas height (`"30%"`). Named values like `center` are NOT accepted in a keyframe. Only meaningful when `position` is `custom` or has been resolved to custom by an `x`/`y` keyframe. * **width** (number or string, optional) — Target width: pixels, `"800px"`, or a percentage of the canvas width (`"70%"`). A value of `-1` preserves the aspect ratio relative to the `height` keyframe value. * **height** (number or string, optional) — Target height: pixels, `"800px"`, or a percentage of the canvas height (`"70%"`). A value of `-1` preserves the aspect ratio relative to the `width` keyframe value. * **zoom** (number, optional) — Target zoom level. Positive values zoom in, negative values zoom out, `0` means no zoom. Range `-10` to `10`. Only valid for `image` and `video` elements; incompatible with `width` / `height` keyframes and with the `pan` property. * **easing** (string, optional, default `"linear"`) — Easing function applied to the transition from the previous keyframe to this one. Acts as a default for every animated property in this keyframe; can be overridden per property via the `easing-*` fields below. Accepted values: - `linear`, `ease` - `ease-in-sine`, `ease-out-sine`, `ease-in-out-sine` - `ease-in-quad`, `ease-out-quad`, `ease-in-out-quad` - `ease-in-cubic`, `ease-out-cubic`, `ease-in-out-cubic` - `ease-in-quart`, `ease-out-quart`, `ease-in-out-quart` - `ease-in-quint`, `ease-out-quint`, `ease-in-out-quint` - `ease-in-expo`, `ease-out-expo`, `ease-in-out-expo` - `ease-in-circ`, `ease-out-circ`, `ease-in-out-circ` - `ease-in-back`, `ease-out-back`, `ease-in-out-back` - `ease-in-elastic`, `ease-out-elastic`, `ease-in-out-elastic` - `ease-in-bounce`, `ease-out-bounce`, `ease-in-out-bounce` * **easing-x** (string, optional) — Overrides `easing` for the `x` property only. Accepts the same values as `easing`. * **easing-y** (string, optional) — Overrides `easing` for the `y` property only. * **easing-width** (string, optional) — Overrides `easing` for the `width` property only. * **easing-height** (string, optional) — Overrides `easing` for the `height` property only. * **easing-zoom** (string, optional) — Overrides `easing` for the `zoom` property only. **Example — pan an image from left to right over 2 seconds with elastic ease-out:** ```json { "type": "image", "src": "https://cdn.json2video.com/assets/images/flower-bee.jpg", "position": "custom", "x": 0, "y": 0, "duration": 4, "keyframes": [ { "time": 0, "x": 0, "easing": "linear" }, { "time": 2, "x": 500, "easing": "ease-out-elastic" } ] } ``` ### mask URL to a PNG or video file that defines a mask, controlling the transparency of the element. The mask uses a grayscale color scheme: black areas render the element fully transparent, white areas render it fully opaque, and shades of gray create varying levels of partial transparency. This allows you to create complex shapes and effects by selectively hiding portions of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### pan Specifies the direction to pan the element within its container. Valid values are `left`, `top`, `right`, `bottom`, and their combinations like `top-left`. If the `zoom` property is also specified, the pan will occur while zooming. If `zoom` is not specified, the element will pan without zooming. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `left`, `top`, `right`, `bottom`, `top-left`, `top-right`, `bottom-left`, `bottom-right` | ### pan-crop When panning an element, this boolean property determines whether the element is stretched and cropped to fill the movie canvas. If set to `true` (default), the element will be stretched and cropped during panning. If set to `false`, the element will not be stretched and potentially leave empty space within the movie canvas. Example: if `pan-crop` is set to `false` and the movie canvas and element have the same size, panning the element to the left may leave a black bar on the right side of the movie canvas as the element moves to the left. If `pan-crop` is set to `true` (default), the element will be stretched and cropped during panning, so the element will effectively fill the movie canvas. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | ### pan-distance Defines the distance the element pans within its container when the `pan` property is specified. This value, expressed as a floating-point number, determines the amount of movement during the panning effect. Higher values result in faster and more pronounced panning. The allowed range is from 0.01 to 0.5, with a default value of 0.1. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0.1` | | **Format** | float | | **Minimum Value** | 0.01 | | **Maximum Value** | 0.5 | ### position Specifies the position of the element within the movie canvas. Choose from predefined positions like 'top-left', 'top-right', 'bottom-right', 'bottom-left', and 'center-center' to quickly place the element. Selecting 'custom' enables precise positioning using the `x` and `y` properties to define the element's horizontal and vertical coordinates. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"custom"` | | **Enum Values** | `top-left`, `top-right`, `bottom-right`, `bottom-left`, `center-center`, `custom` | ### resize Defines how the element should be resized to fit within the movie canvas. The values `cover` and `fill` stretch the element to completely cover the movie canvas, potentially cropping parts of the element. The values `fit` and `contain` ensure the entire element is visible, potentially leaving empty space within the canvas. When `resize` is set, the `width` and `height` properties are ignored, as the element's size is determined by the chosen resize mode. The value `cover`is a synonym for `fill` and `contain`is a synonym for `fit`. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `cover`, `fill`, `fit`, `contain` | ### rotate Defines the rotation properties of the element. It allows you to specify the angle of rotation and the time it takes to complete the rotation, enabling animated rotation effects. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **angle**: (number, required) - Sets the angle of rotation - Default: `0` - Minimum: `-360` - Maximum: `360` * **speed**: (number, optional) - Sets the time it takes to rotate the provided angle. A zero value means no movement - Default: `0` - Minimum: `0` ### src The URL to the image asset file. This should be a publicly accessible URL pointing to the image file, which can be in JPG, PNG, GIF, or any other common image format. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Format** | uri | ### start The element's start time, in seconds, determines when it begins playing within its container's timeline. This time is relative to the beginning of the scene it's in or, if the element is part of the movie's elements array, relative to the beginning of the movie itself. The default value is 0, meaning the element starts at the beginning of its container's timeline. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### type This field specifies the element's type and must be set to `image` for image elements. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `image` | ### variables Defines local variables specific to this element. These variables can be used to dynamically alter the element's properties or content during the rendering process. Variable names must consist of only letters, numbers, and underscores. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### width Sets the width of the element in pixels. The element will be scaled up or down to fit the specified width. A value of -1 instructs the system to maintain the element's original aspect ratio when resizing based on the height property. If 'resize' is set, the 'width' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas width (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### x The horizontal position of the element within the movie canvas, measured to the element's LEFT edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the left edge of the movie canvas. Higher values move the element to the right. Besides a number of pixels, `x` accepts a pixel string (`"250px"`), a percentage of the canvas width (`"30%"`, which may be negative) and the named values `left`, `center` and `right` — flush left, centred, or flush right, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"left"`, `"center"`, `"right"` | ### y Sets the vertical position of the element within the movie canvas, measured to the element's TOP edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the top edge of the movie canvas. Higher values move the element downwards. Besides a number of pixels, `y` accepts a pixel string (`"250px"`), a percentage of the canvas height (`"30%"`, which may be negative) and the named values `top`, `center` and `bottom` — flush top, centred, or flush bottom, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"top"`, `"center"`, `"bottom"` | ### z-index Element's z-index, determining its stacking order within the video. Higher values bring the element to the front, obscuring elements with lower values. Lower values send the element to the back, potentially behind other elements. The value must be an integer between -99 and 99; the default is 0. The natural way of layering elements is by the order of the elements in the `elements` array. If by any reason this does not work in your case, you can use the `z-index` property to manually control the stacking order. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Minimum Value** | -99 | | **Maximum Value** | 99 | ### zoom Zooms the element by a specified percentage. Use positive values (1-10) to zoom in and negative values (-1 to -10) to zoom out. A value of 0 results in no zoom. Combine with the `pan` property to control the focal point during zooming. | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Minimum Value** | -10 | | **Maximum Value** | 10 | ## Examples ### Example 1: Simple Image Element This example demonstrates a movie with three images displayed in a slideshow. Each image is displayed for 3 seconds with a zoom effect and a pan effect. ```json { "resolution": "full-hd", "scenes": [ { "comment": "First scene", "duration": 3, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/london-01.jpg", "zoom": 3, "pan": "right" } ] }, { "comment": "Second scene", "duration": 3, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/london-02.jpg", "zoom": -3, "pan": "left" } ] }, { "comment": "Third scene", "duration": 3, "elements": [ { "type": "image", "src": "https://cdn.json2video.com/assets/images/london-03.jpg", "zoom": 3, "pan": "top" } ] } ] } ``` ### Example 2: Image with Scaling and Rotation This example shows how to scale an image and rotate it. ```json { "scenes": [ { "elements": [ { "type": "image", "src": "https://assets.json2video.com/assets/images/sunglasses-emoji-small.png", "x": 835, "y": 575, "width": 250, "height": 250, "rotate": { "angle": 360, "speed": 1 } } ] } ] } ``` # Video --- type: element element_type: video source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-05-12 --- # Video element **Type:** object Defines a video element that allows you to incorporate video content into your scenes or movie. Specify the video source using a URL pointing to a video file (MP4, MKV, MOV, etc.). Control playback behavior by defining the number of times the video loops and the starting point within the video using the seek property. ## Required Properties - `type` ## Properties ### cache If `true`, the system will attempt to retrieve and use a previously rendered (cached) version of this element, if an identical version is available. This can significantly reduce processing time. If `false`, a new render of the element will always be performed, regardless of whether a cached version exists. The default value is `true`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | | **Format** | boolean | ### chroma-key Allows you to define a color or a range of colors within the element that will be rendered as transparent. This effect is commonly known as chroma keying or 'green screen'. The `color` property specifies the base color to be made transparent, while the optional `tolerance` property adjusts the sensitivity of the transparency, allowing you to define a range of similar colors to also be included in the transparency effect. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **color**: (string, required) - Set the color for which alpha will be set to 0 (full transparency) - Example: `"#00b140"` * **tolerance**: (integer, optional) - Makes the selection more or less sensitive to changes in color. A value of 1 will select only the provided color. A value of 100 will select all colors, so the full canvas - Default: `25` - Minimum: `1` - Maximum: `100` ### comment A field for adding descriptive notes or internal memos related to the element. This comment is for your reference and does not affect the rendering process. It can be used to keep notes about the element like describing the content or the purpose of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### condition A string containing an expression that determines whether the element will be rendered. The element is rendered only if the condition evaluates to true. If the condition is false or an empty string, the element will be skipped and not included in the scene or movie. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### correction Defines image and video correction settings, allowing you to adjust the visual characteristics of the element. This includes properties for adjusting contrast, brightness, saturation, and gamma, enabling fine-tuning of the element's appearance. Values in the edge of the range may result in the element being irrecognizable. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **brightness**: (number, optional) - Adjust the brightness - Default: `0` - Minimum: `-1` - Maximum: `1` * **contrast**: (number, optional) - Adjust the contrast - Default: `1` - Minimum: `-1000` - Maximum: `1000` * **gamma**: (number, optional) - Adjust the gamma - Default: `1` - Minimum: `0.1` - Maximum: `10` * **saturation**: (number, optional) - Adjust the saturation - Default: `1` - Minimum: `0` - Maximum: `3` ### crop Defines the cropping area of the element. It allows you to specify a rectangular region of the element to display, effectively cropping the external parts of the provided area. The `x` and `y` properties define the top-left corner of the cropping rectangle, while the `width` and `height` properties determine the dimensions of the cropped area. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **height**: (integer, required) - Sets the height of the cropping area * **width**: (integer, required) - Sets the width of the cropping area * **x**: (integer, optional) - Sets the left point of cropping - Default: `0` * **y**: (integer, optional) - Sets the top point of cropping - Default: `0` ### duration Defines the duration of the element in seconds. Use a positive value to specify the element's length. A value of -1 instructs the system to automatically set the duration based on the intrinsic length of the asset or file used by the element. A value of -2 sets the element's duration to match that of its parent scene (if it's inside a scene) or the movie (if it's in the movie elements array). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `-1` | | **Format** | float | ### extra-time The amount of time, in seconds, to extend the element's duration beyond its natural length. This allows the element to linger on screen after its content has finished playing or displaying. For example, setting `extra-time` to 0.5 will keep the element visible for an additional half-second. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### fade-in The duration, in seconds, of the fade-in effect applied to the element's appearance. A value of `0` means no fade-in effect. Larger values result in a longer fade-in duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### fade-out The duration, in seconds, of the fade-out effect applied to the element's disappearance. A value of `0` means no fade-out effect. Larger values result in a longer fade-out duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### flip-horizontal If `true`, the element will be flipped horizontally, creating a mirror image effect. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### flip-vertical If `true`, the element will be flipped vertically, creating an upside-down image. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### height Sets the height of the element in pixels, scaling the element up or down as needed to fit the specified height. A value of -1 maintains the element's original aspect ratio when resizing based on the width property. If 'resize' is set, the 'height' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas height (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### id A unique identifier for the element within the movie. This string allows you to reference and manage individual elements. If not provided, the system will automatically generate a random string. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"@randomString"` | ### keyframes Animates the element's properties over time. Each keyframe defines a target value at a given time, and the engine interpolates between consecutive keyframes using the specified easing function. Animatable properties are `x`, `y`, `width`, `height` and `zoom`. If the first keyframe time is not `0`, an implicit keyframe at time `0` is prepended using the element's current values; an implicit keyframe at the element's end time is appended if missing. `zoom` keyframes are only valid for `image` and `video` elements, and cannot be combined with `width` / `height` keyframes or with the `pan` property. | | | |--------------|-------------| | **Type** | array | | **Required** | No | Each item in the array is a `keyframe` object with these properties: * **time** (number or string, required) — Time of the keyframe in seconds, relative to the element's start. Accepts: - A positive number — seconds from the element's start. - A negative number — seconds before the element ends (e.g. `-1` is 1 second before the end). - A percentage string — e.g. `"50%"` is 50% of the element's duration. - Times beyond the element's duration are clamped; keyframes with a time earlier than the previous one are skipped. * **x** (number or string, optional) — Target horizontal position: pixels, `"250px"`, or a percentage of the canvas width (`"30%"`). Named values like `center` are NOT accepted in a keyframe — the value has to be interpolated, and a name has no number. Only meaningful when `position` is `custom` or has been resolved to custom by an `x`/`y` keyframe. * **y** (number or string, optional) — Target vertical position: pixels, `"250px"`, or a percentage of the canvas height (`"30%"`). Named values like `center` are NOT accepted in a keyframe. Only meaningful when `position` is `custom` or has been resolved to custom by an `x`/`y` keyframe. * **width** (number or string, optional) — Target width: pixels, `"800px"`, or a percentage of the canvas width (`"70%"`). A value of `-1` preserves the aspect ratio relative to the `height` keyframe value. * **height** (number or string, optional) — Target height: pixels, `"800px"`, or a percentage of the canvas height (`"70%"`). A value of `-1` preserves the aspect ratio relative to the `width` keyframe value. * **zoom** (number, optional) — Target zoom level. Positive values zoom in, negative values zoom out, `0` means no zoom. Range `-10` to `10`. Only valid for `image` and `video` elements; incompatible with `width` / `height` keyframes and with the `pan` property. * **easing** (string, optional, default `"linear"`) — Easing function applied to the transition from the previous keyframe to this one. Acts as a default for every animated property in this keyframe; can be overridden per property via the `easing-*` fields below. Accepted values: - `linear`, `ease` - `ease-in-sine`, `ease-out-sine`, `ease-in-out-sine` - `ease-in-quad`, `ease-out-quad`, `ease-in-out-quad` - `ease-in-cubic`, `ease-out-cubic`, `ease-in-out-cubic` - `ease-in-quart`, `ease-out-quart`, `ease-in-out-quart` - `ease-in-quint`, `ease-out-quint`, `ease-in-out-quint` - `ease-in-expo`, `ease-out-expo`, `ease-in-out-expo` - `ease-in-circ`, `ease-out-circ`, `ease-in-out-circ` - `ease-in-back`, `ease-out-back`, `ease-in-out-back` - `ease-in-elastic`, `ease-out-elastic`, `ease-in-out-elastic` - `ease-in-bounce`, `ease-out-bounce`, `ease-in-out-bounce` * **easing-x** (string, optional) — Overrides `easing` for the `x` property only. Accepts the same values as `easing`. * **easing-y** (string, optional) — Overrides `easing` for the `y` property only. * **easing-width** (string, optional) — Overrides `easing` for the `width` property only. * **easing-height** (string, optional) — Overrides `easing` for the `height` property only. * **easing-zoom** (string, optional) — Overrides `easing` for the `zoom` property only. **Example — slowly zoom into a video over 5 seconds:** ```json { "type": "video", "src": "https://example.com/path/to/my/video.mp4", "duration": 5, "keyframes": [ { "time": 0, "zoom": 0, "easing": "linear" }, { "time": 5, "zoom": 3, "easing": "ease-in-out-cubic" } ] } ``` ### loop Specifies the number of times the video will play. Setting this value to -1 results in the video looping indefinitely. The default value of 1 ensures that the video plays only once. | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Minimum Value** | -1 | ### mask URL to a PNG or video file that defines a mask, controlling the transparency of the element. The mask uses a grayscale color scheme: black areas render the element fully transparent, white areas render it fully opaque, and shades of gray create varying levels of partial transparency. This allows you to create complex shapes and effects by selectively hiding portions of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### muted If `true`, the audio track of the element (e.g., a video or audio file) will be muted, effectively silencing it. If `false` or omitted, the audio will play according to its original volume or the `volume` setting. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### pan Specifies the direction to pan the element within its container. Valid values are `left`, `top`, `right`, `bottom`, and their combinations like `top-left`. If the `zoom` property is also specified, the pan will occur while zooming. If `zoom` is not specified, the element will pan without zooming. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `left`, `top`, `right`, `bottom`, `top-left`, `top-right`, `bottom-left`, `bottom-right` | ### pan-crop When panning an element, this boolean property determines whether the element is stretched and cropped to fill the movie canvas. If set to `true` (default), the element will be stretched and cropped during panning. If set to `false`, the element will not be stretched and potentially leave empty space within the movie canvas. Example: if `pan-crop` is set to `false` and the movie canvas and element have the same size, panning the element to the left may leave a black bar on the right side of the movie canvas as the element moves to the left. If `pan-crop` is set to `true` (default), the element will be stretched and cropped during panning, so the element will effectively fill the movie canvas. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | ### pan-distance Defines the distance the element pans within its container when the `pan` property is specified. This value, expressed as a floating-point number, determines the amount of movement during the panning effect. Higher values result in faster and more pronounced panning. The allowed range is from 0.01 to 0.5, with a default value of 0.1. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0.1` | | **Format** | float | | **Minimum Value** | 0.01 | | **Maximum Value** | 0.5 | ### position Specifies the position of the element within the movie canvas. Choose from predefined positions like 'top-left', 'top-right', 'bottom-right', 'bottom-left', and 'center-center' to quickly place the element. Selecting 'custom' enables precise positioning using the `x` and `y` properties to define the element's horizontal and vertical coordinates. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"custom"` | | **Enum Values** | `top-left`, `top-right`, `bottom-right`, `bottom-left`, `center-center`, `custom` | ### resize Defines how the element should be resized to fit within the movie canvas. The values `cover` and `fill` stretch the element to completely cover the movie canvas, potentially cropping parts of the element. The values `fit` and `contain` ensure the entire element is visible, potentially leaving empty space within the canvas. When `resize` is set, the `width` and `height` properties are ignored, as the element's size is determined by the chosen resize mode. The value `cover`is a synonym for `fill` and `contain`is a synonym for `fit`. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `cover`, `fill`, `fit`, `contain` | ### rotate Defines the rotation properties of the element. It allows you to specify the angle of rotation and the time it takes to complete the rotation, enabling animated rotation effects. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **angle**: (number, required) - Sets the angle of rotation - Default: `0` - Minimum: `-360` - Maximum: `360` * **speed**: (number, optional) - Sets the time it takes to rotate the provided angle. A zero value means no movement - Default: `0` - Minimum: `0` ### seek Specifies the time, in seconds, at which the video file should start playing. Positive values seek forward from the beginning, while negative values seek backward from the end of the video. By default, the video starts at the beginning (0 seconds). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### speed Sets the playback speed of the video. A value of `1` is normal speed, values greater than `1` play faster (e.g. `1.5` is 50% faster, `2` is double speed) and values lower than `1` play slower (e.g. `0.5` is half speed). The video frames are retimed and, if the video has audio, the audio tempo is adjusted while preserving its pitch. Changing the speed shortens or lengthens the element: with `duration` set to `-1` (auto), a faster video produces a shorter element. The `seek` value is measured in the original (un-sped) asset timeline, so seeking happens before the speed change. The acceptable range is from 0.5 to 4. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `1` | | **Minimum Value** | 0.5 | | **Maximum Value** | 4 | | **Format** | float | ### src The URL to the video asset file. This should be a publicly accessible URL pointing to the image file, which can be in MP4, MKV, MOV, or any other common video format. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Format** | uri | ### start The element's start time, in seconds, determines when it begins playing within its container's timeline. This time is relative to the beginning of the scene it's in or, if the element is part of the movie's elements array, relative to the beginning of the movie itself. The default value is 0, meaning the element starts at the beginning of its container's timeline. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### type This field specifies the element's type and must be set to `video` for video elements. | | | |--------------|-------------| | **Type** | string | | **Required** | Yes | | **Enum Values** | `video` | ### variables Defines local variables specific to this element. These variables can be used to dynamically alter the element's properties or content during the rendering process. Variable names must consist of only letters, numbers, and underscores. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### volume Controls the volume gain of the audio track (e.g., a video or audio file). This is a multiplier applied to the original audio level. A value of `1` represents the original volume (no gain), values greater than `1` increase the volume, and values less than `1` decrease the volume. The acceptable range is from 0 to 10. For background music with voiceovers, a usual value is `0.2`. Increasing the volume of the audio track can reduce the quality of the audio. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `1` | | **Minimum Value** | 0 | | **Maximum Value** | 10 | ### width Sets the width of the element in pixels. The element will be scaled up or down to fit the specified width. A value of -1 instructs the system to maintain the element's original aspect ratio when resizing based on the height property. If 'resize' is set, the 'width' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas width (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### x The horizontal position of the element within the movie canvas, measured to the element's LEFT edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the left edge of the movie canvas. Higher values move the element to the right. Besides a number of pixels, `x` accepts a pixel string (`"250px"`), a percentage of the canvas width (`"30%"`, which may be negative) and the named values `left`, `center` and `right` — flush left, centred, or flush right, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"left"`, `"center"`, `"right"` | ### y Sets the vertical position of the element within the movie canvas, measured to the element's TOP edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the top edge of the movie canvas. Higher values move the element downwards. Besides a number of pixels, `y` accepts a pixel string (`"250px"`), a percentage of the canvas height (`"30%"`, which may be negative) and the named values `top`, `center` and `bottom` — flush top, centred, or flush bottom, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"top"`, `"center"`, `"bottom"` | ### z-index Element's z-index, determining its stacking order within the video. Higher values bring the element to the front, obscuring elements with lower values. Lower values send the element to the back, potentially behind other elements. The value must be an integer between -99 and 99; the default is 0. The natural way of layering elements is by the order of the elements in the `elements` array. If by any reason this does not work in your case, you can use the `z-index` property to manually control the stacking order. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Minimum Value** | -99 | | **Maximum Value** | 99 | ### zoom Zooms the element by a specified percentage. Use positive values (1-10) to zoom in and negative values (-1 to -10) to zoom out. A value of 0 results in no zoom. Combine with the `pan` property to control the focal point during zooming. | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Minimum Value** | -10 | | **Maximum Value** | 10 | # Text --- type: element element_type: text source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-05-12 --- # Text element **Type:** object Defines a text element that allows you to overlay a text on top of your video. Depending on the style selected, the text can include different text animations like word-by-word, character-by-character, or jumping letters. ### Related links - [Text styles](@/reference/text-styles): Check out the available text styles and their corresponding visual characteristics. ## Customizing the Text element The **Text element** can be customized to include a variety of text styles, fonts, and colors. You must use the `settings` object to customize the text element. Example: ```json { "type": "text", "text": "Hello, world!", "settings": { "font-family": "Roboto", "font-size": "48px", "font-weight": "700", "color": "#000000", "background-color": "#FFFFFF", "text-align": "center" } } ``` Most of the CSS properties are supported in the `settings` object. ### Use the exact CSS property name The keys of `settings` are real CSS property names, applied as-is to the rendered text. The text colour is therefore `color` — the CSS property — and **not** `font-color`, which does not exist in CSS. A key that is not a supported CSS property is **silently ignored**: the API accepts the movie, the render succeeds, and the text simply keeps the style's default appearance. There is no error message to tell you the key was dropped, so if a setting seems to have no effect, check the spelling against the [CSS property list](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference) first. | You want | Correct key | Not | |---|---|---| | Text colour | `color` | ~~`font-color`~~, ~~`colour`~~ | | Background colour | `background-color` | ~~`bg-color`~~ | | Font | `font-family` | ~~`font`~~ | > **The `subtitles` element is different.** Its `settings` object is not CSS: it > is a closed list of properties, and an unknown key is *rejected* rather than > ignored. Its colours are `word-color`, `line-color`, `outline-color`, > `shadow-color` and `box-color` — see the > [subtitles element reference](@/reference/json-syntax/element/subtitles). ## Available font families ### Google Fonts You can use any [Google Font](https://fonts.google.com/) in the `font-family` property just by providing the font name. Examples: - `Roboto` - `Lato` - `Montserrat` - `Open Sans` - `Poppins` - `Raleway` In some cases, Google Fonts don't use the same font name as the one used in the `font-family` property. This is the case for some of the Noto fonts that support different languages. Common Noto font families for different languages: - `Noto Sans` – supports multiple languages - `Noto Serif` – supports multiple languages - `Noto Sans JP` – supports Japanese - `Noto Sans SC` – supports Chinese Simplified - `Noto Sans TC` – supports Chinese Traditional - `Noto Sans KR` – supports Korean - `Noto Sans Thai` – supports Thai - `Noto Sans Hebrew` – supports Hebrew - `Noto Sans Arabic` – supports Arabic Example: ```json { "type": "text", "text": "헬로 월드", "settings": { "font-family": "Noto Sans KR", } } ``` ### Custom fonts You can use any custom font in the `font-family` property by providing a URL to the font file. TrueType fonts (.ttf) and OpenType fonts (.otf) are supported. Example: ```json { "type": "text", "text": "Hello, world!", "settings": { "font-family": "https://example.com/fonts/custom-font.ttf" } } ``` > **NOTE** > Be aware that the custom fonts in the `subtitles` element use the `font-url` property instead of the `font-family` property. ## Positioning the Text element The **Text element** is structured to provide flexibility in positioning text within your video. It consists of two main concepts: 1. **Text Element Canvas Area**: - This is the outer area that can occupy the full size of the video canvas. It serves as the boundary within which the text box is placed. The canvas can be adjusted to fit the full video size or a custom size. 2. **Textbox Inside the Canvas**: - Within the text element canvas, there is a textbox that can be aligned both vertically and horizontally. This alignment feature ensures that regardless of the text length, the textbox can be positioned accurately within the canvas. The final position of the textbox relative to the video canvas is determined by the combination of: - The size and position of the text element canvas. - The alignment settings of the textbox within the canvas. This approach is particularly useful for dynamically positioning the textbox. Even if the textbox expands or contracts due to varying text lengths, it will maintain its alignment within the canvas. This ensures a consistent and visually appealing presentation of text in your video, regardless of content changes. To position the textbox inside the canvas, you must use the `vertical-position` and `horizontal-position` properties. The `vertical-position` property can be one of the following values: - `top` - `center` - `bottom` The `horizontal-position` property can be one of the following values: - `left` - `center` - `right` Example: ```json { "type": "text", "text": "Hello, world!", "settings": { "vertical-position": "top", "horizontal-position": "right" } } ``` This example will position the textbox to the top-right corner of the Text element canvas. As the Text element canvas defaults to the full size of the video canvas, the textbox will be positioned to the top-right corner of the video. ## Properties The following properties are required: - `text` - `type` ### cache If `true`, the system will attempt to retrieve and use a previously rendered (cached) version of this element, if an identical version is available. This can significantly reduce processing time. If `false`, a new render of the element will always be performed, regardless of whether a cached version exists. The default value is `true`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | | **Format** | boolean | ### chroma-key Allows you to define a color or a range of colors within the element that will be rendered as transparent. This effect is commonly known as chroma keying or 'green screen'. The `color` property specifies the base color to be made transparent, while the optional `tolerance` property adjusts the sensitivity of the transparency, allowing you to define a range of similar colors to also be included in the transparency effect. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **color**: (string, required) - Set the color for which alpha will be set to 0 (full transparency) - Example: `"#00b140"` * **tolerance**: (integer, optional) - Makes the selection more or less sensitive to changes in color. A value of 1 will select only the provided color. A value of 100 will select all colors, so the full canvas - Default: `25` - Minimum: `1` - Maximum: `100` ### comment A field for adding descriptive notes or internal memos related to the element. This comment is for your reference and does not affect the rendering process. It can be used to keep notes about the element like describing the content or the purpose of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### condition A string containing an expression that determines whether the element will be rendered. The element is rendered only if the condition evaluates to true. If the condition is false or an empty string, the element will be skipped and not included in the scene or movie. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### correction Defines image and video correction settings, allowing you to adjust the visual characteristics of the element. This includes properties for adjusting contrast, brightness, saturation, and gamma, enabling fine-tuning of the element's appearance. Values in the edge of the range may result in the element being irrecognizable. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **brightness**: (number, optional) - Adjust the brightness - Default: `0` - Minimum: `-1` - Maximum: `1` * **contrast**: (number, optional) - Adjust the contrast - Default: `1` - Minimum: `-1000` - Maximum: `1000` * **gamma**: (number, optional) - Adjust the gamma - Default: `1` - Minimum: `0.1` - Maximum: `10` * **saturation**: (number, optional) - Adjust the saturation - Default: `1` - Minimum: `0` - Maximum: `3` ### crop Defines the cropping area of the element. It allows you to specify a rectangular region of the element to display, effectively cropping the external parts of the provided area. The `x` and `y` properties define the top-left corner of the cropping rectangle, while the `width` and `height` properties determine the dimensions of the cropped area. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **height**: (integer, required) - Sets the height of the cropping area * **width**: (integer, required) - Sets the width of the cropping area * **x**: (integer, optional) - Sets the left point of cropping - Default: `0` * **y**: (integer, optional) - Sets the top point of cropping - Default: `0` ### duration Defines the duration of the text element in seconds. Use a positive value to specify the length of time the text is displayed. A value of -1 automatically calculates the duration based on the text animation duration. A value of -2 sets the element's duration to match the duration of its container (being either the parent scene or the movie). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `-2` | | **Format** | float | | **Minimum Value** | -2 | ### extra-time The amount of time, in seconds, to extend the element's duration beyond its natural length. This allows the element to linger on screen after its content has finished playing or displaying. For example, setting `extra-time` to 0.5 will keep the element visible for an additional half-second. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### fade-in The duration, in seconds, of the fade-in effect applied to the element's appearance. A value of `0` means no fade-in effect. Larger values result in a longer fade-in duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### fade-out The duration, in seconds, of the fade-out effect applied to the element's disappearance. A value of `0` means no fade-out effect. Larger values result in a longer fade-out duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### flip-horizontal If `true`, the element will be flipped horizontally, creating a mirror image effect. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### flip-vertical If `true`, the element will be flipped vertically, creating an upside-down image. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### height Sets the height of the element in pixels, scaling the element up or down as needed to fit the specified height. A value of -1 maintains the element's original aspect ratio when resizing based on the width property. If 'resize' is set, the 'height' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas height (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### id A unique identifier for the element within the movie. This string allows you to reference and manage individual elements. If not provided, the system will automatically generate a random string. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"@randomString"` | ### keyframes Animates the element's `x`, `y`, `width` and `height` properties over time. See [the `image` element](@/reference/json-syntax/element/image#keyframes) for the full keyframe schema (`time`, easing functions, per-property easing overrides). > `zoom` keyframes are only supported on `image` and `video` elements. | | | |--------------|-------------| | **Type** | array | | **Required** | No | ### mask URL to a PNG or video file that defines a mask, controlling the transparency of the element. The mask uses a grayscale color scheme: black areas render the element fully transparent, white areas render it fully opaque, and shades of gray create varying levels of partial transparency. This allows you to create complex shapes and effects by selectively hiding portions of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### pan Specifies the direction to pan the element within its container. Valid values are `left`, `top`, `right`, `bottom`, and their combinations like `top-left`. If the `zoom` property is also specified, the pan will occur while zooming. If `zoom` is not specified, the element will pan without zooming. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `left`, `top`, `right`, `bottom`, `top-left`, `top-right`, `bottom-left`, `bottom-right` | ### pan-crop When panning an element, this boolean property determines whether the element is stretched and cropped to fill the movie canvas. If set to `true` (default), the element will be stretched and cropped during panning. If set to `false`, the element will not be stretched and potentially leave empty space within the movie canvas. Example: if `pan-crop` is set to `false` and the movie canvas and element have the same size, panning the element to the left may leave a black bar on the right side of the movie canvas as the element moves to the left. If `pan-crop` is set to `true` (default), the element will be stretched and cropped during panning, so the element will effectively fill the movie canvas. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | ### pan-distance Defines the distance the element pans within its container when the `pan` property is specified. This value, expressed as a floating-point number, determines the amount of movement during the panning effect. Higher values result in faster and more pronounced panning. The allowed range is from 0.01 to 0.5, with a default value of 0.1. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0.1` | | **Format** | float | | **Minimum Value** | 0.01 | | **Maximum Value** | 0.5 | ### position Specifies the position of the element within the movie canvas. Choose from predefined positions like 'top-left', 'top-right', 'bottom-right', 'bottom-left', and 'center-center' to quickly place the element. Selecting 'custom' enables precise positioning using the `x` and `y` properties to define the element's horizontal and vertical coordinates. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"custom"` | | **Enum Values** | `top-left`, `top-right`, `bottom-right`, `bottom-left`, `center-center`, `custom` | ### resize Defines how the element should be resized to fit within the movie canvas. The values `cover` and `fill` stretch the element to completely cover the movie canvas, potentially cropping parts of the element. The values `fit` and `contain` ensure the entire element is visible, potentially leaving empty space within the canvas. When `resize` is set, the `width` and `height` properties are ignored, as the element's size is determined by the chosen resize mode. The value `cover`is a synonym for `fill` and `contain`is a synonym for `fit`. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `cover`, `fill`, `fit`, `contain` | ### rotate Defines the rotation properties of the element. It allows you to specify the angle of rotation and the time it takes to complete the rotation, enabling animated rotation effects. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **angle**: (number, required) - Sets the angle of rotation - Default: `0` - Minimum: `-360` - Maximum: `360` * **speed**: (number, optional) - Sets the time it takes to rotate the provided angle. A zero value means no movement - Default: `0` - Minimum: `0` ### settings Text formatting settings, allowing you to customize the appearance of the text element. These settings are applied as CSS properties to style the text, such as `font-size` and `color` — use the exact CSS property name (`color`, not `font-color`), because an unsupported key is silently ignored instead of returning an error. The available settings are determined by the selected style. Refer to the documentation for the specific styles to see which CSS properties can be adjusted. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### start The element's start time, in seconds, determines when it begins playing within its container's timeline. This time is relative to the beginning of the scene it's in or, if the element is part of the movie's elements array, relative to the beginning of the movie itself. The default value is 0, meaning the element starts at the beginning of its container's timeline. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### style The style of the text element, selected from a predefined set of available styles. Each style offers a unique animation. Refer to the linked documentation for a comprehensive overview of available text styles and their corresponding visual characteristics. The default value is "001". | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"001"` | ### text The text content to be displayed within the text element. This property accepts a string value that represents the text to be rendered in the video. Note that HTML formatting is not supported; the string will be rendered as plain text. | | | |--------------|-------------| | **Type** | string | | **Required** | Yes | ### type This field specifies the element's type and must be set to `text` for text elements. | | | |--------------|-------------| | **Type** | string | | **Required** | Yes | | **Enum Values** | `text` | ### variables Defines local variables specific to this element. These variables can be used to dynamically alter the element's properties or content during the rendering process. Variable names must consist of only letters, numbers, and underscores. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### width Sets the width of the element in pixels. The element will be scaled up or down to fit the specified width. A value of -1 instructs the system to maintain the element's original aspect ratio when resizing based on the height property. If 'resize' is set, the 'width' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas width (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### x The horizontal position of the element within the movie canvas, measured to the element's LEFT edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the left edge of the movie canvas. Higher values move the element to the right. Besides a number of pixels, `x` accepts a pixel string (`"250px"`), a percentage of the canvas width (`"30%"`, which may be negative) and the named values `left`, `center` and `right` — flush left, centred, or flush right, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"left"`, `"center"`, `"right"` | ### y Sets the vertical position of the element within the movie canvas, measured to the element's TOP edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the top edge of the movie canvas. Higher values move the element downwards. Besides a number of pixels, `y` accepts a pixel string (`"250px"`), a percentage of the canvas height (`"30%"`, which may be negative) and the named values `top`, `center` and `bottom` — flush top, centred, or flush bottom, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"top"`, `"center"`, `"bottom"` | ### z-index Element's z-index, determining its stacking order within the video. Higher values bring the element to the front, obscuring elements with lower values. Lower values send the element to the back, potentially behind other elements. The value must be an integer between -99 and 99; the default is 0. The natural way of layering elements is by the order of the elements in the `elements` array. If by any reason this does not work in your case, you can use the `z-index` property to manually control the stacking order. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Minimum Value** | -99 | | **Maximum Value** | 99 | ### zoom Zooms the element by a specified percentage. Use positive values (1-10) to zoom in and negative values (-1 to -10) to zoom out. A value of 0 results in no zoom. Combine with the `pan` property to control the focal point during zooming. | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Minimum Value** | -10 | | **Maximum Value** | 10 | # Component --- type: element element_type: component source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-05-12 --- # Component **Type:** object Creates an element with animation to be rendered in the movie or scene. The `component` property specifies the ID of the component to use from the available library of available components, and the `settings` property allows you to customize the component's appearance and behavior. The component library includes a variety of pre-defined components, such as shape animations, animated text boxes, lower-thirds, and more. ### Related links - [Component library](@/reference/components) ## Properties The following properties are required: - `component` - `type` ### cache If `true`, the system will attempt to retrieve and use a previously rendered (cached) version of this element, if an identical version is available. This can significantly reduce processing time. If `false`, a new render of the element will always be performed, regardless of whether a cached version exists. The default value is `true`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | | **Format** | boolean | ### chroma-key Allows you to define a color or a range of colors within the element that will be rendered as transparent. This effect is commonly known as chroma keying or 'green screen'. The `color` property specifies the base color to be made transparent, while the optional `tolerance` property adjusts the sensitivity of the transparency, allowing you to define a range of similar colors to also be included in the transparency effect. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **color**: (string, required) - Set the color for which alpha will be set to 0 (full transparency) - Example: `"#00b140"` * **tolerance**: (integer, optional) - Makes the selection more or less sensitive to changes in color. A value of 1 will select only the provided color. A value of 100 will select all colors, so the full canvas - Default: `25` - Minimum: `1` - Maximum: `100` ### comment A field for adding descriptive notes or internal memos related to the element. This comment is for your reference and does not affect the rendering process. It can be used to keep notes about the element like describing the content or the purpose of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### component The ID of the pre-defined component to use. This ID references a component from the component library. Use the component picker control in the editor to find available components, or refer to the library documentation for a comprehensive list of available component IDs. | | | |--------------|-------------| | **Type** | string | | **Required** | Yes | ### condition A string containing an expression that determines whether the element will be rendered. The element is rendered only if the condition evaluates to true. If the condition is false or an empty string, the element will be skipped and not included in the scene or movie. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### correction Defines image and video correction settings, allowing you to adjust the visual characteristics of the element. This includes properties for adjusting contrast, brightness, saturation, and gamma, enabling fine-tuning of the element's appearance. Values in the edge of the range may result in the element being irrecognizable. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **brightness**: (number, optional) - Adjust the brightness - Default: `0` - Minimum: `-1` - Maximum: `1` * **contrast**: (number, optional) - Adjust the contrast - Default: `1` - Minimum: `-1000` - Maximum: `1000` * **gamma**: (number, optional) - Adjust the gamma - Default: `1` - Minimum: `0.1` - Maximum: `10` * **saturation**: (number, optional) - Adjust the saturation - Default: `1` - Minimum: `0` - Maximum: `3` ### crop Defines the cropping area of the element. It allows you to specify a rectangular region of the element to display, effectively cropping the external parts of the provided area. The `x` and `y` properties define the top-left corner of the cropping rectangle, while the `width` and `height` properties determine the dimensions of the cropped area. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **height**: (integer, required) - Sets the height of the cropping area * **width**: (integer, required) - Sets the width of the cropping area * **x**: (integer, optional) - Sets the left point of cropping - Default: `0` * **y**: (integer, optional) - Sets the top point of cropping - Default: `0` ### duration Defines the duration of the element in seconds. Use a positive value to specify the exact duration. A value of -1 tells the system to automatically calculate the duration based on the intrinsic length of the element's asset (e.g., video or audio file). A value of -2 sets the element's duration to match that of its parent scene (if the element is within a scene) or the entire movie (if the element is in the movie's top-level elements array). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `-2` | | **Format** | float | | **Minimum Value** | -2 | ### extra-time The amount of time, in seconds, to extend the element's duration beyond its natural length. This allows the element to linger on screen after its content has finished playing or displaying. For example, setting `extra-time` to 0.5 will keep the element visible for an additional half-second. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### fade-in The duration, in seconds, of the fade-in effect applied to the element's appearance. A value of `0` means no fade-in effect. Larger values result in a longer fade-in duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### fade-out The duration, in seconds, of the fade-out effect applied to the element's disappearance. A value of `0` means no fade-out effect. Larger values result in a longer fade-out duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### flip-horizontal If `true`, the element will be flipped horizontally, creating a mirror image effect. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### flip-vertical If `true`, the element will be flipped vertically, creating an upside-down image. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### height Sets the height of the element in pixels, scaling the element up or down as needed to fit the specified height. A value of -1 maintains the element's original aspect ratio when resizing based on the width property. If 'resize' is set, the 'height' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas height (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### id A unique identifier for the element within the movie. This string allows you to reference and manage individual elements. If not provided, the system will automatically generate a random string. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"@randomString"` | ### keyframes Animates the element's `x`, `y`, `width` and `height` properties over time. See [the `image` element](@/reference/json-syntax/element/image#keyframes) for the full keyframe schema (`time`, easing functions, per-property easing overrides). > `zoom` keyframes are only supported on `image` and `video` elements. | | | |--------------|-------------| | **Type** | array | | **Required** | No | ### mask URL to a PNG or video file that defines a mask, controlling the transparency of the element. The mask uses a grayscale color scheme: black areas render the element fully transparent, white areas render it fully opaque, and shades of gray create varying levels of partial transparency. This allows you to create complex shapes and effects by selectively hiding portions of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### pan Specifies the direction to pan the element within its container. Valid values are `left`, `top`, `right`, `bottom`, and their combinations like `top-left`. If the `zoom` property is also specified, the pan will occur while zooming. If `zoom` is not specified, the element will pan without zooming. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `left`, `top`, `right`, `bottom`, `top-left`, `top-right`, `bottom-left`, `bottom-right` | ### pan-crop When panning an element, this boolean property determines whether the element is stretched and cropped to fill the movie canvas. If set to `true` (default), the element will be stretched and cropped during panning. If set to `false`, the element will not be stretched and potentially leave empty space within the movie canvas. Example: if `pan-crop` is set to `false` and the movie canvas and element have the same size, panning the element to the left may leave a black bar on the right side of the movie canvas as the element moves to the left. If `pan-crop` is set to `true` (default), the element will be stretched and cropped during panning, so the element will effectively fill the movie canvas. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | ### pan-distance Defines the distance the element pans within its container when the `pan` property is specified. This value, expressed as a floating-point number, determines the amount of movement during the panning effect. Higher values result in faster and more pronounced panning. The allowed range is from 0.01 to 0.5, with a default value of 0.1. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0.1` | | **Format** | float | | **Minimum Value** | 0.01 | | **Maximum Value** | 0.5 | ### position Specifies the position of the element within the movie canvas. Choose from predefined positions like 'top-left', 'top-right', 'bottom-right', 'bottom-left', and 'center-center' to quickly place the element. Selecting 'custom' enables precise positioning using the `x` and `y` properties to define the element's horizontal and vertical coordinates. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"custom"` | | **Enum Values** | `top-left`, `top-right`, `bottom-right`, `bottom-left`, `center-center`, `custom` | ### resize Defines how the element should be resized to fit within the movie canvas. The values `cover` and `fill` stretch the element to completely cover the movie canvas, potentially cropping parts of the element. The values `fit` and `contain` ensure the entire element is visible, potentially leaving empty space within the canvas. When `resize` is set, the `width` and `height` properties are ignored, as the element's size is determined by the chosen resize mode. The value `cover`is a synonym for `fill` and `contain`is a synonym for `fit`. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `cover`, `fill`, `fit`, `contain` | ### rotate Defines the rotation properties of the element. It allows you to specify the angle of rotation and the time it takes to complete the rotation, enabling animated rotation effects. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **angle**: (number, required) - Sets the angle of rotation - Default: `0` - Minimum: `-360` - Maximum: `360` * **speed**: (number, optional) - Sets the time it takes to rotate the provided angle. A zero value means no movement - Default: `0` - Minimum: `0` ### settings Settings to customize the component's appearance and behavior. The available settings depend on the selected component; refer to the component library documentation for details. This allows you to tailor pre-built components to fit your specific video needs. | | | |--------------|-------------| | **Type** | object | | **Required** | No | ### start The element's start time, in seconds, determines when it begins playing within its container's timeline. This time is relative to the beginning of the scene it's in or, if the element is part of the movie's elements array, relative to the beginning of the movie itself. The default value is 0, meaning the element starts at the beginning of its container's timeline. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### type This field specifies the element's type and must be set to `component` for component elements. | | | |--------------|-------------| | **Type** | string | | **Required** | Yes | | **Enum Values** | `component` | ### variables Defines local variables specific to this element. These variables can be used to dynamically alter the element's properties or content during the rendering process. Variable names must consist of only letters, numbers, and underscores. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### width Sets the width of the element in pixels. The element will be scaled up or down to fit the specified width. A value of -1 instructs the system to maintain the element's original aspect ratio when resizing based on the height property. If 'resize' is set, the 'width' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas width (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### x The horizontal position of the element within the movie canvas, measured to the element's LEFT edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the left edge of the movie canvas. Higher values move the element to the right. Besides a number of pixels, `x` accepts a pixel string (`"250px"`), a percentage of the canvas width (`"30%"`, which may be negative) and the named values `left`, `center` and `right` — flush left, centred, or flush right, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"left"`, `"center"`, `"right"` | ### y Sets the vertical position of the element within the movie canvas, measured to the element's TOP edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the top edge of the movie canvas. Higher values move the element downwards. Besides a number of pixels, `y` accepts a pixel string (`"250px"`), a percentage of the canvas height (`"30%"`, which may be negative) and the named values `top`, `center` and `bottom` — flush top, centred, or flush bottom, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"top"`, `"center"`, `"bottom"` | ### z-index Element's z-index, determining its stacking order within the video. Higher values bring the element to the front, obscuring elements with lower values. Lower values send the element to the back, potentially behind other elements. The value must be an integer between -99 and 99; the default is 0. The natural way of layering elements is by the order of the elements in the `elements` array. If by any reason this does not work in your case, you can use the `z-index` property to manually control the stacking order. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Minimum Value** | -99 | | **Maximum Value** | 99 | ### zoom Zooms the element by a specified percentage. Use positive values (1-10) to zoom in and negative values (-1 to -10) to zoom out. A value of 0 results in no zoom. Combine with the `pan` property to control the focal point during zooming. | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Minimum Value** | -10 | | **Maximum Value** | 10 | # HTML --- type: element element_type: html source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-05-12 --- # HTML **Type:** object Defines an HTML element, allowing you to render HTML snippets or capture screenshots of web pages within your video. You can provide the HTML code directly or specify a URL to an external webpage. The element supports HTML5, CSS3, and JavaScript. You can also configure a delay before capturing the screenshot and enable Tailwind CSS styling for the HTML snippet. ## Required Properties - `type` ## Properties ### cache If `true`, the system will attempt to retrieve and use a previously rendered (cached) version of this element, if an identical version is available. This can significantly reduce processing time. If `false`, a new render of the element will always be performed, regardless of whether a cached version exists. The default value is `true`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | | **Format** | boolean | ### chroma-key Allows you to define a color or a range of colors within the element that will be rendered as transparent. This effect is commonly known as chroma keying or 'green screen'. The `color` property specifies the base color to be made transparent, while the optional `tolerance` property adjusts the sensitivity of the transparency, allowing you to define a range of similar colors to also be included in the transparency effect. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **color**: (string, required) - Set the color for which alpha will be set to 0 (full transparency) - Example: `"#00b140"` * **tolerance**: (integer, optional) - Makes the selection more or less sensitive to changes in color. A value of 1 will select only the provided color. A value of 100 will select all colors, so the full canvas - Default: `25` - Minimum: `1` - Maximum: `100` ### comment A field for adding descriptive notes or internal memos related to the element. This comment is for your reference and does not affect the rendering process. It can be used to keep notes about the element like describing the content or the purpose of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### condition A string containing an expression that determines whether the element will be rendered. The element is rendered only if the condition evaluates to true. If the condition is false or an empty string, the element will be skipped and not included in the scene or movie. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### correction Defines image and video correction settings, allowing you to adjust the visual characteristics of the element. This includes properties for adjusting contrast, brightness, saturation, and gamma, enabling fine-tuning of the element's appearance. Values in the edge of the range may result in the element being irrecognizable. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **brightness**: (number, optional) - Adjust the brightness - Default: `0` - Minimum: `-1` - Maximum: `1` * **contrast**: (number, optional) - Adjust the contrast - Default: `1` - Minimum: `-1000` - Maximum: `1000` * **gamma**: (number, optional) - Adjust the gamma - Default: `1` - Minimum: `0.1` - Maximum: `10` * **saturation**: (number, optional) - Adjust the saturation - Default: `1` - Minimum: `0` - Maximum: `3` ### crop Defines the cropping area of the element. It allows you to specify a rectangular region of the element to display, effectively cropping the external parts of the provided area. The `x` and `y` properties define the top-left corner of the cropping rectangle, while the `width` and `height` properties determine the dimensions of the cropped area. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **height**: (integer, required) - Sets the height of the cropping area * **width**: (integer, required) - Sets the width of the cropping area * **x**: (integer, optional) - Sets the left point of cropping - Default: `0` * **y**: (integer, optional) - Sets the top point of cropping - Default: `0` ### duration Defines the duration of the element in seconds. Use a positive value to specify the exact duration. A value of -1 tells the system to automatically calculate the duration based on the intrinsic length of the element's asset (e.g., video or audio file). A value of -2 sets the element's duration to match that of its parent scene (if the element is within a scene) or the entire movie (if the element is in the movie's top-level elements array). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `-2` | | **Format** | float | | **Minimum Value** | -2 | ### extra-time The amount of time, in seconds, to extend the element's duration beyond its natural length. This allows the element to linger on screen after its content has finished playing or displaying. For example, setting `extra-time` to 0.5 will keep the element visible for an additional half-second. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### fade-in The duration, in seconds, of the fade-in effect applied to the element's appearance. A value of `0` means no fade-in effect. Larger values result in a longer fade-in duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### fade-out The duration, in seconds, of the fade-out effect applied to the element's disappearance. A value of `0` means no fade-out effect. Larger values result in a longer fade-out duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### flip-horizontal If `true`, the element will be flipped horizontally, creating a mirror image effect. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### flip-vertical If `true`, the element will be flipped vertically, creating an upside-down image. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### height Sets the height of the element in pixels, scaling the element up or down as needed to fit the specified height. A value of -1 maintains the element's original aspect ratio when resizing based on the width property. If 'resize' is set, the 'height' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas height (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### html The HTML code to be rendered by this element. This should be a valid HTML5 snippet, including CSS3 and Javascript. The HTML will be rendered as part of the video. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### id A unique identifier for the element within the movie. This string allows you to reference and manage individual elements. If not provided, the system will automatically generate a random string. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"@randomString"` | ### keyframes Animates the element's `x`, `y`, `width` and `height` properties over time. See [the `image` element](@/reference/json-syntax/element/image#keyframes) for the full keyframe schema (`time`, easing functions, per-property easing overrides). > `zoom` keyframes are only supported on `image` and `video` elements. | | | |--------------|-------------| | **Type** | array | | **Required** | No | ### mask URL to a PNG or video file that defines a mask, controlling the transparency of the element. The mask uses a grayscale color scheme: black areas render the element fully transparent, white areas render it fully opaque, and shades of gray create varying levels of partial transparency. This allows you to create complex shapes and effects by selectively hiding portions of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### pan Specifies the direction to pan the element within its container. Valid values are `left`, `top`, `right`, `bottom`, and their combinations like `top-left`. If the `zoom` property is also specified, the pan will occur while zooming. If `zoom` is not specified, the element will pan without zooming. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `left`, `top`, `right`, `bottom`, `top-left`, `top-right`, `bottom-left`, `bottom-right` | ### pan-crop When panning an element, this boolean property determines whether the element is stretched and cropped to fill the movie canvas. If set to `true` (default), the element will be stretched and cropped during panning. If set to `false`, the element will not be stretched and potentially leave empty space within the movie canvas. Example: if `pan-crop` is set to `false` and the movie canvas and element have the same size, panning the element to the left may leave a black bar on the right side of the movie canvas as the element moves to the left. If `pan-crop` is set to `true` (default), the element will be stretched and cropped during panning, so the element will effectively fill the movie canvas. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | ### pan-distance Defines the distance the element pans within its container when the `pan` property is specified. This value, expressed as a floating-point number, determines the amount of movement during the panning effect. Higher values result in faster and more pronounced panning. The allowed range is from 0.01 to 0.5, with a default value of 0.1. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0.1` | | **Format** | float | | **Minimum Value** | 0.01 | | **Maximum Value** | 0.5 | ### position Specifies the position of the element within the movie canvas. Choose from predefined positions like 'top-left', 'top-right', 'bottom-right', 'bottom-left', and 'center-center' to quickly place the element. Selecting 'custom' enables precise positioning using the `x` and `y` properties to define the element's horizontal and vertical coordinates. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"custom"` | | **Enum Values** | `top-left`, `top-right`, `bottom-right`, `bottom-left`, `center-center`, `custom` | ### resize Defines how the element should be resized to fit within the movie canvas. The values `cover` and `fill` stretch the element to completely cover the movie canvas, potentially cropping parts of the element. The values `fit` and `contain` ensure the entire element is visible, potentially leaving empty space within the canvas. When `resize` is set, the `width` and `height` properties are ignored, as the element's size is determined by the chosen resize mode. The value `cover`is a synonym for `fill` and `contain`is a synonym for `fit`. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `cover`, `fill`, `fit`, `contain` | ### rotate Defines the rotation properties of the element. It allows you to specify the angle of rotation and the time it takes to complete the rotation, enabling animated rotation effects. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **angle**: (number, required) - Sets the angle of rotation - Default: `0` - Minimum: `-360` - Maximum: `360` * **speed**: (number, optional) - Sets the time it takes to rotate the provided angle. A zero value means no movement - Default: `0` - Minimum: `0` ### src The URL to the web page to be captured as a screenshot and rendered as part of the video. The content of this webpage will be rendered as an image in the video. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### start The element's start time, in seconds, determines when it begins playing within its container's timeline. This time is relative to the beginning of the scene it's in or, if the element is part of the movie's elements array, relative to the beginning of the movie itself. The default value is 0, meaning the element starts at the beginning of its container's timeline. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### tailwindcss If `true`, enables the use of Tailwind CSS classes within the `html` property to style the HTML snippet. If `false`, Tailwind CSS styling will not be applied. Defaults to `false`. When enabled, ensure your HTML snippet includes valid Tailwind CSS classes for styling. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### type This field specifies the element's type and must be set to `html` to indicate that this element is an HTML snippet or webpage capture. | | | |--------------|-------------| | **Type** | string | | **Required** | Yes | | **Enum Values** | `html` | ### variables Defines local variables specific to this element. These variables can be used to dynamically alter the element's properties or content during the rendering process. Variable names must consist of only letters, numbers, and underscores. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### wait The time, in seconds, to wait after the HTML content has loaded before taking a screenshot of the webpage specified by the `src` property. This allows dynamic content to load fully before the screenshot is captured. The value must be between 0 and 5 seconds; the default is 2 seconds. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `2` | | **Minimum Value** | 0 | | **Maximum Value** | 5 | ### width Sets the width of the element in pixels. The element will be scaled up or down to fit the specified width. A value of -1 instructs the system to maintain the element's original aspect ratio when resizing based on the height property. If 'resize' is set, the 'width' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas width (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### x The horizontal position of the element within the movie canvas, measured to the element's LEFT edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the left edge of the movie canvas. Higher values move the element to the right. Besides a number of pixels, `x` accepts a pixel string (`"250px"`), a percentage of the canvas width (`"30%"`, which may be negative) and the named values `left`, `center` and `right` — flush left, centred, or flush right, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"left"`, `"center"`, `"right"` | ### y Sets the vertical position of the element within the movie canvas, measured to the element's TOP edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the top edge of the movie canvas. Higher values move the element downwards. Besides a number of pixels, `y` accepts a pixel string (`"250px"`), a percentage of the canvas height (`"30%"`, which may be negative) and the named values `top`, `center` and `bottom` — flush top, centred, or flush bottom, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"top"`, `"center"`, `"bottom"` | ### z-index Element's z-index, determining its stacking order within the video. Higher values bring the element to the front, obscuring elements with lower values. Lower values send the element to the back, potentially behind other elements. The value must be an integer between -99 and 99; the default is 0. The natural way of layering elements is by the order of the elements in the `elements` array. If by any reason this does not work in your case, you can use the `z-index` property to manually control the stacking order. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Minimum Value** | -99 | | **Maximum Value** | 99 | ### zoom Zooms the element by a specified percentage. Use positive values (1-10) to zoom in and negative values (-1 to -10) to zoom out. A value of 0 results in no zoom. Combine with the `pan` property to control the focal point during zooming. | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Minimum Value** | -10 | | **Maximum Value** | 10 | # Audio --- type: element element_type: audio source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-05-12 --- # Audio **Type:** object Defines an audio element to be included in the video. The audio source can be specified using a URL, supporting common audio formats like MP3 and WAV. Control playback behavior by defining the number of times the audio loops and the starting point within the audio using the seek property. You can also control audio properties such as muted and volume. ## Properties ### cache If `true`, the system will attempt to retrieve and use a previously rendered (cached) version of this element, if an identical version is available. This can significantly reduce processing time. If `false`, a new render of the element will always be performed, regardless of whether a cached version exists. The default value is `true`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | | **Format** | boolean | ### comment A field for adding descriptive notes or internal memos related to the element. This comment is for your reference and does not affect the rendering process. It can be used to keep notes about the element like describing the content or the purpose of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### condition A string containing an expression that determines whether the element will be rendered. The element is rendered only if the condition evaluates to true. If the condition is false or an empty string, the element will be skipped and not included in the scene or movie. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### duration Defines the duration of the element in seconds. Use a positive value to specify the element's length. A value of -1 instructs the system to automatically set the duration based on the intrinsic length of the asset or file used by the element. A value of -2 sets the element's duration to match that of its parent scene (if it's inside a scene) or the movie (if it's in the movie elements array). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `-1` | | **Format** | float | ### extra-time The amount of time, in seconds, to extend the element's duration beyond its natural length. This allows the element to linger on screen after its content has finished playing or displaying. For example, setting `extra-time` to 0.5 will keep the element visible for an additional half-second. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### fade-in The duration, in seconds, of the fade-in effect applied to the element's appearance. A value of `0` means no fade-in effect. Larger values result in a longer fade-in duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### fade-out The duration, in seconds, of the fade-out effect applied to the element's disappearance. A value of `0` means no fade-out effect. Larger values result in a longer fade-out duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### id A unique identifier for the element within the movie. This string allows you to reference and manage individual elements. If not provided, the system will automatically generate a random string. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"@randomString"` | ### loop Specifies the number of times the audio will play. The default value of 1 means the audio plays once and then stops. A value of -1 indicates the audio should loop indefinitely. If loop is set, the `duration` property must be adjusted to match the looped audio length. For infinite loops, set `duration` to -2 to extend the duration of the audio element to match the element container (being either the parent scene or the movie). | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Minimum Value** | -1 | ### muted If `true`, the audio track of the element (e.g., a video or audio file) will be muted, effectively silencing it. If `false` or omitted, the audio will play according to its original volume or the `volume` setting. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### seek Specifies the time, in seconds, at which the audio file should fast forward to. Positive values seek forward from the beginning, while negative values seek backward from the end. By default, the playback starts at the beginning (0 seconds). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### speed Sets the playback speed of the audio. A value of `1` is normal speed, values greater than `1` play faster (e.g. `1.5` is 50% faster, `2` is double speed) and values lower than `1` play slower (e.g. `0.5` is half speed). The audio tempo is changed while preserving its pitch (it does not sound higher or lower). Changing the speed shortens or lengthens the element: with `duration` set to `-1` (auto), a faster audio produces a shorter element. The `seek` value is measured in the original (un-sped) asset timeline, so seeking happens before the speed change. The acceptable range is from 0.5 to 4. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `1` | | **Minimum Value** | 0.5 | | **Maximum Value** | 4 | | **Format** | float | ### src The URL to the audio asset file. This should be a publicly accessible URL pointing to the audio file, which can be in MP3, WAV, or any other common audio format. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Format** | uri | ### start The element's start time, in seconds, determines when it begins playing within its container's timeline. This time is relative to the beginning of the scene it's in or, if the element is part of the movie's elements array, relative to the beginning of the movie itself. The default value is 0, meaning the element starts at the beginning of its container's timeline. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### type This field specifies the element's type and must be set to `audio` for audio elements. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `audio` | ### variables Defines local variables specific to this element. These variables can be used to dynamically alter the element's properties or content during the rendering process. Variable names must consist of only letters, numbers, and underscores. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### volume Controls the volume gain of the audio track (e.g., a video or audio file). This is a multiplier applied to the original audio level. A value of `1` represents the original volume (no gain), values greater than `1` increase the volume, and values less than `1` decrease the volume. The acceptable range is from 0 to 10. For background music with voiceovers, a usual value is `0.2`. Increasing the volume of the audio track can reduce the quality of the audio. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `1` | | **Minimum Value** | 0 | | **Maximum Value** | 10 | ### z-index Element's z-index, determining its stacking order within the video. Higher values bring the element to the front, obscuring elements with lower values. Lower values send the element to the back, potentially behind other elements. The value must be an integer between -99 and 99; the default is 0. The natural way of layering elements is by the order of the elements in the `elements` array. If by any reason this does not work in your case, you can use the `z-index` property to manually control the stacking order. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Minimum Value** | -99 | | **Maximum Value** | 99 | # Audiogram --- type: element element_type: audiogram source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-05-12 --- # Audiogram **Type:** object Visualizes the audio waveform of the scene or movie as an audiogram. The audiogram's appearance can be customized with properties such as color, opacity, relative amplitude, width, and height. A width or height of -1 will inherit the movie's dimensions. The audiogram's duration can be set explicitly or configured to match the duration of its parent scene or the movie itself. ## Required Properties - `type` ## Properties ### amplitude Defines the scaling factor for the audiogram's wave amplitude, influencing the visual prominence of the waves. This value ranges from 0 to 10, where higher values result in taller and more pronounced waves, while lower values create subtler visualizations. A default value of 5 provides a balanced visual representation. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `5` | | **Format** | float | | **Minimum Value** | 0 | | **Maximum Value** | 10 | ### cache If `true`, the system will attempt to retrieve and use a previously rendered (cached) version of this element, if an identical version is available. This can significantly reduce processing time. If `false`, a new render of the element will always be performed, regardless of whether a cached version exists. The default value is `true`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | | **Format** | boolean | ### chroma-key Allows you to define a color or a range of colors within the element that will be rendered as transparent. This effect is commonly known as chroma keying or 'green screen'. The `color` property specifies the base color to be made transparent, while the optional `tolerance` property adjusts the sensitivity of the transparency, allowing you to define a range of similar colors to also be included in the transparency effect. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **color**: (string, required) - Set the color for which alpha will be set to 0 (full transparency) - Example: `"#00b140"` * **tolerance**: (integer, optional) - Makes the selection more or less sensitive to changes in color. A value of 1 will select only the provided color. A value of 100 will select all colors, so the full canvas - Default: `25` - Minimum: `1` - Maximum: `100` ### color The hexadecimal color code (e.g., `#FF0000` for red) that defines the color of the waves displayed in the audiogram visualization. This allows you to customize the audiogram's appearance to match your video's aesthetics. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### comment A field for adding descriptive notes or internal memos related to the element. This comment is for your reference and does not affect the rendering process. It can be used to keep notes about the element like describing the content or the purpose of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### condition A string containing an expression that determines whether the element will be rendered. The element is rendered only if the condition evaluates to true. If the condition is false or an empty string, the element will be skipped and not included in the scene or movie. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### correction Defines image and video correction settings, allowing you to adjust the visual characteristics of the element. This includes properties for adjusting contrast, brightness, saturation, and gamma, enabling fine-tuning of the element's appearance. Values in the edge of the range may result in the element being irrecognizable. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **brightness**: (number, optional) - Adjust the brightness - Default: `0` - Minimum: `-1` - Maximum: `1` * **contrast**: (number, optional) - Adjust the contrast - Default: `1` - Minimum: `-1000` - Maximum: `1000` * **gamma**: (number, optional) - Adjust the gamma - Default: `1` - Minimum: `0.1` - Maximum: `10` * **saturation**: (number, optional) - Adjust the saturation - Default: `1` - Minimum: `0` - Maximum: `3` ### crop Defines the cropping area of the element. It allows you to specify a rectangular region of the element to display, effectively cropping the external parts of the provided area. The `x` and `y` properties define the top-left corner of the cropping rectangle, while the `width` and `height` properties determine the dimensions of the cropped area. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **height**: (integer, required) - Sets the height of the cropping area * **width**: (integer, required) - Sets the width of the cropping area * **x**: (integer, optional) - Sets the left point of cropping - Default: `0` * **y**: (integer, optional) - Sets the top point of cropping - Default: `0` ### duration Defines the duration of the audiogram element in seconds. Use a positive value to specify the exact duration. A value of -1 instructs the system to automatically calculate the duration based on the intrinsic length of the audio being visualized. A value of -2 sets the audiogram's duration to match that of its parent scene (if the audiogram is within a scene) or the entire movie (if the audiogram is in the movie's top-level elements array). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `-2` | | **Format** | float | | **Minimum Value** | -2 | ### extra-time The amount of time, in seconds, to extend the element's duration beyond its natural length. This allows the element to linger on screen after its content has finished playing or displaying. For example, setting `extra-time` to 0.5 will keep the element visible for an additional half-second. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### fade-in The duration, in seconds, of the fade-in effect applied to the element's appearance. A value of `0` means no fade-in effect. Larger values result in a longer fade-in duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### fade-out The duration, in seconds, of the fade-out effect applied to the element's disappearance. A value of `0` means no fade-out effect. Larger values result in a longer fade-out duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### flip-horizontal If `true`, the element will be flipped horizontally, creating a mirror image effect. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### flip-vertical If `true`, the element will be flipped vertically, creating an upside-down image. The default value is `false`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### height Sets the height of the element in pixels, scaling the element up or down as needed to fit the specified height. A value of -1 maintains the element's original aspect ratio when resizing based on the width property. If 'resize' is set, the 'height' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas height (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### id A unique identifier for the element within the movie. This string allows you to reference and manage individual elements. If not provided, the system will automatically generate a random string. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"@randomString"` | ### keyframes Animates the element's `x`, `y`, `width` and `height` properties over time. See [the `image` element](@/reference/json-syntax/element/image#keyframes) for the full keyframe schema (`time`, easing functions, per-property easing overrides). > `zoom` keyframes are only supported on `image` and `video` elements. | | | |--------------|-------------| | **Type** | array | | **Required** | No | ### mask URL to a PNG or video file that defines a mask, controlling the transparency of the element. The mask uses a grayscale color scheme: black areas render the element fully transparent, white areas render it fully opaque, and shades of gray create varying levels of partial transparency. This allows you to create complex shapes and effects by selectively hiding portions of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### opacity The opacity of the audiogram, ranging from 0.0 (fully transparent) to 1.0 (fully opaque). A value of 0.5 represents 50% transparency. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0.5` | | **Format** | float | | **Minimum Value** | 0 | | **Maximum Value** | 1 | ### pan Specifies the direction to pan the element within its container. Valid values are `left`, `top`, `right`, `bottom`, and their combinations like `top-left`. If the `zoom` property is also specified, the pan will occur while zooming. If `zoom` is not specified, the element will pan without zooming. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `left`, `top`, `right`, `bottom`, `top-left`, `top-right`, `bottom-left`, `bottom-right` | ### pan-crop When panning an element, this boolean property determines whether the element is stretched and cropped to fill the movie canvas. If set to `true` (default), the element will be stretched and cropped during panning. If set to `false`, the element will not be stretched and potentially leave empty space within the movie canvas. Example: if `pan-crop` is set to `false` and the movie canvas and element have the same size, panning the element to the left may leave a black bar on the right side of the movie canvas as the element moves to the left. If `pan-crop` is set to `true` (default), the element will be stretched and cropped during panning, so the element will effectively fill the movie canvas. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | ### pan-distance Defines the distance the element pans within its container when the `pan` property is specified. This value, expressed as a floating-point number, determines the amount of movement during the panning effect. Higher values result in faster and more pronounced panning. The allowed range is from 0.01 to 0.5, with a default value of 0.1. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0.1` | | **Format** | float | | **Minimum Value** | 0.01 | | **Maximum Value** | 0.5 | ### position Specifies the position of the element within the movie canvas. Choose from predefined positions like 'top-left', 'top-right', 'bottom-right', 'bottom-left', and 'center-center' to quickly place the element. Selecting 'custom' enables precise positioning using the `x` and `y` properties to define the element's horizontal and vertical coordinates. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"custom"` | | **Enum Values** | `top-left`, `top-right`, `bottom-right`, `bottom-left`, `center-center`, `custom` | ### resize Defines how the element should be resized to fit within the movie canvas. The values `cover` and `fill` stretch the element to completely cover the movie canvas, potentially cropping parts of the element. The values `fit` and `contain` ensure the entire element is visible, potentially leaving empty space within the canvas. When `resize` is set, the `width` and `height` properties are ignored, as the element's size is determined by the chosen resize mode. The value `cover`is a synonym for `fill` and `contain`is a synonym for `fit`. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `cover`, `fill`, `fit`, `contain` | ### rotate Defines the rotation properties of the element. It allows you to specify the angle of rotation and the time it takes to complete the rotation, enabling animated rotation effects. | | | |--------------|-------------| | **Type** | object | | **Required** | No | This object contains the following properties: * **angle**: (number, required) - Sets the angle of rotation - Default: `0` - Minimum: `-360` - Maximum: `360` * **speed**: (number, optional) - Sets the time it takes to rotate the provided angle. A zero value means no movement - Default: `0` - Minimum: `0` ### start The element's start time, in seconds, determines when it begins playing within its container's timeline. This time is relative to the beginning of the scene it's in or, if the element is part of the movie's elements array, relative to the beginning of the movie itself. The default value is 0, meaning the element starts at the beginning of its container's timeline. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### type This field specifies the element's type and must be set to `audiogram` to indicate that this element is an audiogram visualization. | | | |--------------|-------------| | **Type** | string | | **Required** | Yes | | **Enum Values** | `audiogram` | ### variables Defines local variables specific to this element. These variables can be used to dynamically alter the element's properties or content during the rendering process. Variable names must consist of only letters, numbers, and underscores. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### width Sets the width of the element in pixels. The element will be scaled up or down to fit the specified width. A value of -1 instructs the system to maintain the element's original aspect ratio when resizing based on the height property. If 'resize' is set, the 'width' property is ignored. The minimum accepted value is -1. Besides a number of pixels, this property accepts a pixel string (`"800px"`) and a percentage of the canvas width (`"70%"`). Named values are not accepted here — `center` is meaningless as a size. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | integer or string | | **Required** | No | | **Default Value** | `-1` | | **Minimum Value** | -1 | | **Accepted values** | `800`, `"800px"`, `"70%"`, `-1` (keep the aspect ratio) | ### x The horizontal position of the element within the movie canvas, measured to the element's LEFT edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the left edge of the movie canvas. Higher values move the element to the right. Besides a number of pixels, `x` accepts a pixel string (`"250px"`), a percentage of the canvas width (`"30%"`, which may be negative) and the named values `left`, `center` and `right` — flush left, centred, or flush right, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"left"`, `"center"`, `"right"` | ### y Sets the vertical position of the element within the movie canvas, measured to the element's TOP edge. This property is only applicable when the `position` property is set to `custom`. A value of `0` places the element at the top edge of the movie canvas. Higher values move the element downwards. Besides a number of pixels, `y` accepts a pixel string (`"250px"`), a percentage of the canvas height (`"30%"`, which may be negative) and the named values `top`, `center` and `bottom` — flush top, centred, or flush bottom, with no margin. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | | |--------------|-------------| | **Type** | number or string | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Accepted values** | `250`, `"250px"`, `"30%"`, `"top"`, `"center"`, `"bottom"` | ### z-index Element's z-index, determining its stacking order within the video. Higher values bring the element to the front, obscuring elements with lower values. Lower values send the element to the back, potentially behind other elements. The value must be an integer between -99 and 99; the default is 0. The natural way of layering elements is by the order of the elements in the `elements` array. If by any reason this does not work in your case, you can use the `z-index` property to manually control the stacking order. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Minimum Value** | -99 | | **Maximum Value** | 99 | ### zoom Zooms the element by a specified percentage. Use positive values (1-10) to zoom in and negative values (-1 to -10) to zoom out. A value of 0 results in no zoom. Combine with the `pan` property to control the focal point during zooming. | | | |--------------|-------------| | **Type** | integer | | **Required** | No | | **Minimum Value** | -10 | | **Maximum Value** | 10 | # Voice --- type: element element_type: voice source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-07-14 --- # Voice element **Type:** object Creates a voiceover element by converting the provided text into synthesized speech. The text to be spoken is specified using the `text` property. The `voice` property determines the voice to use, and the `model` property selects the text-to-speech provider (`azure` or `elevenlabs`). Optionally, a `connection` ID can be provided to utilize your own API key for voice generation. > Important note: Text-to-speech voiceovers may be used only for lawful narration and authorized content. Users may not use voice features for impersonation, deception, fraud, unauthorized voice cloning, celebrity imitation, harassment, adult content, or any use that violates third-party rights or provider terms. ## Working with the Voice element The Voice element produces a text-to-speech voiceover for your video. Currently supported TTS providers are Microsoft Azure (default, included in every plan) and ElevenLabs. **Note** The `azure` model is the default model and will be used if no model is specified. **Example** This example creates a voiceover for a video using the Azure model. ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "voice", "text": "Hello, world!", "voice": "en-US-EmmaMultilingualNeural", "model": "azure" } ] } ] } ``` ### Voice generation costs Generating a voiceover may consume credits depending on the model you choose. The exact per-minute cost for each voice model is summarised in [Credit consumption](@/reference/credits/credit-consumption). The `azure` default is included in all plans and does not consume credits. Voiceovers are **cached** to avoid calling the upstream provider for the same voiceover multiple times. If you call the API with the same parameters for the same voiceover again, the cached version is reused, avoiding unnecessary costs. To regenerate a voiceover, set the `cache` property to `false`. #### Using your own API key If you already have an ElevenLabs or Azure API account, you can use your API key to generate your voiceovers. This is specially useful for ElevenLabs custom voices. To use your own API key: 1. You need to create a connection in the [Connections](https://json2video.com/dashboard/connections) page. 2. You need to provide the connection ID to the `connection` property in the Voice element. **Example** This example creates a voiceover for a video using the ElevenLabs model and your own API key. ```json { "resolution": "full-hd", "scenes": [ { "elements": [ { "type": "voice", "text": "Hello, world!", "model": "elevenlabs", "voice": "Daniel", "connection": "my-connection-id" } ] } ] } ``` ### Choosing the right voice Finding the right voice for your project can be a challenge. #### Azure voices Azure voices have this format: `en-US-EmmaMultilingualNeural`. The first part is the language code (2 digits), the second part is the country code (2 digits) and the third part is the name of the voice. Browse the complete catalog of Azure voices supported by JSON2Video, organised by language, at [json2video.com/ai-voices/azure/languages/](https://json2video.com/ai-voices/azure/languages/). Each voice page lists the exact short name to put in the `voice` property, plus an audio sample. #### ElevenLabs voices ElevenLabs voices have natural names like `Daniel`, `Serena`, `Antoni`, `Bella`, `Nova`, `Shimmer` and more. You can also use the ElevenLabs's `voiceID` to specify the voice you want to use. Browse the complete catalog of ElevenLabs voices supported by JSON2Video, organised by language, at [json2video.com/ai-voices/elevenlabs/languages/](https://json2video.com/ai-voices/elevenlabs/languages/). Each voice page lists the name and voice ID to put in the `voice` property, plus an audio sample. Cloned or custom voices are the exception: they only exist in your own ElevenLabs account, so they will not appear in the catalog above. Use a `connection` and take the voice ID from your ElevenLabs dashboard. ## Properties The following properties are required: - `text` - `type` ### cache If `true`, the system will attempt to retrieve and use a previously rendered (cached) version of this element, if an identical version is available. This can significantly reduce processing time. If `false`, a new render of the element will always be performed, regardless of whether a cached version exists. The default value is `true`. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `true` | | **Format** | boolean | ### comment A field for adding descriptive notes or internal memos related to the element. This comment is for your reference and does not affect the rendering process. It can be used to keep notes about the element like describing the content or the purpose of the element. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### condition A string containing an expression that determines whether the element will be rendered. The element is rendered only if the condition evaluates to true. If the condition is false or an empty string, the element will be skipped and not included in the scene or movie. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### connection The ID of your pre-configured connection to use for voice generation. Connections are defined within the application's dashboard. By specifying a connection ID, you can leverage the API key associated with that connection, enabling you to use your own account with the TTS provider for voice generation. If a connection ID is not provided, the default JSON2Video API keys will be used, potentially deducting credits for the API calls. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### duration Defines the duration of the element in seconds. Use a positive value to specify the element's length. A value of -1 instructs the system to automatically set the duration based on the intrinsic length of the asset or file used by the element. A value of -2 sets the element's duration to match that of its parent scene (if it's inside a scene) or the movie (if it's in the movie elements array). | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `-1` | | **Format** | float | ### extra-time The amount of time, in seconds, to extend the element's duration beyond its natural length. This allows the element to linger on screen after its content has finished playing or displaying. For example, setting `extra-time` to 0.5 will keep the element visible for an additional half-second. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### fade-in The duration, in seconds, of the fade-in effect applied to the element's appearance. A value of `0` means no fade-in effect. Larger values result in a longer fade-in duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### fade-out The duration, in seconds, of the fade-out effect applied to the element's disappearance. A value of `0` means no fade-out effect. Larger values result in a longer fade-out duration. The value must be a non-negative number. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Format** | float | | **Minimum Value** | 0 | ### id A unique identifier for the element within the movie. This string allows you to reference and manage individual elements. If not provided, the system will automatically generate a random string. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"@randomString"` | ### model The text-to-speech provider to use for synthesizing the voice. `elevenlabs` is an alias of `elevenlabs-v2`. Be aware that some models may consume credits for each request. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Enum Values** | `azure`, `elevenlabs`, `elevenlabs-v2`, `elevenlabs-flash-v2-5`, `elevenlabs-v3` | ### muted If `true`, the audio track of the element (e.g., a video or audio file) will be muted, effectively silencing it. If `false` or omitted, the audio will play according to its original volume or the `volume` setting. | | | |--------------|-------------| | **Type** | boolean | | **Required** | No | | **Default Value** | `false` | ### speed Sets the playback speed of the synthesized voice. A value of `1` is normal speed, values greater than `1` play faster (e.g. `1.5` is 50% faster, `2` is double speed) and values lower than `1` play slower (e.g. `0.5` is half speed). The voice tempo is changed while preserving its pitch, so it does not sound higher or lower. Because the voice audio is generated first and then sped up, any subtitles generated from it stay in sync. Changing the speed shortens or lengthens the element accordingly. The acceptable range is from 0.5 to 4. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `1` | | **Minimum Value** | 0.5 | | **Maximum Value** | 4 | | **Format** | float | ### start The element's start time, in seconds, determines when it begins playing within its container's timeline. This time is relative to the beginning of the scene it's in or, if the element is part of the movie's elements array, relative to the beginning of the movie itself. The default value is 0, meaning the element starts at the beginning of its container's timeline. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | float | ### text The text content to be synthesized into speech. | | | |--------------|-------------| | **Type** | string | | **Required** | Yes | ### type This field specifies the element's type and must be set to `voice` for voiceover elements. | | | |--------------|-------------| | **Type** | string | | **Required** | Yes | | **Enum Values** | `voice` | ### variables Defines local variables specific to this element. These variables can be used to dynamically alter the element's properties or content during the rendering process. Variable names must consist of only letters, numbers, and underscores. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | ### voice The name of the voice to be used for text-to-speech synthesis. This value determines which voice will be used to generate the audio. Refer to the available voices documentation to explore the supported options. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### volume Controls the volume gain of the audio track (e.g., a video or audio file). This is a multiplier applied to the original audio level. A value of `1` represents the original volume (no gain), values greater than `1` increase the volume, and values less than `1` decrease the volume. The acceptable range is from 0 to 10. For background music with voiceovers, a usual value is `0.2`. Increasing the volume of the audio track can reduce the quality of the audio. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `1` | | **Minimum Value** | 0 | | **Maximum Value** | 10 | ### z-index Element's z-index, determining its stacking order within the video. Higher values bring the element to the front, obscuring elements with lower values. Lower values send the element to the back, potentially behind other elements. The value must be an integer between -99 and 99; the default is 0. The natural way of layering elements is by the order of the elements in the `elements` array. If by any reason this does not work in your case, you can use the `z-index` property to manually control the stacking order. | | | |--------------|-------------| | **Type** | number | | **Required** | No | | **Default Value** | `0` | | **Format** | integer | | **Minimum Value** | -99 | | **Maximum Value** | 99 | # Subtitles --- type: element element_type: subtitles source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-05-12 --- # Subtitles element **Type:** object Defines a subtitles element, allowing you to add subtitles to the video. Subtitles can be automatically generated by transcribing the audio or provided via a URL to a subtitle file (SRT, VTT or ASS). You can customize the appearance of the subtitles using the `settings` property, including style, font, colors, position and more. ## Working with the Subtitles element ### Special considerations The subtitles element works a bit differently than other elements: - It can only be used in the Movie `elements` array, meaning that you cannot enable or disable it on a per-scene basis. - You can't have multiple subtitles elements in a single movie - It's always processed at the end of the rendering process once the movie is complete - The automatic transcription "listens" to the audio track of the movie and transcribes it into text. If the voice is not clear, the transcription may not be accurate. ### Providing your own captions If you provide the captions in the `captions` property, the voiceover will not be transcribed and the captions will be displayed instead. The supported formats for the `captions` property are: SRT, VTT and ASS. The word highlighting option is not available when providing your own captions unless your provide the captions in ASS format and the captions include the timing for each word. ### Manual review of the transcription In some cases, you may want to manually review the transcription before finally publishing the movie. To manually review the transcription: 1. Render the movie with the automatic transcription enabled. 2. Download the transcription file in ASS format. You will find a URL to file in the `GET /v2/movies` response object (`ass` property) along with the URL to the rendered movie. 3. Open the ASS file with a text editor and review the transcription. 4. Upload the edited ASS file to public server and get a URL to the file. 5. Render the movie again, this time providing the URL to the edited ASS file in the `captions` property. ## Properties The following properties are required: - `type` ### captions Specifies the captions to be used as subtitles. This can be either a URL pointing to a subtitle file, or the actual subtitle content directly embedded as a string. Supported subtitle formats for URLs or inline subtitles are: SRT, VTT, and ASS. If this property is omitted, the subtitles will be automatically generated from the audio track of the video. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### comment A field for adding descriptive notes or internal memos related to the subtitles element. This comment is for your reference and does not affect the rendering process. | | | |--------------|-------------| | **Type** | string | | **Required** | No | ### language The language of the audio to be transcribed. Specify a supported language code (e.g., `en` for English, `es` for Spanish) to improve transcription accuracy. Use `auto` to enable automatic language detection by the API. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"auto"` | | **Enum Values** | `auto`, `en`, `bg`, `ca`, `cs`, `da`, `nl`, `en-AU`, `en-GB`, `en-NZ`, `en-IN`, `en-US`, `et`, `fr`, `fi`, `nl-BE`, `de`, `de-CH`, `el`, `hi`, `hi-Latn`, `hu`, `id`, `it`, `ja`, `ko`, `lv`, `lt`, `ms`, `no`, `pl`, `pt`, `pt-BR`, `ro`, `ru`, `sk`, `es`, `es-419`, `sv`, `th`, `tr`, `uk`, `vi`, `zh`, `zh-TW` | ### model Specifies the transcription model to use when automatically generating subtitles from the audio. If a model is not specified, a default model will be used. Different models may offer varying levels of accuracy or support different languages. | | | |--------------|-------------| | **Type** | string | | **Required** | No | | **Default Value** | `"default"` | | **Enum Values** | `default`, `whisper` | ### settings Settings to customize the appearance of the subtitles, including style, font, colors, positioning, and other visual attributes. These settings allow you to tailor the subtitles to match your video's aesthetics and improve readability. Available settings depend on the chosen `style`. > **This is a closed list.** Unlike the `text` element — whose `settings` are free-form CSS properties — the subtitles `settings` object accepts *only* the properties documented below. Any other key is rejected before the render starts, with the error `Property 'X' is not allowed in movie/elements[0]/settings`. > > In particular, **there is no `color` or `font-color` property here.** The colours are set with: > > | You want | Correct key | > |---|---| > | Colour of the word being spoken | `word-color` | > | Colour of the rest of the line | `line-color` | > | Outline / border colour | `outline-color` | > | Shadow colour | `shadow-color` | > | Background box colour | `box-color` | > > To colour the whole caption uniformly, set `word-color` and `line-color` to the same value. > Note also that `font-size` here is an integer in pixels (e.g. `90`), not a CSS length such as `"7vw"`. | | | |--------------|-------------| | **Type** | object | | **Required** | No | | **Default Value** | `{}` | This object contains the following properties: * **all-caps**: (boolean, optional) - Makes the subtitles uppercase. - Default: `false` * **box-color**: (string, optional) - Color of the box behind the subtitles. Depending on the style, it can be the background color of the spoken word or the full line. - Default: `"#000000"` * **font-family**: (string, optional) - Font family of the subtitles. You can choose any Google Font name, one of the font families below or a custom font if font-url is provided (see below). - Default: `"Arial"` - Additional fonts: `Arial`, `Arial Bold`, `Oswald Bold`, `NotoSans Bold`, `Simplified Chinese`, `Traditional Chinese`, `Japanese`, `Korean`, `Korean Bold` * **font-size**: (integer, optional) - Font size of the subtitles. Usual sizes are between 90 and 150. Defaults to 5% of the movie width. * **font-url**: (string, optional) - URL to the font file to use for the subtitles. The font file must be in TTF format. The font-family property must match the font family name in the font file. * **font-weight**: (string, optional) - Font weight in the format of "100", "200", "300" to "900". The font weight is only available for Google Fonts, and must be a valid font family / weight pair. If the weight for the font family is not available, the "400" weight is used. * **keywords**: (array, optional) - Keywords provides additional vocabulary to the transcription process. Use it to improve the accuracy of the transcription of non-standard words or phrases. This options is only available for the `default` model. * **line-color**: (string, optional) - Color of the rest of words in the sentence. - Default: `"#FFFFFF"` * **max-words-per-line**: (integer, optional) - Maximum number of words per line. Setting this to `1` will show one word at a time. - Default: `4` * **outline-color**: (string, optional) - Outline color of the subtitles. - Default: `"#000000"` * **outline-width**: (integer, optional) - Width of the outline. - Default: `0` * **position**: (string, optional) - Position of the subtitles relative to the movie canvas. - Default: `"bottom-center"` - Allowed values: `top-left`, `top-center`, `top-right`, `center-left`, `center-center`, `center-right`, `bottom-left`, `bottom-center`, `bottom-right`, `mid-bottom-center`, `mid-top-center`, `custom` * **replace**: (object, optional) - Replaces words with the specified replacement. Useful to correct the transcription of non-standard words or phrases. The object is a key-value pair where the key is the word to replace and the value is the replacement. * **shadow-color**: (string, optional) - Shadow color of the subtitles. - Default: `"#000000"` * **shadow-offset**: (integer, optional) - Offset of the shadow. - Default: `0` * **style**: (string, optional) - Style of the subtitles. Classic styles show simple text overlays, while boxed styles show a box behind the subtitles. Check the examples for more details. - Default: `"classic"` - Allowed values: `classic`, `classic-progressive`, `classic-one-word`, `boxed-line`, `boxed-word` * **word-color**: (string, optional) - Color of word that is being spoken at the moment. - Default: `"#FFFF00"` * **x**: (integer, optional) - X coordinate of the subtitles relative to the movie canvas. This property is only used when the `position` property is set to `custom`. Plain pixels only — unlike other visual elements, this does not accept `"30%"` or named values such as `center`. - Default: `0` * **y**: (integer, optional) - Y coordinate of the subtitles relative to the movie canvas. This property is only used when the `position` property is set to `custom`. Plain pixels only — unlike other visual elements, this does not accept `"30%"` or named values such as `center`. - Default: `0` > **Subtitles are placed through `settings`, not through the element.** The > `position`, `x` and `y` above live inside `settings`. A subtitles element also > inherits top-level `position`/`x`/`y` from every visual element, but they are > accepted and then ignored — subtitles are burned in as a text tag, which is a > different mechanism from the overlay every other element goes through. This is > also why the named values described in > [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values) > are rejected here: there is no element box to centre against. ### type This field specifies the element's type and must be set to `subtitles` for subtitles elements. | | | |--------------|-------------| | **Type** | string | | **Required** | Yes | | **Enum Values** | `subtitles` | ## Examples ### Example 1: Adding subtitles This example demonstrates a movie with a text-to-speech voiceover with automatic subtitles using a Google Font (Roboto 900). ```json { "width": "1080", "height": "1920", "scenes": [ { "elements": [ { "voice": "en-US-AmandaMultilingualNeural", "extra-time": 2, "model": "azure", "text": "The five boxing wizards jump quickly across the hazy lawn.", "type": "voice" }, { "type": "subtitles", "language": "auto", "model": "default", "settings": { "max-words-per-line": 3, "font-size": "80", "all-caps": false, "outline-color": "#FFFFFF", "outline-width": 8, "word-color": "#D22A1F", "x": 540, "y": 1470, "font-family": "Roboto", "font-weight": "900", "style": "classic", "position": "custom", "line-color": "#D22A1F" } } ] } ] } ``` # Component library --- type: reference source: https://cdn.json2video.com/data/components/schemas/index.json last_reviewed: 2026-05-21 --- # Component library Components are pre-built animated HTML templates rendered by the JSON2Video engine on their own canvas. You reference a component by its ID (e.g. `basic/000`) inside a [component element](@/reference/json-syntax/element/component) and customise its appearance through the `settings` object. The catalog below is generated from the live CDN, so it always reflects the current production library. Each card links to a detail page with the full settings reference and a video preview. {{ components_index }} ## See also - [Component element reference](@/reference/json-syntax/element/component) — shared properties (`duration`, `start`, `x`, `y`…) - [Component CSS properties](@/reference/component-css-properties) — full list of CSS keys accepted inside `settings` - [Text styles](@/reference/text-styles) — text-only animations applied via `style` on a `text` element - [Components deep-dive](@/guides/advanced/components) — patterns for combining components with other elements # Text styles --- type: reference source: https://cdn.json2video.com/data/components/schemas/index.json last_reviewed: 2026-05-21 --- # Text styles Text styles control the entry animation of a [text element](@/reference/json-syntax/element/text). They all share the same CSS settings — `font-family`, `font-size`, `color`, `text-align`, `text-shadow`… — and only the animation differs. The keys are real CSS property names, so the text colour is `color` (there is no `font-color`). Pick a style by setting the `style` property on the text element: ```json { "type": "text", "style": "005", "text": "Hello", "settings": { "font-family": "Anton", "font-size": "8vw", "color": "#FFFFFF" } } ``` The catalog below is generated from the live CDN. Each card links to a detail page with the animation description, defaults, and a video preview. {{ text_styles_index }} ## See also - [Text element reference](@/reference/json-syntax/element/text) — shared text properties, available fonts, RTL support - [Component library](@/reference/components) — animated overlays beyond plain text # Component CSS properties --- type: reference source: https://cdn.json2video.com/data/components/config.json last_reviewed: 2026-05-21 --- # Component CSS properties Every group inside a component's `settings` object accepts CSS-like properties. They look almost identical to standard CSS — the example below uses six of them (`vertical-align`, `horizontal-align`, `background-color`, `padding`, `border-radius`, `font-family`, `font-size`, `font-weight`, `color`) on the `basic/000` "Simple card" component: ```json { "type": "component", "component": "basic/000", "settings": { "card": { "vertical-align": "center", "horizontal-align": "center", "background-color": "#000000", "padding": "40px", "border-radius": "12px" }, "headline": { "font-family": "Inter", "font-size": "5vw", "font-weight": "700", "color": "#ffffff" } } } ``` The accepted keys inside each settings group (`card`, `headline`, …) come from the table below. Available groups and which properties are meaningful for each are documented on every [component](@/reference/components) detail page. {{ component_css_properties }} ## See also - [Component library](@/reference/components) — browse the 22 building-block components - [Component element reference](@/reference/json-syntax/element/component) — top-level element properties # Webhooks --- section: webhooks source: api/endpoints/destinations/index.js last_reviewed: 2026-07-14 --- # Webhooks Webhooks deliver render completion events to a URL of your choice. They are the recommended alternative to polling [`GET /v2/movies`](@/reference/api-endpoints/movies-status). ## Configuration Webhooks are declared in the movie's `exports` array as a `destinations[].type = "webhook"` entry, or by referencing a Dashboard connection ID. ### Inline endpoint ```json { "resolution": "full-hd", "scenes": [], "exports": [ { "destinations": [ { "type": "webhook", "endpoint": "https://example.com/webhook", "content-type": "application/json" } ] } ] } ``` | Field | Required | Description | |-------|----------|-------------| | `type` | yes | `"webhook"`. | | `endpoint` | yes | Publicly reachable HTTPS URL. | | `content-type` | no | `application/json` (default) or `application/x-www-form-urlencoded`. The full MIME string is required — short values like `json` / `urlencoded` are not recognized and silently fall back to JSON. | ### Connection reference For security, store the endpoint as a Dashboard connection and reference it by ID. This keeps secrets out of the Movie JSON. ```json { "exports": [ { "destinations": [ { "id": "your-webhook-connection-id" } ] } ] } ``` ## Event types The webhook fires once per export pipeline, after rendering completes (or, when configured, after another destination has finished). There is one event today: | Event | Fired when | |-------|-----------| | `movie.completed` | The render finished — successfully or with an error. Inspect `success` and `status` in the payload to distinguish. | The event name is not currently transmitted in the payload; consumers should treat any inbound POST to their webhook URL as a `movie.completed` event. ## Payload The HTTP request is `POST` with the body shape below. Default `content-type` is `application/json`. When the destination declares `content-type: application/x-www-form-urlencoded` the same fields are sent form-encoded. ```json { "width": "1920", "height": "1080", "duration": "10.5", "size": "4567890", "url": "https://assets.json2video.com/clients/xxxxxxxx/renders/yourmovie.mp4", "thumbnail": "https://assets.json2video.com/clients/xxxxxxxx/renders/yourmovie.jpg", "project": "JkGxEoPRF9EgRb32", "id": "your-movie-id", "client-data": { "order_id": "ord_42" } } ``` | Field | Type | Description | |-------|------|-------------| | `width` | string | Output width in pixels. | | `height` | string | Output height in pixels. | | `duration` | string | Output duration in seconds. | | `size` | string | Output file size in bytes. | | `url` | string | Public URL of the rendered MP4. | | `thumbnail` | string | Public URL of the movie thumbnail. Omitted entirely when the render produced none, so check for its presence. See the movie [`thumbnail`](@/reference/json-syntax/movie) property to choose the frame. | | `project` | string | The 16-character project ID. | | `id` | string | The `id` from the submitted Movie JSON, if any. | | `client-data` | object | The `client-data` from the submitted Movie JSON, if any. | > Field types are reported as strings in the current payload format. Validate accordingly. ## Failure cases When the render fails, the webhook is still fired (assuming an export pipeline ran). The payload may have an empty or partial `url` field. Consumers should always cross-check the final status by calling [`GET /v2/movies?project={id}`](@/reference/api-endpoints/movies-status) when handling a webhook. ## Retries > The exact retry behaviour is subject to change. Confirm with support if your workflow depends on guaranteed delivery. The handler treats the destination as a fire-and-forget HTTP POST: it is sent once, with a short request timeout, and any non-2xx response is recorded but not retried automatically by the public API today. > > For workflows that require strong delivery guarantees, configure your endpoint behind a queue you control (e.g. Make.com, n8n, or your own job runner) and reconcile against `GET /v2/movies` on a schedule. ## Verification Webhooks are not currently signed by JSON2Video. To verify authenticity: 1. Configure a long, unguessable path in your endpoint URL (e.g. `https://your.app/webhooks/json2video/abc123…`). 2. Optionally include a secret token in the URL query string or as part of the path, and check it server-side. 3. Always cross-check the payload by calling `GET /v2/movies?project={id}` with your API key. ## Multiple destinations Destinations inside an `exports[].destinations` array run sequentially. A common pattern is to upload the video to an FTP server first, then notify your backend with a webhook: ```json { "exports": [ { "destinations": [ { "id": "your-ftp-connection-id" }, { "type": "webhook", "endpoint": "https://example.com/webhook" } ] } ] } ``` ## Building the receiver The endpoint must be publicly reachable over HTTPS with a valid certificate. Minimal receivers: ### PHP ```php { const { url, project } = req.body; // Your business logic here. res.status(200).send("ok"); }); app.listen(3000); ``` Always respond with a 2xx status code as soon as the payload is durably captured. Heavy work should happen out of band. # Errors --- section: errors source: api/endpoints/ last_reviewed: 2026-07-14 --- # Errors The API surfaces errors in two places: 1. **HTTP responses** — synchronous endpoint errors (validation, auth, quota). The response body is `{ "success": false, "message": "…" }` with an HTTP status code in the 4xx–5xx range. 2. **Render status** — asynchronous errors discovered while a render is being produced. The render completes with `movie.status = "error"` and `movie.message` describing what failed. The HTTP status of `GET /v2/movies` is still `200`. All error messages are short, descriptive strings. They are not stable identifiers — match on HTTP status and on the endpoint plus message family rather than on exact text. ## HTTP status code summary | Status | Meaning | |--------|---------| | `200` | Request accepted. For `GET /v2/movies`, also check `movie.status`. | | `400` | Validation error, invalid or missing API key, or insufficient credits. | | `401` | Quota / plan limit error. | | `403` | Authorisation error (role, blocked storage, invalid admin token). | | `404` | Resource (template, movie, file) not found. | | `405` | HTTP method not supported on this endpoint. | | `409` | Conflict (duplicate filename on upload, etc.). | | `413` | Payload too large (media upload > 500 MB). | | `500` | Internal error. | ## Authentication errors Returned when the request lacks a valid API key or is using one without sufficient permissions. Note that an invalid or missing API key returns HTTP `400`, not `401`. | Status | Message | Endpoint | |--------|---------|----------| | `400` | `Error: API Key not provided` | All endpoints | | `400` | `Error: Invalid API Key` | All endpoints | | `403` | `Insufficient permissions` | `/v2/templates`, `/v2/media`, `POST /v2/movies` (admin endpoints) | | `403` | `Invalid token` | Admin-only query parameters | To resolve: confirm the `x-api-key` header is present and that the key has the right role for the action — `Render` to render videos, `Editor` to create / edit templates, `Manager` to manage Connections. See [API keys → Permission roles](@/guides/dashboard/api-keys#permission-roles). ## Quota and plan errors Returned when the account has exhausted its plan allowance. | Status | Message | Endpoint | |--------|---------|----------| | `401` | `You exceeded the quota of movies in your plan. Please upgrade your plan to continue.` | `POST /v2/movies` | | `401` | `You exceeded the quota of drafts in your plan. Please upgrade your plan to continue.` | `POST /v2/movies` | | `401` | `Movie is larger ({w}x{h}) than your plan allowance ({w}x{h})` | `POST /v2/movies` | | `400` | `Insufficient credits` | Render submission paths | | `403` | `Storage is blocked. Add credits to continue uploading.` | `POST /v2/media/file` | To resolve: top up credits or upgrade the plan. See [Credits & limits](@/reference/credits/credit-consumption). ## Validation errors Returned when the request payload is missing required fields or has the wrong shape. | Status | Message | Endpoint | |--------|---------|----------| | `400` | `No movie JSON received` | `POST /v2/movies` | | `400` | `Error parsing movie JSON or the movie was empty` | `POST /v2/movies` | | `400` | `No valid movie JSON received` | `POST /v2/movies` | | `400` | `Project ID must be a 16-character string. Received ID: '…' (length: N)` | `GET /v2/movies` | | `400` | `Invalid start date` / `Invalid end date` | `GET /v2/movies` | | `400` | `Maximum date range is 3 months.` | `GET /v2/movies` | | `400` | `No payload provided` | `POST /v2/templates`, `POST /v2/media/file`, `PUT /v2/media/file`, `DELETE /v2/media/file` | | `400` | `Tags must be a string or an array` | `POST /v2/templates` | | `400` | `Payload movie must be a JSON string or JSON object` | `POST /v2/templates` | | `400` | `Template movie is not valid JSON or it's too large` | `POST /v2/templates` | | `400` | `name is required` | `/v2/media/file` | | `400` | `contentType is required` | `POST /v2/media/file` | | `400` | `size is required and must be a positive number` | `POST /v2/media/file` | | `400` | `path is required` / `Invalid path: no filename` | `GET /v2/media/file` | | `400` | `destination is required` | `PUT /v2/media/file` | | `400` | `folder is required` / `Invalid folder name` | `/v2/media/folder` | | `400` | `Cannot delete root folder` / `Cannot delete the temp folder` | `DELETE /v2/media/folder` | | `400` | `Folder is not empty. Delete all files first.` | `DELETE /v2/media/folder` | | `400` | `Invalid movie status` | Render submission | | `404` | `Template {id} not found` | `/v2/templates`, `POST /v2/movies` (template ref) | | `404` | `File not found` | `/v2/media/file` | | `404` | `Movie ID {id} not found` | Render submission | | `403` | `Template {id} is not owned by you` / `Movie ID {id} is not owned by you` | `POST /v2/templates`, render submission | | `405` | `Method not supported` | All endpoints | | `409` | `A file with this name already exists. Delete it first.` | `POST /v2/media/file` | | `409` | `A file with this name already exists in the destination folder` | `PUT /v2/media/file` | | `413` | `File exceeds maximum size of 500 MB` | `POST /v2/media/file` | ## Rendering errors Surfaced asynchronously via `GET /v2/movies` when `movie.status = "error"`. The `movie.message` field contains the underlying error. Examples seen in production: | Family | Example `message` | Trigger | |--------|-------------------|---------| | Element validation | `Scene #1 Element #2: The element type 'video' requires a 'src' property.` | A required field was missing or null. | | Positioning / sizing | `/Movie/scenes[0]/elements[1]: 'x' has an invalid value "auto". Use a number in pixels, a percentage of the canvas like "30%", one of: left, center, right` | `x`, `y`, `width` or `height` held a value that is neither a number nor one of the accepted strings. See [Positioning and sizing values](@/reference/json-syntax/element#positioning-and-sizing-values). | | Asset download | Asset fetch errors propagate from the downloader. | The element `src` URL is unreachable, requires authentication, or returns an unsupported media type. | | Webpage capture | Errors from the HTML / webpage renderer. | An `html` element pointed at a URL that timed out, returned non-2xx, or required interaction. | | Voice synthesis | Errors from the speech provider. | Invalid `voice` ID, unsupported language, empty `text`, rate-limit, or a transient 5xx. | | Subtitles | Speech-to-text transcription error. | Source audio was missing or unreadable. | For rendering errors, the `movie.success` field is `false` even though the HTTP status is `200`. ## Timeouts When a render is `running` for more than 15 minutes, `GET /v2/movies` returns `movie.status = "timeout"` with `message = "Movie took too long to render"`. The original render may still eventually complete; treat `timeout` the same as `error` for client code paths. ## Retry guidance | Class | Retry strategy | |-------|----------------| | `400` validation | Do not retry. Fix the payload. | | `401` quota | Do not retry until credits are topped up. | | `403` auth | Do not retry. Fix the key or role. | | `404` not found | Do not retry. Confirm the ID. | | `409` conflict | Do not retry blindly. Resolve the conflict (rename, delete the existing file). | | `500` internal | Retry with exponential backoff, up to 3–5 attempts. | | `movie.status = "error"` | Inspect the message. Re-submit a fixed payload if the cause was client-side. | | `movie.status = "timeout"` | Re-submit the same job; treat as a transient failure. | # File retention --- section: file-retention source: api/cronjobs/garbage-collector/index.mjs last_reviewed: 2026-07-27 --- # File retention JSON2Video is a video generation platform, not a storage or hosting service. Every file the platform produces has a retention window, after which it is permanently deleted. **Download or re-host anything you want to keep.** ## Retention at a glance | File | Where you see it | Retention | |------|------------------|-----------| | Rendered video | `url` in [`GET /v2/movies`](@/reference/api-endpoints/movies-status) | **7 days** after the render finishes | | Movie entry (status, timings, credits spent) | Render history / `GET /v2/movies` | Kept — not removed with the file | | Cached generated or downloaded asset | `url` in [`GET /v2/preloads`](@/guides/advanced/save-to-media) when `persistent: false` | **3 days** after generation | | Render working files | Internal, not exposed | 3 days | | Media library files (uploaded or `save-to-media`) | [Media panel](@/guides/dashboard/media) / `GET /v2/media` | Kept while your account has credits — see below | All periods are counted from the moment the file is created, not from the last time it was accessed. Downloading a file does not extend its life. ## Rendered videos — 7 days Every finished render is stored for 7 days and then deleted automatically. This applies to every plan, free and paid alike: there is no longer window to buy today. The dashboard's render logs show a countdown per render. Through the API, a video whose file has expired returns `url: null`. Two things to keep in mind: - The URL is meant for **you** to download the file. It is not a content delivery service — do not embed it in a public page or app that depends on it long-term, because it stops working after 7 days. - To free storage earlier, call [`DELETE /v2/movies`](@/reference/api-endpoints/movies-delete). It removes the file immediately and keeps the movie entry in your history. ## Cached assets — 3 days Assets produced by [`POST /v2/preloads`](@/guides/advanced/save-to-media), by `movie.preload[]`, or by generable elements inside a scene, go to a temporary cache. The response tells you exactly what you got: - `persistent: false` — temporary URL, with `expires_at` set to roughly 3 days after generation. - `persistent: true` — the asset was saved to your Media library and follows the Media rules below. While an asset is in the cache it is reused automatically by any later render that references the same prompt or source URL, so re-rendering within the window does not cost credits twice. Once it expires, the next render regenerates it and charges for it again. To keep a generated asset beyond the cache window, set `save-to-media: true`. See [Save generated assets to Media](@/guides/advanced/save-to-media). ## Media library files Files in your Media library — both the ones you upload and the ones persisted with `save-to-media` — have **no expiry date**. They are kept as long as your account holds credits, and their URLs are stable, so these are the files you can safely reference from scheduled renders and long-lived templates. They are deleted in three cases: 1. **You delete them**, from the Media panel or with [`DELETE /v2/media`](@/reference/api-endpoints/media-delete). Immediate and irreversible. 2. **Your account runs out of credits and stays at zero for 30 days.** When the balance reaches zero a grace period starts. If you top up or renew at any point during those 30 days, the clock is cleared and nothing is deleted. If it elapses, all your Media library files and your rendered videos are permanently removed. 3. **You close your account.** Export anything you want to keep first. The 30-day grace period tracks your credit balance, not your subscription: an account with credits left over from a previous cycle is not at risk, and neither is a prepaid account with a positive balance. ## Recommended practice - Treat every JSON2Video URL as a pickup point, not as storage. Download the render — or use an [export destination](@/reference/webhooks) to push it straight to your own bucket, FTP, or platform — as soon as it is ready. - Use webhooks instead of polling, so you fetch the file as soon as it exists rather than days later. - Put the assets you reuse across renders (logos, intros, background music) in the Media library, not in the temporary cache. - If your account is going to sit idle without credits, export your Media library before the 30-day window elapses. ## See also - [Delete movie (DELETE)](@/reference/api-endpoints/movies-delete) - [Media panel](@/guides/dashboard/media) - [Save generated assets to Media](@/guides/advanced/save-to-media) - [Terms of use](https://json2video.com/legal/terms-of-use.html) and [SLA](https://json2video.com/legal/sla.html) # Content ownership & usage rights --- section: content-ownership source: website/legal/terms-of-use.html last_reviewed: 2026-07-28 --- # Content ownership & usage rights Who owns the videos you render, what you can do with them, and what you are responsible for. This page summarises the position in plain language. The binding text is the [Developer API Terms of Use](https://json2video.com/legal/terms-of-use.html); where the two differ, the Terms control. ## The short answer | Question | Answer | |----------|--------| | Who owns the video I render? | **You do**, on every plan. | | Can I use it commercially? | **On a paid plan, yes**, without restriction. **On the free plan, no** — see [The free plan is different](#the-free-plan-is-different). | | Will there be a watermark? | **Possibly on the free plan** — we may apply one at any time. Paid plans always render clean. | | Do I need to credit JSON2Video? | **No.** There is no attribution requirement on any plan. | | Does JSON2Video claim any rights over my videos? | **No** ownership. Only the limited licence needed to run the render and deliver the file to you. | | Does JSON2Video reuse my content? | **No.** Not for marketing, not to train models, not for anything else — unless you explicitly agree. | | Is my ownership unconditional? | **No.** You only ever get out what you had the right to put in — see [The one real limit](#the-one-real-limit). | ## What you provide stays yours The assets you send us — images, video clips, audio, fonts, text, templates, and anything you upload to your [Media library](@/guides/dashboard/media) — remain entirely yours. Uploading a file to JSON2Video transfers no ownership. To operate the service we need a narrow, practical licence over those files: permission to store, copy, transcode, cache, and transmit them, strictly to produce the render you asked for and deliver it back to you. That licence exists for no other purpose. It ends when the files do, on the schedule in [File retention](@/reference/file-retention). ## What you generate is yours As between you and JSON2Video, every rendered video belongs to you, on every plan. On a paid plan you may publish it, sell it, edit it, license it onward, put it behind a paywall, or use it in paid advertising, with no watermark and no attribution. (The free plan grants ownership too, but restricts what you may do with the video — see [the next section](#the-free-plan-is-different).) Concretely, JSON2Video will not: - claim ownership or co-ownership of your renders; - use your videos, uploads, or templates in its own marketing, demos, or public galleries; - use your content to develop, train, or improve machine-learning models; - share your content with anyone else, except the providers strictly needed to produce the render (see [below](#third-party-providers-follow-their-own-terms)) or where the law compels us. If we ever want to feature something you made, we will ask you first. ## The free plan is different The free plan exists so you can evaluate and learn the platform. Videos rendered on it come with two conditions that paid plans do not have: - **They may carry a JSON2Video watermark.** Whether one is applied is decided automatically from your plan at render time — there is no JSON property that controls it, and the deprecated `draft` flag does nothing. We may start or stop applying it at any time. Where one is present you may not remove, crop, obscure, or otherwise defeat it. - **They are for non-commercial use only.** Personal projects, learning, and internal evaluation are fine. Selling the video, using it in advertising or promotion, publishing it on a monetised channel, or delivering it to a client is not. This applies whether or not a watermark is present. These conditions travel with the video. Upgrading later does not retroactively clear a video you already rendered on the free plan — but **re-rendering the same movie on a paid plan gives you a clean, unrestricted version**. That is usually a few credits and the simplest fix. Every paid plan removes both conditions. See [Plans](@/reference/credits/plans). ## The one real limit **Your rights in the output can never exceed your rights in the input.** Rendering does not launder licences. If a scene contains a stock clip, a licensed font, a music track, a brand logo, or a person's face or voice, the terms attached to that material still apply to the finished video, exactly as they did before. This is the practical checklist: - **Stock media** — check whether your licence covers the use you have in mind. Many "free" libraries permit personal but not commercial use, or require attribution. - **Fonts** — a font served over a URL still needs a licence that allows embedding and redistribution. - **Music** — a track cleared for one YouTube channel is usually not cleared for a client's advertising campaign. - **People** — using someone's name, face, or voice needs their consent. This is not optional, and it is one of the categories the Terms prohibit outright. - **Brands** — trademarks and product imagery you do not own need permission. You are solely responsible for holding these rights. Section 3(f) of the Terms puts it directly: you must have "all rights, licenses, consents, releases, and lawful basis required for such content and its intended use." ## Third-party providers follow their own terms Some elements are produced by an external provider rather than by JSON2Video — most visibly the text-to-speech voices behind the [`voice`](@/reference/json-syntax/element/voice) element, whether billed through your JSON2Video credits or through your own [Connection](@/guides/dashboard/connections). For those components, the provider's licence terms travel with the output. A synthetic voice, for example, may carry restrictions on the kind of content it may narrate. If you brought your own provider account through a Connection, the agreement you signed with that provider governs. See [Third-party integrations](@/guides/third-party). JSON2Video does not add restrictions of its own on top — but it cannot remove the provider's either. ## What you may not create Ownership does not mean anything goes. Section 3(f) of the Terms lists content that may not be created through the platform under any circumstances. The categories that account for nearly every real case: - synthetic impersonation of a real person — deepfakes, face swaps, lip-sync manipulation; - any use of a real person's name, likeness, or voice without their verifiable consent; - sexual or suggestive content, and absolutely anything sexualising minors; - deceptive political content, disinformation, and fabricated statements attributed to real people or organisations; - impersonation of a brand, fraud, scams, phishing, and fake reviews or testimonials; - hate speech, harassment, incitement to violence, and content promoting self-harm; - anything infringing someone else's copyright, trademark, or other rights; - anything unlawful in a jurisdiction where it is made, distributed, or watched. [Section 3(f)](https://json2video.com/legal/terms-of-use.html) is the authoritative and longer list. We may refuse, remove, or take down content and suspend accounts over it, without prior notice. ## Where your videos live Ownership and hosting are separate questions. Owning a render does not turn our storage into your CDN: - A render URL is a **pickup point**, valid for 7 days. Download the file, or push it straight to your own storage with an [export destination](@/reference/webhooks). Render URLs must not be embedded in or linked from public sites (SLA §4.2). - [Media library](@/guides/dashboard/media) URLs are the exception: they are stable and meant to be referenced from your own renders and properties. Full detail in [File retention](@/reference/file-retention). ## The legal documents - [Developer API Terms of Use](https://json2video.com/legal/terms-of-use.html) — the binding agreement. Ownership is §4; prohibited content is §3(f); file storage is §3(g). - [Service Level Agreement](https://json2video.com/legal/sla.html) — availability, support response times, and the hosting restriction. - [Privacy Policy](https://json2video.com/legal/privacy-policy.html) — how we handle personal data. Questions the pages above do not answer: [contact us](https://json2video.com/contact-us/). ## See also - [File retention](@/reference/file-retention) - [Rate limits & quotas](@/reference/credits/limits) - [Third-party integrations](@/guides/third-party) # Credits & limits --- section: credits source: documentation-site/content/pricing/ last_reviewed: 2026-05-22 --- # Credits & limits JSON2Video bills usage in **credits**. Every render and every generated asset consumes credits. The pages below break down how: - [Credit consumption](@/reference/credits/credit-consumption) — how credits are deducted for rendering and asset generation. - [Plans](@/reference/credits/plans) — free, subscription, and prepaid plans, with their per-render limits. - [Rate limits & quotas](@/reference/credits/limits) — how many requests you may send, and what happens when a quota runs out. - [FAQ](@/reference/credits/faq) — common billing questions. # Credit consumption --- section: credits page: credit-consumption source: api/endpoints/render-movie/index.mjs last_reviewed: 2026-07-14 --- # Credit consumption Credits are the unit of usage on JSON2Video. New accounts receive 600 free credits. ## What credits are consumed for 1. Rendering videos. 2. Text-to-speech voiceovers. ## Rendering | Output resolution | Credits per second of output | |-------------------|------------------------------| | Any resolution (SD up to 4K) | 1 | The rendering cost depends only on the output duration — the resolution does not change the per-second rate. Failed renders are not billed. ## Voice assets At the moment, the text-to-speech voiceovers are provided included in all plans. When the `voice` element specifies a `connection` ID, the upstream provider (Microsoft, ElevenLabs, …) bills the customer directly. | Asset type | Model | Provider | Credits / unit | |------------|-------|----------|----------------| | Voice | `azure` | Microsoft | 0 per minute | | Voice | `elevenlabs-flash-v2-5` | ElevenLabs | 0 per minute | | Voice | `elevenlabs-v3` | ElevenLabs | 0 per minute | | Subtitles | `default` / `whisper` | JSON2Video | 0 per minute | Cached assets are not re-billed. If the same input has been generated before (and the element's `cache` flag is `true`, which is the default), the cached version is reused. ## Order of deduction Credits are consumed in this order: 1. Subscription-plan credits, earliest-expiring first. 2. Prepaid (non-expiring) credits. When neither has remaining credits, further renders fail with `Insufficient credits`. See [Errors](@/reference/errors). ## Worked examples ### Simple Full HD video, no voiceover `1920×1080` × 10 seconds, no generated assets. **Cost:** 10 credits. ### Vertical video with a managed voiceover `1080×1920` × 30 seconds with a 20-second ElevenLabs voiceover using JSON2Video's managed key. **Cost:** 30 (rendering) + 0 (ElevenLabs voice, currently included) = **30 credits**. Voice models are included at 0 credits today; this may change as new models ship. ### Same video, customer-supplied keys `1080×1920` × 30 seconds, voiceover routed through the customer's own ElevenLabs account via a Dashboard connection. **Cost:** 30 credits (rendering only). The voice charge is billed directly by ElevenLabs. # Plans --- section: credits page: plans source: documentation-site/content/pricing/plans.md last_reviewed: 2026-05-12 --- # Plans JSON2Video offers a free plan, monthly subscription plans, and one-time prepaid plans. The single source of truth for current pricing is the [JSON2Video pricing page](https://json2video.com/pricing) — the tables below summarise the structure. ## Free plan - 600 non-renewable credits on signup. - Renders may include a JSON2Video watermark. - For personal, educational, and evaluation use — **not for commercial use**. See [Content ownership & usage rights](@/reference/content-ownership). - Maximum output resolution: 1080p. - Maximum output duration: 60 seconds. - All features are accessible. ## Subscription plans Monthly subscriptions renew a credit pool at the start of each billing cycle. | Plan | Monthly credits | Max output duration | Notes | |------|-----------------|---------------------|-------| | Professional | 12,000 | 10 minutes | Up to 200 minutes of Full HD video per month. | | Startup | 30,000 | 30 minutes | Up to 500 minutes of Full HD video per month. | | Enterprise | 78,000 | 30 minutes | Up to 1,300 minutes of Full HD video per month. | All paid plans render without a watermark, allow unrestricted commercial use, and unlock priority support. ## Prepaid plans Prepaid credits do not expire. They top up the credit balance on top of any active subscription. | Plan | Credits | Max output duration | |------|---------|---------------------| | 7,200 credits | 7,200 | 30 minutes | | 15,600 credits | 15,600 | 30 minutes | ## Combining plans Subscription and prepaid credits can coexist on the same account. The deduction order is documented in [Credit consumption](@/reference/credits/credit-consumption). For the most up-to-date prices, plan tiers, and add-ons, see the [pricing page](https://json2video.com/pricing). # Rate limits & quotas --- section: credits page: limits source: api/endpoints/render-movie/index.mjs last_reviewed: 2026-07-28 --- # Rate limits & quotas The usage limits on a JSON2Video account cap **how much video you produce**, not how fast you ask for it. Usage is metered in credits plus a few per-plan caps; there is no separate throughput or rate tier to buy. ## What is metered | Limit | What it caps | Set by | |-------|--------------|--------| | **Credits** | Total seconds of video you can produce | Your balance | | **Maximum render length** | Duration of any single movie | Your plan | | **Movies / drafts** *(legacy plans)* | Number of renders per period | Your plan | **Credits** are the main one: 1 credit per second of output, at any resolution from SD to 4K. New accounts get 600 free. Failed renders are not billed. See [Credit consumption](@/reference/credits/credit-consumption). **Maximum render length** caps a single movie — a plan with plenty of credits can still refuse a very long render. The per-plan figures are on the [Plans](@/reference/credits/plans) page. **Movies and drafts** counters apply only to older count-metered plans. Current plans meter time instead. `GET /v2/movies` returns whichever apply to you in `remaining_quota`, so you never have to guess: ```json "remaining_quota": { "time": 1807 } ``` ## When a quota runs out Requests are rejected immediately rather than slowed down: | Response | Message | Meaning | |----------|---------|---------| | `400` | `Insufficient credits` | Balance is empty. Top up to continue. | | `401` | `You exceeded the quota of movies in your plan. Please upgrade your plan to continue.` | A plan count limit, not a credit limit. | | `403` | `account_suspended` | The account has been sitting without credits. Adding credits lifts it automatically. | None of these is worth retrying — a retry fails the same way. Treat them as terminal and alert instead, as described in [Error handling](@/guides/production/error-handling). Check `remaining_quota` before submitting a large batch. ## Request rate For normal use, the API's request rate is not a constraint you need to design around: a render is a single `POST /v2/movies`, and the traffic after it is status checking. Almost every account that worries about request volume is really worrying about polling. Two habits keep you comfortably clear: - **Don't poll faster than every 5 seconds** per render. Renders take considerably longer than that, so faster polling returns the same answer repeatedly and buys nothing. - **Better still, don't poll.** Register a [webhook](@/reference/webhooks) and let us call you the moment a render finishes. One request per render instead of dozens. When you submit a batch, spread the calls out rather than firing them all at once — it costs you nothing in wall-clock time, because the renders queue and run regardless. ## High-volume and burst use Renders run in parallel, and your throughput is governed by your credits and plan caps rather than by a queue you can see. That works well up to substantial volumes without any special arrangement. If you are preparing something unusual — a launch, a bulk migration, a campaign that will produce far more video in a day than you normally do in a month — **[tell us in advance](https://json2video.com/contact-us/)**. We will confirm the capacity is there for you and, if your use case needs guarantees in writing, discuss what that looks like. The conversation is free and considerably cheaper than discovering a ceiling mid-campaign. ## See also - [Credit consumption](@/reference/credits/credit-consumption) - [Plans](@/reference/credits/plans) - [Credits FAQ](@/reference/credits/faq) - [Error handling](@/guides/production/error-handling) - [Retries & idempotency](@/guides/production/retries-idempotency) - [Webhooks](@/reference/webhooks) # FAQ --- section: credits page: faq source: documentation-site/content/pricing/faq.md last_reviewed: 2026-08-07 --- # Credits FAQ ## General ### How do I get credits? Two options: - A subscription plan that renews a credit pool monthly. - A one-time prepaid plan that tops up a non-expiring credit balance. See [Plans](@/reference/credits/plans) and the [pricing page](https://json2video.com/pricing). ### How do I remove the watermark? The watermark is applied only on the free plan. Any paid plan removes it. ### Does the free plan watermark every render? Yes. The watermark exists to prevent abuse from accounts created solely to consume free credits. ### How many videos can I create? It depends on the duration of each video, its resolution, and whether generated assets are involved. See [Credit consumption](@/reference/credits/credit-consumption). ### What happens when credits run out? Renders and asset generation fail with HTTP `400` and message `Insufficient credits` (or `401 You exceeded the quota …` on `POST /v2/movies`). Top up to continue. ### Where do I see my balance? In the [Credits section of the dashboard](https://json2video.com/dashboard/credits). ### Do prepaid credits expire? No. ### Can I have both a subscription and prepaid credits? Yes. Subscription credits are deducted first, prepaid credits next. ### Can I have more than one subscription? Yes. Each subscription renews independently. ## Credit consumption ### How many credits does a 30-second 1080p video cost? 30 credits (1 per second), excluding generated assets. ### How many credits does a 30-second 4K video cost? 120 credits — 4K renders cost 4 credits per second. ### How are credits split between a subscription and prepaid balance? Subscription credits are used first, in order of earliest expiry. Prepaid credits are used afterwards. ## Billing ### Can I cancel at any time? Yes. Cancellations take effect at the end of the current billing period. Cancel from the dashboard: **Credits → Subscriptions → ⋯ → Change → Cancel subscription**. ### Are refunds available? No. ### Can I upgrade or downgrade? Yes, at any time, from the same Subscriptions panel in the dashboard. ### Are taxes included in the listed prices? No. Taxes are added at checkout depending on jurisdiction. ### How do I get an invoice with my tax information? Every past payment is listed in the dashboard under **Credits → Billing history**, each row with a link to the invoice. The same link is in the receipt email sent after the purchase. The invoice is issued by the payment provider, so tax details — company name, billing address, VAT/tax ID — are added on their side, from the billing link in **Credits → Subscriptions**: Paddle opens its hosted billing page, PayPro emails a one-time login link (valid about 15 minutes) to its portal. Once the details are saved, download the corrected invoice. ### I paid but my credits haven't appeared in my account. What happened? In every case reported so far, the cause has been the same: **the email used at checkout did not match the email used to log into JSON2Video**. When that happens, Paddle or PayPro creates a new JSON2Video account associated with the checkout email and the credits are delivered there. The original account does not receive the credits because, from the system's point of view, the payment belongs to a different customer. To resolve it: 1. Find the receipt email sent by Paddle or PayPro after the purchase. The address that received it is the account that now holds the credits. 2. To move the credits back to your original account, email **support@json2video.com** from the original account and include the order number from the receipt. Credits are not transferred automatically — account ownership has to be verified manually. ### What is Paddle? What is PayPro? JSON2Video uses Paddle and PayPro as merchants of record. They handle invoicing, tax compliance, and payment processing on behalf of JSON2Video. Charges appear on bank statements as `Paddle.net* Json2video` or `PayPro Global`. New subscriptions are handled by PayPro; legacy subscriptions remain on Paddle until renewal. # SDKs --- section: sdks source: documentation-site/content/sdks.md last_reviewed: 2026-05-12 --- # SDKs Official client libraries wrap the JSON2Video REST API so a host application can render movies with one function call. - [PHP SDK](@/reference/sdks/php) - [Node.js SDK](@/reference/sdks/nodejs) Both SDKs target the public endpoints documented in [API endpoints](@/reference/api-endpoints) — `/movies`, `/templates`, `/media`. Any feature available in the REST API is available in the SDKs; the SDKs are thin wrappers that add type safety and convenience helpers (polling, error handling, request signing) on top. Other languages are not officially supported. The REST API can be consumed from any HTTP client; the [.md per URL](@/help-for-ai-agents/overview) feature and the OpenAPI spec — available as [`openapi.yaml`](@/openapi.yaml) or [`openapi.json`](@/openapi.json) — make it straightforward to generate clients automatically. # PHP --- section: sdks sdk: php source: https://github.com/JSON2Video/json2video-php-sdk last_reviewed: 2026-05-12 --- # PHP SDK Official PHP SDK for the JSON2Video REST API. - Repository: [github.com/JSON2Video/json2video-php-sdk](https://github.com/JSON2Video/json2video-php-sdk) - Composer package: `json2video/json2video-php-sdk` ## Install ```bash composer require json2video/json2video-php-sdk ``` ## Minimal example ```php setAPIKey(getenv('J2V_API_KEY')); $movie->setResolution('full-hd'); $scene = $movie->addScene(); $scene->addElement([ 'type' => 'text', 'text' => 'Hello, JSON2Video', 'duration' => 5, 'style' => '001', ]); $result = $movie->render(); // Submits POST /v2/movies. $status = $movie->getStatus(true); // Polls GET /v2/movies until done. echo $status['movie']['url']; ``` ## Configuration | Method | Purpose | |--------|---------| | `setAPIKey($key)` | Sets the `x-api-key` header. | | `setResolution($preset)` | Convenience wrapper for `movie.resolution`. | | `setQuality($q)` | Convenience wrapper for `movie.quality`. | | `addScene()` | Appends a scene; returns a `Scene` builder. | | `addElement($element)` | Adds a top-level (movie-wide) element. | | `render()` | Submits the movie and returns the API response. | | `getStatus($wait = false)` | Calls `GET /v2/movies`. With `true`, polls until completion. | The SDK transparently maps method calls to the JSON shape documented in [Movie JSON](@/reference/json-syntax/movie). ## Error handling API failures throw exceptions with the HTTP status and message body. See [Errors](@/reference/errors) for the full list. # NodeJS --- section: sdks sdk: nodejs source: https://github.com/JSON2Video/json2video-nodejs-sdk last_reviewed: 2026-05-12 --- # Node.js SDK Official Node.js SDK for the JSON2Video REST API. - Repository: [github.com/JSON2Video/json2video-nodejs-sdk](https://github.com/JSON2Video/json2video-nodejs-sdk) - npm package: `@json2video/json2video-sdk` ## Install ```bash npm install @json2video/json2video-sdk ``` ## Minimal example ```javascript const { Movie } = require('@json2video/json2video-sdk'); const movie = new Movie(); movie.setAPIKey(process.env.J2V_API_KEY); movie.setResolution('full-hd'); const scene = movie.addScene(); scene.addElement({ type: 'text', text: 'Hello, JSON2Video', duration: 5, style: '001', }); const result = await movie.render(); // POST /v2/movies. const status = await movie.waitToFinish(); // Polls GET /v2/movies. console.log(status.movie.url); ``` ## Configuration | Method | Purpose | |--------|---------| | `setAPIKey(key)` | Sets the `x-api-key` header. | | `setResolution(preset)` | Convenience wrapper for `movie.resolution`. | | `setQuality(q)` | Convenience wrapper for `movie.quality`. | | `addScene()` | Appends a scene; returns a `Scene` builder. | | `addElement(obj)` | Adds a top-level (movie-wide) element. | | `render()` | Submits the movie and returns the API response. | | `waitToFinish()` | Polls `GET /v2/movies` until the status is terminal. | The SDK builds the JSON payload documented in [Movie JSON](@/reference/json-syntax/movie). Anything that can be expressed in the REST payload can be expressed via `addElement()` and friends — pass through any property by name. ## Error handling API failures reject with `Error` objects carrying the HTTP status and message. See [Errors](@/reference/errors) for the full list. ## TypeScript The SDK ships type definitions. Import types alongside the runtime symbols: ```typescript import { Movie, MovieResponse } from '@json2video/json2video-sdk'; ``` # Changelog --- section: changelog source: documentation-site/ last_reviewed: 2026-05-12 --- # Changelog This changelog tracks **API behaviour changes**. Documentation-only updates are recorded in the project's git history but not here. Entries are reverse chronological. Each entry is dated and describes any change to request shape, response shape, status codes, or default behaviour. ## 2026-05-12 - Documentation overhaul. No API behaviour change. - Deprecated: the `template` element type inside an element list. The behaviour still works at runtime, but it will be removed in a future release. Replace `template` elements with either a [`component`](@/reference/json-syntax/element/component) (for reusable visual blocks) or a movie-level [`template` reference](@/reference/json-syntax/movie#template) loaded from a saved template. # Help for coding agents # Help for coding agents This section is for two audiences: - **LLMs and crawlers** that scrape this documentation to answer user questions about JSON2Video. - **Developers building with coding agents** (Claude Code, Cursor, Windsurf, custom Anthropic / OpenAI integrations) who want to plug JSON2Video into their tooling. It groups together everything you need to feed a model the right context and let it produce correct JSON, call the API and ship working videos with minimal hand-holding. ## What you'll find here - **[Overview](@/help-for-ai-agents/overview)** — machine-readable resources: `llms.txt`, `llms-full.txt`, the `.md` per URL feature, OpenAPI spec and JSON Schema downloads. - **[MCP server](@/help-for-ai-agents/mcp-server)** — set up the `@json2video/cli` MCP server in Claude Code, Cursor and Windsurf so your agent can render videos, check status and validate JSON without leaving the editor. - **[Rules for your agent](@/help-for-ai-agents/rules-for-your-agent)** — copy-paste blocks you can drop into `CLAUDE.md`, `AGENTS.md` or `.cursorrules` to give your agent the right defaults and avoid the most common JSON mistakes. # Overview # Overview This page lists every machine-readable resource we publish so that coding agents, LLM crawlers and automated tooling can consume the JSON2Video documentation and API surface without scraping HTML. ## LLM-ready content We publish two plain-text bundles designed to be passed straight into an LLM context window. - **[`https://json2video.com/llms.txt`](https://json2video.com/llms.txt)** — a short index following the [llms.txt](https://llmstxt.org/) convention. Lives at the website root (not under `/docs/v2/`) because crawlers expect it there. Lists the main documentation entry points with one-line descriptions. - **[`https://json2video.com/llms-full.txt`](https://json2video.com/llms-full.txt)** — the entire documentation site concatenated into a single markdown file, in navigation order, with the canonical URL preserved as a comment before each page. Generated by [`scripts/build-llms-full.sh`](https://github.com/JSON2Video/documentation-site/blob/main/scripts/build-llms-full.sh) in this repo and copied to the website root on each release. ### `.md` per URL Every documentation page is also available as raw markdown by appending `.md` to its URL. No special headers, no auth. This makes it trivial to fetch a single page directly from an agent without parsing HTML. Examples: - `https://json2video.com/docs/v2/getting-started/quickstart.md` returns the raw quickstart markdown. - `https://json2video.com/docs/v2/reference/json-syntax/movie.md` returns the raw Movie object reference. The response is `Content-Type: text/markdown; charset=utf-8`. Missing pages return `404 Not Found` with `Content-Type: text/plain`. ## Schema downloads Static schemas you can point your validator at, or feed to an agent for structured output. - **OpenAPI spec** — [`/docs/v2/openapi.yaml`](@/openapi.yaml) or [`/docs/v2/openapi.json`](@/openapi.json) — the v2 REST API description (endpoints, request/response shapes, auth). Covers `/movies`, `/templates` and `/media`. - **JSON Schema for Movie** — [`/docs/v2/movie-schema.json`](@/movie-schema.json) — the schema for the Movie object passed to `POST /v2/movies`. Use it for client-side validation or to constrain model output (e.g. with structured outputs). ## See also - **[MCP server](@/help-for-ai-agents/mcp-server)** — wire the `@json2video/cli` MCP into Claude Code, Cursor or Windsurf to give your agent direct access to render, status and validation tools. - **[Rules for your agent](@/help-for-ai-agents/rules-for-your-agent)** — copy-paste rule blocks for `CLAUDE.md`, `AGENTS.md` and `.cursorrules`. # MCP server # MCP server [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) is an open standard for giving AI assistants secure, structured access to local tools and data sources. The [`@json2video/cli`](https://www.npmjs.com/package/@json2video/cli) package ships with a built-in MCP server: once configured in your editor or agent, the model can render videos, check the status of a job, validate a movie JSON, browse templates and upload media without you having to write any glue code. ## Install ```bash npm install -g @json2video/cli ``` You can also skip the global install and let the editor invoke it via `npx -y @json2video/cli mcp` — that pattern is used in every config snippet below. You will need a JSON2Video API key. Get one from the [dashboard](https://json2video.com/dashboard) and either store it with `json2video auth --api-key YOUR_API_KEY` or pass it through the MCP config via the `JSON2VIDEO_API_KEY` environment variable (recommended for editor configs). ## Configure your editor ### Claude Code (`.mcp.json`) Add the following to your project's `.mcp.json` (or the global one at `~/.claude/.mcp.json`): ```json { "mcpServers": { "json2video": { "command": "npx", "args": ["-y", "@json2video/cli", "mcp"], "env": { "JSON2VIDEO_API_KEY": "YOUR_API_KEY" } } } } ``` Restart Claude Code, then run `/mcp` to confirm the `json2video` server is listed as connected. ### Cursor Open Cursor settings → **MCP** → **Add new MCP server**, or edit `~/.cursor/mcp.json` directly: ```json { "mcpServers": { "json2video": { "command": "npx", "args": ["-y", "@json2video/cli", "mcp"], "env": { "JSON2VIDEO_API_KEY": "YOUR_API_KEY" } } } } ``` After saving, Cursor will pick up the new server automatically. You can check the connection in the MCP panel. ### Windsurf Open Windsurf settings → **Cascade** → **MCP Servers** → **Edit raw config**, or edit `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "json2video": { "command": "npx", "args": ["-y", "@json2video/cli", "mcp"], "env": { "JSON2VIDEO_API_KEY": "YOUR_API_KEY" } } } } ``` Reload the MCP servers from the Cascade panel. ## Tools the MCP exposes These are the tools your agent will see once the server is connected (source of truth: [`cli/src/mcp/tools.js`](https://github.com/JSON2Video/cli/blob/main/src/mcp/tools.js) — see `cli/README.md` for the authoritative list). | Tool | What it does | |---|---| | `render_video` | Submit a movie JSON to `POST /v2/movies` and return the project ID. | | `check_status` | Poll `GET /v2/movies?project=…` and return the current status (`queued`, `running`, `done`, `error`). | | `validate_movie` | Validate a movie JSON locally against the schema before submitting. | | `list_templates` | List the templates available in the current account. | | `list_featured_templates` | List templates from the public library, optionally filtered by tag. | | `get_template` | Fetch a template's variables (as JSON Schema) and optionally its full movie source. | | `get_example` | Return a named example movie JSON the agent can use as a starting point. | | `get_help` | Pull a documentation snippet from the docs so the agent can self-serve without leaving the editor. | | `get_account` | Return the current account info (plan, credits remaining, etc.). | | `media_list` | List files in a folder of your account's media library. | | `media_get` | Download a single file from media. | | `media_upload` | Upload a local file (or URL) to media. | | `media_delete` | Delete a file from media. | The MCP server uses stdio transport. You can also run it manually for debugging with `json2video mcp` — it will wait for an MCP client to connect on stdin/stdout. ## Troubleshooting **Authentication errors** — the most common failure mode is a missing or wrong API key. Check that: - `JSON2VIDEO_API_KEY` is set in the `env` block of your MCP config, **or** - `json2video auth --api-key YOUR_API_KEY` has been run on the machine where the MCP server starts (key stored at `~/.config/json2video/config.json`). A 401 / 403 from any tool almost always means the key is missing, expired, or restricted to a different environment. **Network errors** — `@json2video/cli` talks to `https://api.json2video.com`. If you're behind a corporate firewall or a proxy: - Allow outbound HTTPS to `api.json2video.com` and `cdn.json2video.com`. - Export `HTTPS_PROXY` (and `HTTP_PROXY` if you proxy plain HTTP) in the environment where the MCP server starts. **`npx -y @json2video/cli` keeps re-downloading** — the `-y` flag accepts the install prompt every time but it's still cached after the first run. If your editor reports a slow startup, install globally with `npm install -g @json2video/cli` and change the `command` in your config to `json2video` and the `args` to `["mcp"]`. **Common errors** - `Project not found` — the project ID is wrong or belongs to another account / API key. - `Invalid movie` from `validate_movie` — the JSON does not match the schema. The error message includes the offending path; fix it and re-validate. See the [Rules for your agent](@/help-for-ai-agents/rules-for-your-agent) page for the conventions that prevent most validation failures. - `Insufficient credits` / `You exceeded the quota …` — the account is out of credits or has hit a plan limit. Do not retry; top up or upgrade. See [Rate limits & quotas](@/reference/credits/limits). If something else is going wrong, run `json2video mcp` directly from a terminal — the same process the editor spawns. Errors and stack traces print to stderr, which is otherwise swallowed by the MCP client. # Rules for your agent --- source: api/endpoints/00_common/json2video-api.json last_reviewed: 2026-07-14 --- # Rules for your agent Drop the following into your project's `CLAUDE.md`, `AGENTS.md`, or `.cursorrules` to give your coding agent the right context when working with JSON2Video. ## Block 1 — General rules ````markdown # JSON2Video — general rules You are working with the JSON2Video API (https://json2video.com/docs/v2/). Follow these rules whenever you generate, edit or submit a movie JSON, or call the API. ## Authentication - Every API request to api.json2video.com MUST include the `x-api-key` header. - The user's key lives in an environment variable. Never inline the key in code, JSON, or example URLs. ## Rendering model - The API renders scenes in PARALLEL. Split work into scenes whenever possible — it is faster and cheaper than putting everything in one scene. - A Movie is the top-level object. It contains a `scenes` array. Each Scene contains an `elements` array. - `scenes` is REQUIRED on the Movie root. It can be an empty array but the key must be present. ## Elements - Every element MUST have an explicit `type` field. - Valid element types: `image`, `video`, `text`, `audio`, `voice`, `audiogram`, `subtitles`, `component`, `html`. - Do NOT use `template` as an element type — it is deprecated and removed. - Never nest one element inside another. All elements are siblings inside `scene.elements`. ## Variables - Variables use the `{{name}}` syntax (double curly braces). - Variable names must contain only letters, numbers and underscores. No spaces, no dashes, no dots. - Pass values via the `variables` object on the Movie root or on a template request. ## Voices and premium models - For premium voice models (ElevenLabs, Azure neural voices, etc.) ALWAYS use a connection ID configured in the dashboard. - Never paste a raw third-party API key into the movie JSON. ## Caching - The `cache` field defaults to `true` at every level. Leave it alone unless you have a reason. - Only set `cache: false` when you actually need to regenerate (e.g. the asset behind a URL changed but the URL did not). ## Polling - After `POST /v2/movies` returns a `project` ID, poll `GET /v2/movies?project=` every 5 to 10 seconds. - Do NOT poll faster than every 5 seconds — renders take far longer than that, so you would just re-read the same status and waste requests. - Stop polling when `status` is `done` or `error`. ## Resolutions - Valid `resolution` values: `sd`, `hd`, `full-hd`, `squared`, `instagram-story`, `instagram-feed`, `twitter-landscape`, `twitter-portrait`, `custom`. - `custom` REQUIRES both `width` and `height` to be set on the Movie. - Do not invent resolution names. ```` ## Block 2 — JSON generation conventions ````markdown # JSON2Video — JSON generation conventions These rules apply whenever you generate a movie JSON for the JSON2Video API. ## Anti-patterns to avoid - DO NOT nest an element under another element. Every element is a sibling inside `scene.elements`. - DO NOT put movie-level fields (`resolution`, `quality`, `cache`, `variables`, `client_data`) inside a scene. They belong on the Movie root only. - DO NOT reference voice IDs, model names or template IDs you have not verified exist. Use the MCP tools `get_template`, `list_templates`, `list_featured_templates` to confirm IDs, or call the relevant catalog endpoint. - DO NOT use the deprecated `template` element type. Use `component`, `html` or assemble explicit elements instead. - DO NOT set `cache: false` everywhere "just in case" — it disables the cache for the entire render and slows things down significantly. ## Error handling - A successful render request returns `success: true` with a `project` ID; the movie then shows `status: "pending"` in `GET /v2/movies` until a worker picks it up. You must poll. - A render that fails comes back with `status: "error"` and a `message` field describing what went wrong. Read the `message` first; it tells you the offending path inside the movie JSON (e.g. `scenes[1].elements[0].src is not a valid URL`). - A 4xx HTTP response from the submit call means the JSON is invalid before rendering even starts. Validate first. ## Validate before submitting - Use the JSON Schema for Movie to validate locally. It is available at: https://json2video.com/docs/v2/movie-schema.json - If you have the MCP server installed, call the `validate_movie` tool — it runs the same validation the API does, but locally and faster. ## When in doubt - Fetch the raw markdown of the relevant doc page by appending `.md` to its URL. Example: https://json2video.com/docs/v2/reference/json-syntax/movie.md - Or list endpoints and elements via the MCP `get_help` tool. ````