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

# Tạo Video

> Cách tạo video với các model của Trạm AI

Trạm AI hỗ trợ tạo video từ text prompt (và tùy chọn thêm ảnh tham chiếu) thông qua một API bất đồng bộ chuyên dụng (asynchronous API). Hiện tại Trạm AI hỗ trợ tạo video với các model: `veo-3.1`, `seedance-2.0`, `Kling 3.0`.

Xem khả năng của từng model và bảng giá bằng cách lọc [danh sách model theo output video](https://tramlabs.io/models).

## Khám phá model

Có thể tìm các model tạo video theo nhiều cách:

### Thông qua Models API

Dùng [Public Models API](/tong-quan/models#public-models-api-publicmodels) để lấy danh sách model, sau đó lọc các model có `mode` là `"video_generation"`:

**`lines`**

```bash lines
# Lấy danh sách tất cả model
curl "https://api.tramlabs.io/public/models"

# Tìm model tạo video: lọc các model có mode = "video_generation"
```

### Trên trang Models

Truy cập [trang Models](/tong-quan/models) và lọc theo output modalities để tìm các model có khả năng tạo video. Hãy để ý những model có liệt kê `"video"` trong phần output modalities.

## Cách hoạt động

Tạo video là quá trình theo cơ chế chạy ngầm (bất đồng bộ-asynchronous), khác với tạo văn bản hay hình ảnh, vì nó mất nhiều thời gian hơn đáng kể. Quy trình diễn ra như sau:

1. **Gửi** một request tạo video đến `POST /v1/videos`
2. **Nhận** ngay một job ID (status `submitted`)
3. **Poll** trạng thái job (`GET /v1/videos/{jobId}`) cho đến khi status là `completed`
4. **Tải** video từ content endpoint (`GET /v1/videos/{jobId}/content`)

## Sử dụng API

### Gửi request tạo video

**`Python expandable lines`**

```python Python expandable lines
import requests
import json
import time

url = "https://api.tramlabs.io/v1/videos"
headers = {
    "Authorization": f"Bearer {API_KEY_REF}",
    "Content-Type": "application/json"
}

payload = {
    "model": "{{MODEL}}",
    "prompt": "A golden retriever playing fetch on a sunny beach with waves crashing in the background"
}

# Step 1: Submit the generation request
response = requests.post(url, headers=headers, json=payload)
result = response.json()

job_id = result["id"]
print(f"Job submitted: {job_id}")
print(f"Status: {result['status']}")  # "submitted"

# Step 2: Poll until completion
while True:
    time.sleep(30)  # Wait 30 seconds between polls
    poll_response = requests.get(f"{url}/{job_id}", headers=headers)
    status = poll_response.json()

    print(f"Status: {status['status']}")

    if status["status"] == "completed":
        # Step 3: Download the video
        content_url = f"{url}/{job_id}/content"
        video_response = requests.get(content_url, headers=headers)
        with open("output.mp4", "wb") as f:
            f.write(video_response.content)
        print("Video saved to output.mp4")
        break
    elif status["status"] == "failed":
        print(f"Generation failed: {status.get('error', {}).get('message', 'Unknown error')}")
        break
```

**`TypeScript (fetch)`**

```typescript title="TypeScript (fetch)" expandable lines
const baseUrl = 'https://api.tramlabs.io/v1/videos';
const headers = {
  Authorization: `Bearer ${API_KEY_REF}`,
  'Content-Type': 'application/json',
};

// Step 1: Submit the generation request
const response = await fetch(baseUrl, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    model: '{{MODEL}}',
    prompt: 'A golden retriever playing fetch on a sunny beach with waves crashing in the background',
  }),
});

const result = await response.json();
const jobId = result.id;
console.log(`Job submitted: ${jobId}`);
console.log(`Status: ${result.status}`); // "submitted"

// Step 2: Poll until completion
while (true) {
  await new Promise((resolve) => setTimeout(resolve, 30000)); // Wait 30 seconds
  const pollResponse = await fetch(`${baseUrl}/${jobId}`, { headers });
  const status = await pollResponse.json();

  console.log(`Status: ${status.status}`);

  if (status.status === 'completed') {
    // Step 3: Download the video
    const videoResponse = await fetch(`${baseUrl}/${jobId}/content`, { headers });
    const videoBuffer = await videoResponse.arrayBuffer();
    // Save or process the video buffer
    console.log('Video ready!');
    break;
  } else if (status.status === 'failed') {
    console.error(`Generation failed: ${status.error?.message ?? 'Unknown error'}`);
    break;
  }
}
```

**`cURL`**

```bash title="cURL" expandable lines
# Step 1: Submit the generation request
curl -X POST "https://api.tramlabs.io/v1/videos" \
  -H "Authorization: Bearer $TRAM_AI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "{{MODEL}}",
    "prompt": "A golden retriever playing fetch on a sunny beach with waves crashing in the background"
  }'

# Response (202 Accepted):
# {
#   "id": "<job_id>",
#   "object": "video.job",
#   "status": "submitted",
#   "model": "veo-3.1",
#   "created_at": 1782886429
# }

# Step 2: Poll for status
curl "https://api.tramlabs.io/v1/videos/<job_id>" \
  -H "Authorization: Bearer $TRAM_AI_API_KEY"

# Step 3: Once status is "completed", download the video
curl "https://api.tramlabs.io/v1/videos/<job_id>/content" \
  -H "Authorization: Bearer $TRAM_AI_API_KEY" \
  --output video.mp4
```

### Tham số request

| Tham số             | Kiểu             | Bắt buộc | Mô tả                                                                                                              |
| ------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `model`             | string           | Có       | Model dùng để tạo video (ví dụ: `veo-3.1`, `seedance-2.0-mini`)                                                    |
| `prompt`            | string           | Có       | Mô tả bằng văn bản về video cần tạo (tối đa 10.000 ký tự)                                                          |
| `seconds`           | integer          | Không    | Thời lượng video được tạo, tính bằng giây. Mặc định là thời lượng tối đa của model                                 |
| `size`              | string           | Không    | Kích thước / tỷ lệ khung hình (ví dụ: `1280x720`, `16:9`)                                                          |
| `input_reference`   | string \| object | Không    | Ảnh first frame cho image-to-video (data URI hoặc URL). Xem [API reference](/api-reference/videos#input_reference) |
| `last_frame`        | string \| object | Không    | Ảnh last frame — tạo video chuyển tiếp từ first frame đến last frame. Chỉ model Seedance                           |
| `reference_images`  | string\[]        | Không    | Danh sách URL hoặc data URI ảnh tham khảo (tối đa 9). Chỉ model Seedance. Không dùng cùng `input_reference`        |
| `resolution`        | string           | Không    | Độ phân giải: `480p`, `720p`, `1080p`, `4k`. Chỉ model Seedance                                                    |
| `seed`              | integer          | Không    | Seed để tạo kết quả có thể tái tạo                                                                                 |
| `generate_audio`    | boolean          | Không    | Tạo âm thanh đồng bộ. Mặc định `true` trên Seedance                                                                |
| `watermark`         | boolean          | Không    | Đóng dấu "AI-generated" vào video. Chỉ model Seedance                                                              |
| `return_last_frame` | boolean          | Không    | Trả URL ảnh frame cuối trong response khi hoàn tất. Chỉ model Seedance                                             |
| `parameters`        | object           | Không    | Tham số bổ sung truyền thẳng đến provider (xem bên dưới)                                                           |

### Tham số pass-through (`parameters`)

Trường `parameters` cho phép truyền các tham số riêng của provider mà Trạm AI không map trực tiếp. Các tham số này được chuyển tiếp đến provider.

**Các tham số pass-through được hỗ trợ (Vertex AI / Veo):**

| Tham số          | Kiểu   | Mô tả                                           |
| ---------------- | ------ | ----------------------------------------------- |
| `resolution`     | string | Độ phân giải video: `"720p"`, `"1080p"`, `"4k"` |
| `numberOfVideos` | number | Số lượng video cần tạo                          |

**Ví dụ:**

**`lines`**

```json lines
{
  "model": "veo-3.1",
  "prompt": "A time-lapse of a flower blooming",
  "seconds": 8,
  "size": "1280x720",
  "parameters": {
    "resolution": "1080p",
    "numberOfVideos": 1
  }
}
```

> **Note**
>
> Với model Seedance, `resolution` là top-level param (cùng với `seed`, `generate_audio`, `watermark`, `return_last_frame`), không cần đặt trong `parameters`. Với model Veo, `resolution` phải đặt trong `parameters`.

> **Warning**
>
> Với model Veo (Google), chỉ các tham số nằm trong block `parameters` của provider mới được pass-through. Các tham số nằm ở level `instances` (như `lastFrame`, `referenceImages`, `video`) **không thể** truyền qua cách này.

### Mapping tham số

Trạm AI tự động map các tham số chuẩn sang format của provider:

| Tram API            | Vertex AI (Veo)              | Seedance (ByteDance)         | Mô tả              |
| ------------------- | ---------------------------- | ---------------------------- | ------------------ |
| `prompt`            | `instances[0].prompt`        | `prompt`                     | Text prompt        |
| `seconds`           | `parameters.durationSeconds` | `duration`                   | Thời lượng video   |
| `size`              | `parameters.aspectRatio`     | `ratio`                      | Tỷ lệ khung hình   |
| `input_reference`   | `instances[0].image`         | `input_reference` (data URI) | Ảnh first frame    |
| `last_frame`        | —                            | `last_frame` (data URI)      | Ảnh last frame     |
| `reference_images`  | —                            | `reference_images`           | Ảnh tham khảo      |
| `resolution`        | `parameters.resolution` ⚠️   | `resolution`                 | Độ phân giải       |
| `seed`              | —                            | `seed`                       | Seed tái tạo       |
| `generate_audio`    | —                            | `generate_audio`             | Tạo âm thanh       |
| `watermark`         | —                            | `watermark`                  | Dấu AI-generated   |
| `return_last_frame` | —                            | `return_last_frame`          | Trả URL frame cuối |

### Ràng buộc thời lượng

Mỗi model có ràng buộc thời lượng riêng. Nếu truyền giá trị `seconds` không hợp lệ, request sẽ bị từ chối với lỗi 400.

| Model          | Thời lượng cho phép (giây) | Ghi chú                                     |
| -------------- | -------------------------- | ------------------------------------------- |
| `veo-3.1`      | 4, 6, 8                    |                                             |
| `seedance-2.0` | 4–15 (integer), hoặc -1    | `-1` để model tự chọn thời lượng phù hợp    |
| `Kling 3.0`    | 3–15 (string)              | Nếu dùng video reference thì tối đa 10 giây |

## Định dạng response

### Response khi gửi (202 Accepted)

Khi người dùng gửi một request tạo video, Trạm AI sẽ trả về response ngay lập tức kèm theo thông tin chi tiết của job:

**`lines`**

```json lines
{
  "id": "c0845e6f-...",
  "object": "video.job",
  "status": "submitted",
  "model": "veo-3.1",
  "created_at": 1782886429
}
```

### Response khi poll

Khi poll trạng thái của job (`GET /v1/videos/{jobId}`), response sẽ phản ánh trạng thái hiện tại:

**Đang xử lý:**

**`lines`**

```json lines
{
  "id": "c0845e6f-...",
  "object": "video.job",
  "status": "processing",
  "model": "veo-3.1",
  "created_at": 1782886429
}
```

**Hoàn tất:**

**`lines`**

```json lines
{
  "id": "c0845e6f-...",
  "object": "video.job",
  "status": "completed",
  "model": "veo-3.1",
  "created_at": 1782886429,
  "completed_at": 1782886489,
  "result": {
    "duration_seconds": 8
  },
  "usage": {
    "duration_seconds": 8,
    "cost_vnd": 42346
  }
}
```

**Thất bại:**

**`lines`**

```json lines
{
  "id": "c0845e6f-...",
  "object": "video.job",
  "status": "failed",
  "model": "veo-3.1",
  "created_at": 1782886429,
  "error": {
    "message": "Video generation failed."
  }
}
```

### Các trạng thái của job

| Status       | Mô tả                                            |
| ------------ | ------------------------------------------------ |
| `submitted`  | Job đã được gửi và đang trong hàng đợi           |
| `processing` | Video đang được tạo                              |
| `completed`  | Video đã sẵn sàng để tải                         |
| `failed`     | Quá trình tạo thất bại (kiểm tra trường `error`) |
| `expired`    | Job đã hết hạn trước khi hoàn tất                |

### Tải video

Khi status của job là `completed`, người dùng có thể tải video từ content endpoint:

**`lines`**

```bash lines
curl "https://api.tramlabs.io/v1/videos/{jobId}/content" \
  -H "Authorization: Bearer $TRAM_AI_API_KEY" \
  --output video.mp4
```

Response trả về trực tiếp bytes video (`video/mp4`).

### Liệt kê các job

Người dùng có thể liệt kê các video job của mình:

**`lines`**

```bash lines
# Liệt kê tất cả job (mặc định 20 job gần nhất)
curl "https://api.tramlabs.io/v1/videos" \
  -H "Authorization: Bearer $TRAM_AI_API_KEY"

# Lọc theo status
curl "https://api.tramlabs.io/v1/videos?status=completed&limit=10" \
  -H "Authorization: Bearer $TRAM_AI_API_KEY"
```

Response trả về mảng `data` chứa các job:

**`lines`**

```json lines
{
  "data": [
    {
      "id": "c0845e6f-...",
      "object": "video.job",
      "status": "completed",
      "model": "veo-3.1",
      "created_at": 1782886429,
      "completed_at": 1782886489,
      "result": { "duration_seconds": 8 },
      "usage": { "duration_seconds": 8, "cost_vnd": 42346 }
    }
  ]
}
```

| Query parameter | Mô tả                                                                            |
| --------------- | -------------------------------------------------------------------------------- |
| `status`        | Lọc theo trạng thái: `submitted`, `processing`, `completed`, `failed`, `expired` |
| `limit`         | Số job tối đa trả về (1-100, mặc định 20)                                        |

## Billing

Tạo video sử dụng cơ chế **deferred-settle billing**:

1. **Khi gửi**: Trạm AI đặt một hold trên tài khoản bạn cho chi phí ước tính tối đa (dựa trên thời lượng × giá mỗi giây)
2. **Khi hoàn tất**: Chi phí thực tế được tính dựa trên thời lượng video thực, hold được giải phóng và số tiền chính xác được trừ
3. **Khi thất bại/hết hạn**: Hold được giải phóng, không tính phí

Hold có thời hạn tối đa 1 giờ. Chi phí được tính bằng VND (`cost_vnd` trong response).

## Thực hành tốt nhất

* **Prompt chi tiết**: Hãy viết prompt cụ thể, giàu tính mô tả để có chất lượng video tốt hơn. Nên nêu rõ về chuyển động, góc máy, ánh sáng và bố cục cảnh
* **Chọn thời lượng phù hợp**: Thời lượng càng dài thì càng tốn chi phí. Kiểm tra `allowedDurations` của model trước khi gửi request
* **Khoảng thời gian poll**: Dùng khoảng thời gian poll hợp lý (ví dụ: 30 giây) để tránh gọi API quá mức. Việc tạo video thường mất từ 30 giây đến vài phút, tùy theo model
* **Xử lý lỗi**: Luôn kiểm tra trạng thái `failed` và `expired` của job và xử lý trường `error` một cách phù hợp

## Zero Data Retention

Việc tạo video **không đủ điều kiện** áp dụng Zero Data Retention (ZDR). Vì tạo video là quá trình theo cơ chế chạy ngầm (bất đồng bộ-asynchronous), provider phải lưu trữ video output trong một khoảng thời gian ngắn để người dùng có thể truy xuất sau khi quá trình tạo hoàn tất. Việc lưu trữ tạm thời này là đặc tính vốn có của quy trình poll bất đồng bộ và không thể bỏ qua.

Nếu người dùng đã bật bắt buộc ZDR (qua [cài đặt tài khoản](https://console.tramlabs.io/settings) hoặc tham số `zdr` theo từng request), Trạm AI sẽ không route các request tạo video.

## Khắc phục sự cố

**Job ở trạng thái `submitted` trong thời gian dài?**

* Việc tạo video có thể mất vài phút, tùy theo model và tải của server
* Hãy tiếp tục poll theo các khoảng đều đặn (30 giây)

**Quá trình tạo thất bại?**

* Kiểm tra trường `error.message` trong response poll để biết chi tiết
* Xác minh model có hỗ trợ tạo video hay không (kiểm tra `mode: "video"` qua [Public Models API](/tong-quan/models#public-models-api-publicmodels))
* Đảm bảo prompt phù hợp và nằm trong khuôn khổ hướng dẫn của model
* Kiểm tra giá trị `seconds` có nằm trong danh sách thời lượng cho phép của model không

**Lỗi 400 khi gửi request?**

* Kiểm tra `seconds` có nằm trong `allowedDurations` của model không (ví dụ: veo-3.1 chỉ cho phép 4, 6, 8)
* Đảm bảo `prompt` không rỗng và không vượt quá 10.000 ký tự

**Không tìm thấy model?**

* Sử dụng [trang Models](/tong-quan/models) để tìm các model tạo video khả dụng
* Xác minh model slug đúng (ví dụ: `veo-3.1`)