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:
{
"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-keyheader. - Validation: malformed JSON, missing required fields, invalid enum values.
- Quota: account out of credits, request rate-limited.
Handle synchronous errors by:
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 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:
{
"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
srcURL returned 4xx/5xx, an element source was an unsupported format. - AI provider failures: ElevenLabs / Replicate / OpenAI 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:
- AI 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
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");
}