# Lấy metadata một playlist

Dành cho đối tác video. Trả về một playlist cho CMS của bạn: Category, nhóm, tên, mô tả, ảnh, và các clip theo thứ tự kèm tên, mô tả, thời lượng, nhóm cơ, dụng cụ, độ khó, calo, poster và mã đơn sản xuất. Khi tổ chức của bạn được cấp playlist này, nó cho biết thêm bản chép của bạn nằm ở đâu trong media space của bạn. Gọi khi [Liệt kê playlist](https://docs.wandura.ai/vi/developers/reference/playlists-list) hiện một `content_version` bạn chưa lưu.

```
GET /api/delivery/v1/playlists/{code}
```

**Scope:** `metadata:read`

## Mô tả
Đọc được mọi playlist trong kho, dù được cấp hay chưa. Khi `granted` là `true`, `media` cho biết media space của bạn và playlist trong đó, mỗi clip đã chép kèm video ID (`vid`) và `format` (`hls` hoặc `mp4`). Khi `granted` là `false`, `media` là `null` và mọi clip có `ready: false`, không có video ID. Không có link phát: app của bạn phát bản chép thẳng từ [media space của chính bạn](https://docs.wandura.ai/vi/developers/concepts/own-space).

Playlist không tồn tại và playlist không còn trong kho (đã withdrawn, hoặc Category của nó đã withdrawn) trả cùng một `404`. Key của tổ chức lấy phim nhận `403` `4032`; hãy dùng [Lấy metadata một bộ phim](https://docs.wandura.ai/vi/developers/reference/series-get).

Response thành công được bọc trong envelope `{code, message, data, server_time}`; lỗi dùng cùng envelope nhưng không có `data`, trừ `429` và `400` `unsupported_version` dùng định dạng phẳng. Endpoint này dùng chung bộ đếm rate limit với [catalog](https://docs.wandura.ai/vi/developers/reference/catalog).

## 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 `metadata:read`. |
| `code` | path | string | Có | Mã playlist, ví dụ `wall-pilates-01` (`code` trong danh sách). |
| `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/playlists/wall-pilates-01 \
  -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: bỏ qua trường bạn không biết.

| Trường | Kiểu | Mô tả |
|---|---|---|
| `code` | integer | `0`. |
| `message` | string | `"ok"`. |
| `data` | object |  |
| `server_time` | integer | Giờ server tính bằng giây Unix epoch. |
| `links` | object | URL tuyệt đối dựng sẵn của chính tài nguyên này (link API, không phải file media). |

### Các trường của một playlist (`data`)
| Trường | Kiểu | Mô tả |
|---|---|---|
| `code` | string | Mã playlist. |
| `status` | string | Luôn là `active` (playlist không có trong kho trả 404). |
| `title` | string | Tên playlist. |
| `description` | string hoặc null | Mô tả playlist, hoặc `null`. |
| `category` | object | Category của playlist (ví dụ Fitness). |
| `category.slug` | string | Mã ổn định của Category, ví dụ `fitness`. Cũng là bộ lọc `category` của danh sách. |
| `category.name` | string | Tên hiển thị. |
| `group` | object hoặc null | Nhãn lọc của playlist trong Category (ví dụ Pilates hoặc Gym), hoặc `null`. Không phải đơn vị cấp quyền. |
| `group.code` | string | Mã ổn định. |
| `group.title` | string | Tên hiển thị. |
| `updated_at` | integer | Lần đổi gần nhất tính bằng giây Unix epoch. |
| `content_version` | string | Mã băm của toàn bộ response này. Lưu lại; danh sách trả cùng giá trị thì không có gì phải tải. |
| `metadata_version` | string hoặc null | Mã băm của riêng metadata playlist (giống nhau với mọi tổ chức). |
| `granted` | boolean | `true` khi tổ chức của bạn được cấp playlist này. |
| `media` | object hoặc null | Bản chép của bạn nằm ở đâu, hoặc `null` khi playlist chưa được cấp. Việc phát diễn ra giữa app và media space của bạn; API này không bao giờ trả link phát. |
| `media.space` | string hoặc null | Media space của bạn cho Category này, hoặc `null` khi chưa dựng. |
| `media.playlist_id` | string hoặc null | ID playlist trong `space` (đặt tên theo `code`), hoặc `null` cho tới khi nó tồn tại. |
| `media.playlist_name` | string hoặc null | Tên playlist đó, hoặc `null`. |
| `media.playlists` | array | Mọi playlist chứa các clip theo thứ tự (mỗi cái tối đa 100 video, nên playlist dài hơn nối tiếp ở `<code>-2`…). |
| `images` | array | Ảnh của playlist. Hiện có một `cover` (poster của clip đầu tiên); có thể có thêm loại. Rỗng khi không có. |
| `images[].kind` | string | `cover`. Có thể có loại mới. |
| `images[].source` | string | `media_space` hoặc `wandura_cdn`. Có thể có giá trị mới. |
| `images[].url` | string hoặc null | URL ảnh khi `source` là `wandura_cdn`, ngược lại `null`. Tải về và tự lưu bản của bạn. |
| `images[].expires_at` | integer hoặc null | Lúc `url` hết hiệu lực, tính bằng giây Unix epoch, hoặc `null`. |
| `images[].space` | string hoặc null | Media space của bạn đang chứa ảnh, hoặc `null`. |
| `images[].uri` | string hoặc null | URI của ảnh trong `space`, hoặc `null`. |
| `clips` | array | Các clip theo thứ tự playlist. |
| `clips[].position` | integer | Vị trí trong playlist. Các clip trả về theo thứ tự này. |
| `clips[].id` | string (UUID) | Clip ID. Không bao giờ đổi; đồng bộ theo trường này. Một clip có thể nằm trong nhiều playlist. |
| `clips[].title` | string | Tên clip. |
| `clips[].description` | string hoặc null | Mô tả ngắn, hoặc `null`. |
| `clips[].duration_ms` | integer hoặc null | Thời lượng tính bằng mili giây. |
| `clips[].muscle_groups` | mảng string | Nhóm cơ được tập. Có thể rỗng. |
| `clips[].equipment` | mảng string | Dụng cụ cần có. Có thể rỗng. |
| `clips[].difficulty` | string hoặc null | `beginner`, `intermediate` hoặc `advanced`, hoặc `null`. |
| `clips[].kcal_est` | integer hoặc null | Lượng calo ước tính tiêu hao. |
| `clips[].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`. |
| `clips[].poster` | object hoặc null | Một ảnh, hoặc `null` khi không có. `source` cho biết lấy ở đâu. `media_space`: ảnh đã nằm trong media space của bạn (`space`) tại `uri` (với clip đó cũng là poster gắn trên `vid` của nó); tải qua space của bạn như cách bạn tải video. `wandura_cdn`: link dùng được tới `expires_at`; tải file về và tự lưu bản của bạn, đừng lưu link. Có thể có nguồn mới: bỏ qua giá trị bạn không biết. |
| `clips[].poster.source` | string | `media_space` (trong media space của bạn) hoặc `wandura_cdn` (link Wandura để tải về). Có thể có giá trị mới. |
| `clips[].poster.url` | string hoặc null | URL ảnh khi `source` là `wandura_cdn`, ngược lại `null`. |
| `clips[].poster.expires_at` | integer hoặc null | Lúc `url` hết hiệu lực, tính bằng giây Unix epoch, hoặc `null`. |
| `clips[].poster.space` | string hoặc null | Media space của bạn đang chứa ảnh khi `source` là `media_space`, ngược lại `null`. |
| `clips[].poster.uri` | string hoặc null | URI của ảnh trong `space` khi `source` là `media_space`, ngược lại `null`. |
| `clips[].ready` | boolean | `true` khi clip đã chép xong và phát được trong media space của bạn (`media.space`). |
| `clips[].vid` | string hoặc null | Video ID của clip trong media space của bạn, hoặc `null` khi chưa chép (hoặc playlist chưa được cấp). |
| `clips[].format` | string hoặc null | Dạng đóng gói của bản chép: `hls` (clip từ 30 giây) hoặc `mp4` (clip ngắn hơn). `null` khi chưa chép. |
| `clips[].playlist_id` | string hoặc null | ID playlist trong media space của bạn đang chứa clip này, hoặc `null`. |
| `clips[].playlist_name` | string hoặc null | Tên playlist đó, hoặc `null`. |

### Ảnh
Mỗi ảnh (ảnh `cover` của playlist trong `images[]`, và `poster` của từng clip) ghi trong `source` chỗ lấy nó. `media_space`: ảnh đã nằm trong media space của bạn (`space`, `uri`); tải qua space của bạn như cách tải video. `wandura_cdn`: `url` là link dùng được 1 giờ (tới `expires_at`); tải file về và tự lưu bản của bạn, không bao giờ lưu link. Ảnh không có thì là `null`.

## Ví dụ response
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "code": "wall-pilates-01",
    "status": "active",
    "title": "Wall Pilates",
    "description": "Ten low-impact moves using a wall for support.",
    "category": { "slug": "fitness", "name": "Fitness" },
    "group": { "code": "pilates", "title": "Pilates" },
    "updated_at": 1790390000,
    "content_version": "5d41402abc4b2a76b9719d911017c592",
    "metadata_version": "0cc175b9c0f1b6a831c399e269772661",
    "granted": true,
    "media": {
      "space": "your-fitness-space",
      "playlist_id": "9f8e7d6c5b4a3921",
      "playlist_name": "wall-pilates-01",
      "playlists": [{ "id": "9f8e7d6c5b4a3921", "name": "wall-pilates-01" }]
    },
    "images": [{ "kind": "cover", "source": "media_space", "url": null, "expires_at": null, "space": "your-fitness-space", "uri": "your-fitness-space/fit-3f2a1b4c.jpg" }],
    "clips": [
      {
        "position": 1,
        "id": "3f2a1b4c-5d6e-4f70-8a9b-0c1d2e3f4a5b",
        "title": "Wall Squat",
        "description": "Slide down the wall and hold.",
        "duration_ms": 45000,
        "muscle_groups": ["quads", "glutes"],
        "equipment": ["wall"],
        "difficulty": "intermediate",
        "kcal_est": 9,
        "order_code": "ABS_01_E01",
        "poster": { "source": "media_space", "url": null, "expires_at": null, "space": "your-fitness-space", "uri": "your-fitness-space/fit-3f2a1b4c.jpg" },
        "ready": true,
        "vid": "v0a1b2c3d4e5f6",
        "format": "hls",
        "playlist_id": "9f8e7d6c5b4a3921",
        "playlist_name": "wall-pilates-01"
      }
    ]
  },
  "server_time": 1790400000
}
```

## 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` | 200 | 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` | 200 | 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` | 200 | Luôn là `no-store`. Đừng lưu các response này trong cache dùng chung. |
| `Retry-After` | 429 | 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. |

## Lỗi
| Status | `code` | Khi nào |
|---|---|---|
| 400 | không có | Header `Wandura-Version`, hoặc bản ghim của key, là bản lạ hay đã ngừng. |
| 401 | 4010 | Thiếu header `Authorization: Bearer`. |
| 401 | 4011 | Key không tồn tại hoặc đã bị thu hồi. |
| 403 | 4030 | Key thiếu scope `metadata:read`. |
| 403 | 4031 | Quyền truy cập của tổ chức bạn đang bị tạm ngưng. |
| 403 | 4032 | Metadata playlist dành cho đối tác video; tổ chức của bạn lấy phim (dùng các endpoint series). |
| 404 | 4040 | Không có playlist này, hoặc nó không có trong kho (cố ý trả cùng một câu). |
| 429 | không có | Đã chạm rate limit. |
| 503 | 5030 | Playlist tạm thời không khả dụng. |
| 503 | 5031 | Wandura chưa kiểm được API key kịp thời. |

Xem [Lỗi](https://docs.wandura.ai/vi/developers/concepts/errors#playlists-get-errors) để biết cách xử lý từng trường hợp.
