# Liệt kê playlist

Dành cho đối tác video. Trả về một trang các playlist trong kho video của Wandura, mỗi playlist kèm `content_version`. Dùng trong job đồng bộ CMS: khi `content_version` bằng giá trị bạn đã lưu thì không có gì đổi, bạn bỏ qua được [Lấy metadata một playlist](https://docs.wandura.ai/vi/developers/reference/playlists-get).

```
GET /api/delivery/v1/playlists
```

**Scope:** `metadata:read`

## Mô tả
Liệt kê mọi playlist trong kho, thay đổi cũ nhất trước: mọi Category, hoặc một Category với `category`. Bạn thấy cả kho, không chỉ phần được cấp: `granted` cho biết playlist nào tổ chức của bạn đã có. Gửi `updated_since` (giây Unix epoch) để chỉ nhận các playlist đổi sau mốc đó. Playlist Wandura rút khỏi kho — chính playlist đó, hoặc cả Category của nó — vẫn còn trong danh sách dạng `withdrawn` 30 ngày, để job đồng bộ của bạn kịp thấy; ở dòng withdrawn thì `clip_count` và `content_version` là `null`.

Endpoint này chỉ đọc và không trả link phát: app của bạn phát bản chép trong [media space của chính bạn](https://docs.wandura.ai/vi/developers/concepts/own-space). Key của tổ chức lấy phim nhận `403` `4032`; hãy dùng [Liệt kê bộ phim](https://docs.wandura.ai/vi/developers/reference/series-list).

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`. |
| `category` | query | string | Không | Tuỳ chọn. Slug Category, ví dụ `fitness`. Bỏ trống = mọi Category. Category bị rút khỏi kho chưa quá 30 ngày vẫn lọc được (playlist của nó trả về `withdrawn`). |
| `updated_since` | query | integer | Không | Tuỳ chọn. Giây Unix epoch; chỉ trả playlist đổi sau mốc này. |
| `limit` | query | integer | Không | Tuỳ chọn. Số phần tử mỗi trang, 1 đến 100. Mặc định 50. |
| `cursor` | query | string | Không | Tuỳ chọn. `next_cursor` của trang trước, truyền lại nguyên vẹn. |
| `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`. |

Giữ nguyên `updated_since` và `category` trên mọi trang của một lượt đồng bộ, và đi theo `next_cursor` tới khi nó là `null`. Trang đầy luôn kèm `next_cursor`, nên trang cuối có thể rỗng.

## Ví dụ request
```bash
curl -s "https://www.wandura.ai/api/delivery/v1/playlists?category=fitness&updated_since=1790390000&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: bỏ qua trường bạn không biết.

| Trường | Kiểu | Mô tả |
|---|---|---|
| `code` | integer | `0`. |
| `message` | string | `"ok"`. |
| `data.next_cursor` | string hoặc null | Gửi lại làm `cursor`; `null` ở trang cuối. |
| `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 một response dạng danh sách. Đi theo chúng thay vì tự ráp URL. |

### Các trường của một playlist (`data.items[]`)
| Trường | Kiểu | Mô tả |
|---|---|---|
| `code` | string | Mã playlist, ví dụ `wall-pilates-01`. Không bao giờ đổi. |
| `status` | string | `active`: đang có trong kho. `withdrawn`: Wandura đã rút playlist khỏi kho; nó còn trong danh sách 30 ngày để job đồng bộ của bạn kịp thấy. Có thể có giá trị mới. |
| `title` | string | Tên playlist. |
| `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ị. |
| `clip_count` | integer hoặc null | Số clip trong playlist. `null` khi withdrawn. |
| `granted` | boolean | `true` khi tổ chức của bạn được cấp playlist này (clip được chép vào media space của bạn). Bạn thấy cả kho; playlist `false` không mang video ID. |
| `updated_at` | integer | Lần đổi gần nhất tính bằng giây Unix epoch (metadata, quyền của bạn, hoặc bản chép trong media space của bạn). |
| `content_version` | string hoặc null | Mã băm của mọi thứ GET /playlists/{code} trả cho bạn. Không đổi = không cần gọi chi tiết. `null` khi withdrawn. |
| `withdrawn_at` | integer hoặc null | Lúc bị rút (giây epoch), hoặc `null`. |

## Ví dụ response
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "items": [
      {
        "code": "wall-pilates-01",
        "status": "active",
        "title": "Wall Pilates",
        "category": { "slug": "fitness", "name": "Fitness" },
        "group": { "code": "pilates", "title": "Pilates" },
        "clip_count": 24,
        "granted": true,
        "updated_at": 1790390000,
        "content_version": "5d41402abc4b2a76b9719d911017c592",
        "withdrawn_at": null
      },
      {
        "code": "gym-basics-02",
        "status": "active",
        "title": "Gym Basics",
        "category": { "slug": "fitness", "name": "Fitness" },
        "group": { "code": "gym", "title": "Gym" },
        "clip_count": 18,
        "granted": false,
        "updated_at": 1790391000,
        "content_version": "7d793037a0760186574b0282f2f435e7",
        "withdrawn_at": null
      }
    ],
    "next_cursor": null
  },
  "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ó Category này. |
| 422 | 4220 | `limit` không trong 1 đến 100, `updated_since` không phải giây epoch, `category` không phải slug, hoặc `cursor` không phải giá trị endpoint này trả về. |
| 429 | không có | Đã chạm rate limit. |
| 503 | 5030 | Danh sách 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-errors) để biết cách xử lý từng trường hợp.
