# Liệt kê video mở

> **Đường này đã đóng băng.** Nó vẫn chạy và app đang dùng không phải đổi gì, nhưng Wandura không phát triển thêm tính năng cho nó. Tích hợp mới nên đi theo [Kho video riêng](https://docs.wandura.ai/vi/developers/concepts/own-space), nơi app của bạn đọc thẳng từ nền tảng video trong kho của tổ chức mình.

Trả về một trang các video mở được cấp cho tổ chức của bạn, mỗi video kèm link MP4 đã ký có hiệu lực 1 giờ. Hãy duyệt hết mọi trang và cache kết quả trên server của bạn. Xem [Video mở](https://docs.wandura.ai/vi/developers/concepts/open-video) và [Đồng bộ video mở](https://docs.wandura.ai/vi/developers/guides/open-video-sync).

```
GET /api/delivery/v1/videos
```

**Scope:** `videos:read`, trên một key gắn với một app

## Tham số request
| Tên | Vị trí | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|---|
| `Authorization` | header | string | Có | `Bearer <API key>` của một key có scope `videos:read`. |
| `collection` | query | string | Không | Chỉ lấy một bộ sưu tập, theo mã của bộ. |
| `cursor` | query | string | Không | Token opaque lấy từ `next_cursor` trước đó. Đừng tự dựng hay phân tích nó. |
| `limit` | query | integer | Không | Số dòng mỗi trang, từ 1 đến 200. Mặc định `50`. |
| `Wandura-Version` | header | string | Không | Tuỳ chọn. Một phiên bản API theo ngày (YYYY-MM-DD) dùng cho request này thay cho bản ghim vào API key của bạn. Giá trị lạ hoặc đã ngừng trả 400 với `error_code` `unsupported_version`. |

## Ví dụ request
```bash
curl -s "https://www.wandura.ai/api/delivery/v1/videos?limit=50" \
  -H "Authorization: Bearer $WANDURA_API_KEY"
```

## Response
`200 OK`. Trường mới có thể được thêm bất cứ lúc nào: hãy bỏ qua những trường bạn không nhận ra.

| Trường | Kiểu | Mô tả |
|---|---|---|
| `rows` | array | Các video trên trang này, theo thứ tự của bộ sưu tập. |
| `next_cursor` | string hoặc null | Truyền làm `cursor` để lấy trang kế. `null` nghĩa là bạn đã có đủ. |
| `expires_at` | string (ISO 8601, UTC) | Thời điểm mọi link trên trang này ngừng chạy (1 giờ sau response). |
| `refresh_before` | string (ISO 8601, UTC) | Gọi lại danh sách trước thời điểm này (45 phút sau response). |
| `links` | object | URL tuyệt đối dựng sẵn — đi theo `links.next` thay vì tự ráp URL trang kế. |
| `links.self` | string | Chính request này. |
| `links.next` | string hoặc null | Trang sau, hoặc `null` ở trang cuối. |

### Các trường của một video (`rows[]`)
| Trường | Kiểu | Mô tả |
|---|---|---|
| `id` | string (UUID) | Video ID. Không bao giờ đổi; đồng bộ theo trường này. |
| `slug` | string | Định danh video an toàn cho URL. |
| `title` | string | Tên video. |
| `order_code` | string hoặc null | Mã đơn sản xuất (ví dụ `ABS_01_E01`), khớp từng ký tự với file đặt hàng của bạn, hoặc `null` với video không có trong đó. Dùng nó để ghép một lần với file đặt hàng. |
| `collection` | string | Mã bộ sưu tập của dòng này. Video thuộc hai bộ được cấp sẽ xuất hiện một lần dưới mỗi bộ. |
| `position` | integer | Vị trí của video trong bộ sưu tập. Các dòng trả về theo thứ tự của bộ. |
| `duration_ms` | integer hoặc null | Thời lượng tính bằng mili giây. |
| `muscle_groups` | mảng string | Nhóm cơ được tập. Có thể rỗng. |
| `equipment` | mảng string | Dụng cụ cần có. Có thể rỗng. |
| `difficulty` | string hoặc null | Nhãn độ khó. |
| `kcal_est` | integer hoặc null | Lượng calo ước tính tiêu hao. |
| `mp4` | object | Link MP4 đã ký, dùng được tới `expires_at`. Phát nguyên như nhận được; đừng lưu chúng trong app. |
| `mp4.p480` | string | Link MP4 480p. Mặc định phát bản này. |
| `mp4.p720` | string hoặc null | Link MP4 720p, hoặc `null` cho tới khi có bản này. |

Dừng duyệt khi `next_cursor` là `null`, không bao giờ dừng vì một trang ngắn hay rỗng: các video chưa sẵn sàng phát bị bỏ qua.

## Ví dụ response
```json
{
  "rows": [
    {
      "id": "3f6c1a52-8d0e-4b7a-9c21-5e4f2a7b9d10",
      "slug": "core-abs-01",
      "title": "Core abs 01",
      "order_code": "ABS_01_E01",
      "collection": "ABS_01",
      "position": 1,
      "duration_ms": 42000,
      "muscle_groups": ["abs"],
      "equipment": [],
      "difficulty": "beginner",
      "kcal_est": 12,
      "mp4": { "p480": "https://cdn.example.com/v/core-abs-01-480.mp4?sign=example", "p720": null }
    }
  ],
  "next_cursor": null,
  "expires_at": "2026-10-01T13:00:00.000Z",
  "refresh_before": "2026-10-01T12:45:00.000Z"
}
```

## Header của response
| Header | Response | Mô tả |
|---|---|---|
| `Wandura-API-Status` | mọi response | Trạng thái API. Hiện là `beta`, ở mọi response, thành công hay lỗi. |
| `Wandura-Version` | mọi response | Phiên bản API theo ngày (YYYY-MM-DD) mà response này dùng: bản ghim vào API key của bạn, hoặc bản bạn gửi trong header request `Wandura-Version`. Key tạo trước khi có phiên bản ngày dùng `2026-10-02`. |
| `Wandura-Request-Id` | mọi response | Mã định danh mờ của request này (`req_` và 26 ký tự). Mọi response đều có, kể cả lỗi, và body lỗi lặp lại nó ở `request_id`. Hãy ghi log nó, và gửi kèm khi liên hệ Wandura về một request: nó tìm đúng request đó trong log của chúng tôi. Đừng phân tích chuỗi này. |
| `Cache-Control` | mọi response | Luôn là `no-store`. Đừng lưu các response này trong cache dùng chung. |
| `Deprecation` | 200 | Chỉ có khi endpoint này, hoặc phiên bản ngày đang phục vụ response, đã bị ngừng hỗ trợ (RFC 9745): `@<giây unix>` của ngày ngừng. Hãy ghi nó vào hệ giám sát. |
| `Sunset` | 200 | Chỉ có khi endpoint hay phiên bản ngày đã bị ngừng có ngày thôi chạy (RFC 8594), dạng ngày HTTP. |
| `Link` | 200 | Có kèm `Deprecation`: `<https://docs.wandura.ai/…>; rel="deprecation"` (và `rel="sunset"`) trỏ tới trang giải thích dùng gì thay thế. |
| `Retry-After` | 429, 503 | Số giây cần chờ trước khi thử lại. Có ở response 429 và các response 503 nên thử lại. |
| `X-RateLimit-Limit` | 429 | Số request (với playback là số vé) được phép trong một cửa sổ cho nhóm endpoint này và tổ chức của bạn. |
| `X-RateLimit-Window` | 429 | Độ dài cửa sổ tính bằng giây. |

## Lỗi
| Status | `error_code` | Khi nào |
|---|---|---|
| 400 | không có | `cursor` không phải token do endpoint này cấp, hoặc `limit` không phải số nguyên từ 1 đến 200. |
| 400 | `unsupported_version` | Header `Wandura-Version`, hoặc bản ghim của key, là bản lạ hay đã ngừng. Body có thêm `supported_versions` và `latest_version` (và `upgrade_to` khi bản ghim đã ngừng). |
| 401 | không có | Thiếu header `Authorization: Bearer`, hoặc key không tồn tại hay đã bị thu hồi. |
| 403 | không có | Key thiếu scope `videos:read`. |
| 403 | không có (`error` là `app_not_registered`) | Key chưa gắn với app nào. Đăng ký app ở trang Developers rồi dùng key của app đó. |
| 403 | không có (`error` là `org_suspended`) | Quyền truy cập của tổ chức bạn đang bị tạm ngưng. |
| 404 | không có (`error` là `collection_not_found`) | Bộ sưu tập không tồn tại, chưa publish, hoặc chưa cấp cho tổ chức của bạn. |
| 429 | `rate_limited` | Đã chạm rate limit của endpoint này. |
| 500 | không có | Không đọc được danh sách video. Retry với backoff. |
| 503 | `service_unavailable` | Wandura chưa kiểm được API key kịp thời. Key không sai: thử lại sau số giây trong `Retry-After`, có backoff. |

Cách xử lý từng lỗi: [Lỗi](https://docs.wandura.ai/vi/developers/concepts/errors).
