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 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. The minimum valid body has a single scenes array; everything else has defaults.
{
"resolution": "full-hd",
"scenes": [
{
"elements": [
{ "type": "video", "src": "https://example.com/video.mp4" }
]
}
]
}
The body may also reference a saved template:
{
"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
{
"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
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
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} every 5–10 seconds until status is done, error, or timeout.
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.