# Lấy catalog được cấp phép

Trả về mọi phim và tập mà tổ chức của bạn được phép stream ngay lúc này. Response chỉ có metadata và không bao giờ chứa URL video; hãy xin playback cho từng tập bằng [Xin playback](https://docs.wandura.ai/vi/developers/reference/playback).

```
GET /api/delivery/v1/catalog
```

**Scope:** `metadata:read`

## Mô tả

Một phim được đưa vào khi tổ chức của bạn có giấy phép streaming đang hoạt động cho phim đó, hôm nay (theo UTC) nằm trong khung thời gian của giấy phép, và phim đã được publish. Với mỗi phim được đưa vào, mọi tập đã publish đều được trả về, cộng thêm một dòng placeholder cho mỗi tập dự kiến chưa publish. Phim được sắp theo `display_order`. Không có pagination. Xem [Catalog](https://docs.wandura.ai/vi/developers/concepts/catalog).

## Tham số request

| Tên | Vị trí | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|---|
| `Authorization` | header | string | Có | `Bearer <API key>`. |

Endpoint này không nhận query parameter nào.

## Ví dụ request

```bash
curl -s https://www.wandura.ai/api/delivery/v1/catalog \
  -H "Authorization: Bearer $WANDURA_API_KEY"
```

## Response

`200 OK` với một object chứa hai mảng.

### Trường của phim (`films[]`)

| Trường | Kiểu | Mô tả |
|---|---|---|
| `id` | string (UUID) | Film ID. Không bao giờ đổi: dùng làm khoá chính bên bạn, và làm `film_ref` khi xin playback. |
| `title` | string | Tên phim. |
| `slug` | string | Định danh phim an toàn cho URL, không trùng giữa các phim. Wandura không đổi nó sau khi tạo phim. Cũng được chấp nhận làm `film_ref` khi xin playback. |
| `synopsis_short` | string hoặc null | Mô tả ngắn cho thẻ. Độ dài không cố định. |
| `synopsis_full` | string hoặc null | Mô tả đầy đủ cho trang chi tiết. |
| `genres` | mảng string | Nhãn thể loại. Có thể rỗng. |
| `language` | string hoặc null | Ngôn ngữ của phim, là chữ tự do do đội nội dung Wandura ghi (ví dụ `en` hoặc `English`). Không phải danh sách mã cố định: hiển thị nguyên, đừng phân tích. |
| `poster_url` | string hoặc null | URL poster phim. |
| `status` | string | Luôn là `published`. |
| `display_order` | integer | Vị trí hiển thị, nhỏ nhất trước. Mảng đã được sắp theo trường này. |
| `planned_episodes` | integer hoặc null | Tổng số tập dự kiến. `null` khi chưa đặt: khi đó phim không có dòng placeholder nào, và `episodes` chỉ liệt kê các tập đã phát hành. |
| `free_until_episode` | integer hoặc null | Các tập có `episode_no` nhỏ hơn hoặc bằng số này là miễn phí. `0` nghĩa là không có tập miễn phí. `null` nghĩa là phim chưa được đặt số này: coi mọi tập là trả phí, và hỏi Wandura nếu bạn nghĩ phim có tập miễn phí. |
| `audience` | string hoặc null | `women`, `men`, `lgbtq`, hoặc `null` nếu chưa phân loại. |
| `form` | string hoặc null | `live_action`, `anime`, hoặc `null` nếu chưa phân loại. |
| `setting` | string hoặc null | `modern`, `city`, `fantasy`, `campus`, `palace`, hoặc `null` nếu chưa phân loại. |

### Trường của tập (`episodes[]`)

| Trường | Kiểu | Mô tả |
|---|---|---|
| `id` | string (UUID) hoặc null | Episode ID. Dùng làm `episode_id` khi xin playback. `null` ở dòng placeholder. |
| `film_id` | string (UUID) | ID của phim mà tập thuộc về. |
| `episode_no` | integer | Số tập, bắt đầu từ 1. |
| `title` | string hoặc null | Tên tập. |
| `synopsis` | string hoặc null | Mô tả tập. Thường là `null`. |
| `duration_ms` | integer hoặc null | Thời lượng tính bằng mili giây. |
| `poster_uri` | string hoặc null | Tham chiếu tới ảnh thumbnail của tập. Không đảm bảo là URL tải trực tiếp được. |
| `status` | string | `published`, hoặc `planned` với dòng placeholder. |
| `stream_ready` | boolean | `true` khi có thể cấp vé cho tập. Luôn là `false` ở dòng placeholder. |

Trong một phim, mỗi `episode_no` chỉ xuất hiện một lần, nên `film_id` + `episode_no` xác định một tập. Các tập được nhóm theo film ID và sắp theo `episode_no` trong mỗi phim. Hãy lấy thứ tự phim từ `films`.

Nếu tổ chức của bạn hiện không có giấy phép nào, cả hai mảng đều rỗng.

## Ví dụ response

```json
{
  "films": [
    {
      "id": "3f6c1a52-8d0e-4b7a-9c21-5e4f2a7b9d10",
      "title": "The Heiress Returns",
      "slug": "the-heiress-returns",
      "synopsis_short": "Cast out on her wedding day, a disowned heiress comes back to take everything.",
      "synopsis_full": "Cast out on her wedding day, Mia loses her family, her name and her fortune. Five years later she returns with a new identity and one goal.",
      "genres": ["romance", "revenge"],
      "language": "en",
      "poster_url": "https://cdn.example.com/posters/the-heiress-returns.jpg",
      "status": "published",
      "display_order": 10,
      "planned_episodes": 3,
      "free_until_episode": 1,
      "audience": "women",
      "form": "live_action",
      "setting": "modern"
    }
  ],
  "episodes": [
    {
      "id": "b2a4d6e8-1c3f-4a5b-8d7e-9f0a1b2c3d4e",
      "film_id": "3f6c1a52-8d0e-4b7a-9c21-5e4f2a7b9d10",
      "episode_no": 1,
      "title": "Episode 1",
      "synopsis": null,
      "duration_ms": 94000,
      "poster_uri": null,
      "status": "published",
      "stream_ready": true
    },
    {
      "id": "c7d9e1f3-5a6b-4c8d-9e0f-1a2b3c4d5e6f",
      "film_id": "3f6c1a52-8d0e-4b7a-9c21-5e4f2a7b9d10",
      "episode_no": 2,
      "title": "Episode 2",
      "synopsis": null,
      "duration_ms": 101000,
      "poster_uri": null,
      "status": "published",
      "stream_ready": false
    },
    {
      "id": null,
      "film_id": "3f6c1a52-8d0e-4b7a-9c21-5e4f2a7b9d10",
      "episode_no": 3,
      "title": null,
      "synopsis": null,
      "duration_ms": null,
      "poster_uri": null,
      "status": "planned",
      "stream_ready": false
    }
  ]
}
```

## Lỗi
| Status | `error_code` | Khi nào |
|---|---|---|
| 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 `metadata:read`. |
| 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. |
| 429 | `rate_limited` | Đã chạm rate limit của endpoint catalog và creatives. |
| 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. |

Hai response `403` không có `error_code`. Phân biệt bằng `error`: đúng bằng `org_suspended` khi tổ chức bạn bị tạm ngưng, còn chữ khác nghĩa là key thiếu `metadata:read`. Đây là trường hợp duy nhất bạn so `error`; hãy so nguyên chuỗi.

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