> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs-dev.ltx.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs-dev.ltx.io/_mcp/server.

# Async Jobs

> Learn how to use the LTX async (V2) API — submit a generation request, poll for status, and retrieve the output. Covers the full job lifecycle, status values, result format, and retention policy.

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

| Status       | Meaning                                                   |
| ------------ | --------------------------------------------------------- |
| `pending`    | Job is queued.                                            |
| `processing` | Generation is running.                                    |
| `completed`  | Job finished successfully. `result` contains output URLs. |
| `failed`     | Job 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. The available keys depend on the endpoint — most video endpoints return `video_url`, while `video-to-video-hdr` returns `exr_frames_url`. Check each endpoint's reference for the exact shape.

```json
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "completed",
  "created_at": "2026-01-15T10:00:00.000Z",
  "completed_at": "2026-01-15T10:02:30.000Z",
  "result": {
    "video_url": "https://storage.googleapis.com/example/video.mp4"
  }
}
```

## Polling guidance

We recommend waiting at least **5 seconds** between polls. Choose a slightly different delay each time to spread out requests when polling multiple jobs.

* Stop polling when the status is `completed` or `failed`.
* Retry transient `5xx` responses and network failures on polling and download `GET` requests with exponential backoff. The examples below omit retry logic for brevity.
* A `404` means the job doesn't exist or has expired (see retention below).

## Retention

Job status is available for up to **24 hours** after the job reaches a terminal state, and can be removed sooner. Once it's removed, `GET /v2/{endpoint}/{id}` returns `404`.

Output URLs expire independently of job status. Download or re-host outputs as soon as you observe `completed`, rather than using these URLs as permanent storage.

## 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](/errors) for the full list.

## Example

A complete submit → poll → download loop. The cURL version uses [`jq`](https://jqlang.github.io/jq/) to read fields out of the JSON responses:

**`Python`**

```python title="Python"
import random
import requests
import time

api_key = "YOUR_API_KEY"
headers = {"Authorization": f"Bearer {api_key}"}

# Submit
submit_response = requests.post(
    "https://api.ltx.io/v2/text-to-video",
    headers={**headers, "Content-Type": "application/json"},
    json={
        "prompt": "A majestic eagle soaring through clouds at sunset",
        "model": "ltx-2-5-pro",
        "duration": 8,
        "resolution": "1920x1080",
    },
)
submit_response.raise_for_status()
job = submit_response.json()

# Poll
while True:
    time.sleep(random.uniform(5, 6))
    poll_response = requests.get(
        f"https://api.ltx.io/v2/text-to-video/{job['id']}",
        headers=headers,
    )
    poll_response.raise_for_status()
    status = poll_response.json()
    if status["status"] in ("completed", "failed"):
        break

if status["status"] == "failed":
    raise RuntimeError(status["error"]["message"])

# Download
video = requests.get(status["result"]["video_url"])
video.raise_for_status()
with open("video.mp4", "wb") as f:
    f.write(video.content)
```

**`TypeScript`**

```typescript title="TypeScript"
import { writeFileSync } from "fs";

const apiKey = "YOUR_API_KEY";
const headers = { Authorization: `Bearer ${apiKey}` };

// Submit
const submitRes = await fetch("https://api.ltx.io/v2/text-to-video", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    prompt: "A majestic eagle soaring through clouds at sunset",
    model: "ltx-2-5-pro",
    duration: 8,
    resolution: "1920x1080",
  }),
});
if (!submitRes.ok) {
  throw new Error(`Submit failed with status ${submitRes.status}`);
}
const job = await submitRes.json();

// Poll
let status;
do {
  const pollDelayMs = 5000 + Math.random() * 1000;
  await new Promise((resolve) => setTimeout(resolve, pollDelayMs));
  const res = await fetch(
    `https://api.ltx.io/v2/text-to-video/${job.id}`,
    { headers },
  );
  if (!res.ok) {
    throw new Error(`Poll failed with status ${res.status}`);
  }
  status = await res.json();
} while (status.status !== "completed" && status.status !== "failed");

if (status.status === "failed") {
  throw new Error(status.error.message);
}

// Download
const video = await fetch(status.result.video_url);
if (!video.ok) {
  throw new Error(`Download failed with status ${video.status}`);
}
writeFileSync("video.mp4", Buffer.from(await video.arrayBuffer()));
```

**`cURL`**

```curl title="cURL"
set -euo pipefail

api_key="YOUR_API_KEY"

job=$(curl -sS --fail-with-body -X POST "https://api.ltx.io/v2/text-to-video" \
  -H "Authorization: Bearer $api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A majestic eagle soaring through clouds at sunset",
    "model": "ltx-2-5-pro",
    "duration": 8,
    "resolution": "1920x1080"
  }')
id=$(jq -r .id <<<"$job")

status="pending"
while [ "$status" != "completed" ] && [ "$status" != "failed" ]; do
  sleep $((5 + RANDOM % 2))
  job=$(curl -sS --fail-with-body "https://api.ltx.io/v2/text-to-video/$id" \
    -H "Authorization: Bearer $api_key")
  status=$(jq -r .status <<<"$job")
done

if [ "$status" = "failed" ]; then
  jq -r .error.message <<<"$job" >&2
  exit 1
fi

curl -sS --fail-with-body -L -o video.mp4 "$(jq -r .result.video_url <<<"$job")"
```