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 (or, equivalently, a movie-level elements array with no scenes); 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" }
}
Size limit
The request body can be up to 2 MB (2,097,152 bytes, counted in UTF-8 bytes). A larger body is rejected with 413 and "code": "movie_json_too_large", and nothing is created or charged:
{
"message": "The movie JSON is too large: 2.35 MB (2,461,234 bytes). The maximum size of a movie JSON is 2 MB (2,097,152 bytes).",
"code": "movie_json_too_large"
}
There is no separate limit on the number of scenes, elements or images: only the size of the JSON counts, and the length of your render is capped by your plan (see Plans). In practice, media URLs take most of the space in a large movie. A signed URL can be 1 KB or more, so a slideshow with hundreds of photos grows quickly. If you get near the limit:
- use shorter media URLs (for example, your own short links or a CDN path instead of long signed URLs);
- split a very long video into several movies.
Media files are downloaded while the movie renders, not when you submit it. If you use signed URLs that expire, give them enough lifetime to cover the whole render, especially when you submit many movies at once.
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. |
400 |
AWS keys cannot be sent in the movie (exports[0].destinations[0]). … |
A destination in exports carries AWS keys. The body also has "code": "aws_keys_in_movie". Save the keys in an Amazon S3 destination and reference it with id. |
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. |
413 |
The movie JSON is too large: … The maximum size of a movie JSON is 2 MB (2,097,152 bytes). |
The body is over 2 MB. The body also has "code": "movie_json_too_large". See Size limit. Do not retry the same body. |
413 |
Error creating movie: the movie record is larger than the database limit … |
The movie could not be saved because of its size. The body also has "code": "movie_record_too_large". This should not happen with a body under 2 MB; contact support. |
500 |
Error creating movie: the movie JSON could not be stored. No movie was created; please try again. |
A temporary storage error. The body also has "code": "movie_json_not_stored". Nothing was created or charged. Retry with exponential backoff. |
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.