Async Jobs

Beta
Submit video generation as background jobs and poll for results

The async API (V2) lets you submit a generation request and poll for its response. The result is downloadable using a URL available when the job completes. This is the recommended approach for production workloads, as it avoids the connection timeouts that can affect long-running sync requests.

Lifecycle

  1. Submit. POST to an async endpoint (/v2/{endpoint}) with your generation parameters. The response is 202 Accepted with a job ID.
  2. Poll. GET /v2/{endpoint}/{id} every few seconds until the status field is completed or failed.
  3. Download. When status is completed, the result object contains one or more URLs pointing to the generated output files.

Status values

StatusMeaning
pendingJob is queued.
processingGeneration is running.
completedJob finished successfully. result contains output URLs.
failedJob failed. error describes why.

completed and failed are terminal — stop polling when you see either.

Result format

The result object on a completed job is a map of output labels to URLs. Keys depend on the endpoint — for video-to-video-hdr, the result contains exr_frames_url:

1{
2 "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
3 "status": "completed",
4 "created_at": "2026-01-15T10:00:00.000Z",
5 "completed_at": "2026-01-15T10:02:30.000Z",
6 "result": {
7 "exr_frames_url": "https://storage.googleapis.com/example/frames.zip"
8 }
9}

Polling guidance

  • Poll every 5 seconds. Shorter intervals add load without meaningfully reducing latency.
  • Treat transient HTTP errors (5xx, network failures) as retryable — your poll loop should retry with backoff rather than giving up. The examples below omit error handling for brevity.
  • A 404 means the job doesn’t exist or has expired (see retention below).

Retention

Job status and output URLs are available for 24 hours after the job reaches a terminal state. After that, GET /v2/{endpoint}/{id} returns 404. Download or re-host outputs before that window expires.

Error handling

Errors appear in two places but use the same { type, message } shape:

  • HTTP errors on POST or GET requests — validation, auth, rate limits, etc.
  • Job failures inside the status response when a job fails during processing.

Your error handling logic can use the same type checks (e.g., content_filtered_error, insufficient_funds_error) regardless of where the error appears. See Error Handling for the full list.

Example

A complete submit → poll → download loop:

1import requests
2import time
3
4api_key = "YOUR_API_KEY"
5headers = {"Authorization": f"Bearer {api_key}"}
6
7# Submit
8
9job = requests.post(
10"https://api.ltx.video/v2/video-to-video-hdr",
11headers={\*\*headers, "Content-Type": "application/json"},
12json={"video_uri": "https://example.com/input.mp4"},
13).json()
14
15# Poll
16
17while True:
18status = requests.get(
19f"https://api.ltx.video/v2/video-to-video-hdr/{job['id']}",
20headers=headers,
21).json()
22if status["status"] in ("completed", "failed"):
23break
24time.sleep(5)
25
26if status["status"] == "failed":
27raise RuntimeError(status["error"]["message"])
28
29# Download
30
31frames = requests.get(status["result"]["exr_frames_url"])
32with open("frames.zip", "wb") as f:
33f.write(frames.content)