# Xin playback

Cấp một vé playback ngắn hạn cho một tập. Backend của bạn gọi endpoint này mỗi khi người xem bắt đầu một tập và chuyển kết quả cho Player SDK trên thiết bị của người xem.

```
POST /api/delivery/v1/playback
```

**Scope:** `playback` (tự tạo trên **Developers** → **Stream films in an app**; nếu key bị lộ, bất kỳ ai cũng stream được phim bạn được cấp phép, nên hãy thu hồi ngay)

## Mô tả

Ở mọi request, Wandura kiểm tra rằng tổ chức của bạn đang hoạt động và giấy phép streaming cho phim chứa tập đó đang hoạt động và nằm trong khung thời gian. Sau đó Wandura trả về một play token gắn với thiết bị của người xem. Các tập được cấp phép đều đã mã hoá và chỉ phát được qua Player SDK, nên bạn phải gửi các giá trị thiết bị `union_info` và `drm_kek`. Xem [Playback](https://docs.wandura.ai/vi/developers/concepts/playback).

Chỉ gọi endpoint này từ backend. Endpoint không gửi header CORS.

## 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 `playback`. |
| `Content-Type` | header | string | Nên có | `application/json`. Body phải là JSON. |
| `episode_id` | body | string (UUID) | Một trong hai: `episode_id` hoặc `film_ref` + `episode_no` | `id` của tập lấy từ catalog. Được ưu tiên nếu bạn gửi kèm `film_ref`. |
| `film_ref` | body | string | Cùng `episode_no`, nếu không có `episode_id` | `id` hoặc `slug` của phim. |
| `episode_no` | body | integer | Cùng `film_ref`, nếu không có `episode_id` | Số tập, bắt đầu từ 1. Phải là số JSON, không phải string. |
| `union_info` | body | string | Có, với tập đã mã hoá | Device ID do Player SDK tạo ra trên thiết bị của người xem. Gửi cùng `drm_kek`. |
| `drm_kek` | body | string | Có, với tập đã mã hoá | Device public key do Player SDK tạo ra. Gửi cùng `union_info`. |
| `intent` | body | string | Không | `play` khi người xem chạm vào tập, `prefetch` khi app tải tập trước khi người xem chạm. Request prefetch không tính là lượt phát. Thiếu trường này hoặc giá trị khác được ghi nhận là không xác định, không bao giờ bị từ chối, và vẫn tính là lượt phát. |
| `viewer_ref` | body | string | Không | Định danh phiên hoặc thiết bị opaque của riêng bạn, được lưu cùng vé để tra cứu khi hỗ trợ. Đừng đưa dữ liệu cá nhân vào. |

`union_info` và `drm_kek` bị từ chối với `400` `invalid_device_info` khi, sau khi bỏ khoảng trắng hai đầu, một giá trị ngắn hơn 8 ký tự hoặc là một trong `(null)`, `null`, `nil`, `undefined`, `nan`, `<null>`, `none` (chữ hoa hay thường đều tính).

## Ví dụ request

```bash
curl -s -X POST https://www.wandura.ai/api/delivery/v1/playback \
  -H "Authorization: Bearer $WANDURA_PLAYBACK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "film_ref": "the-heiress-returns",
    "episode_no": 1,
    "union_info": "<device ID from the Player SDK>",
    "drm_kek": "<device public key from the Player SDK>",
    "intent": "play",
    "viewer_ref": "u_48213:dev_9a7c"
  }'
```

## Response

`200 OK`

| Trường | Kiểu | Mô tả |
|---|---|---|
| `vid` | string | Video ID để tải trong Player SDK. |
| `play_auth_token` | string | Play token để đưa cho Player SDK. Giữ bí mật và không ghi log. |
| `expires_at` | string (ISO 8601, UTC) | Thời điểm vé hết hạn: 30 phút sau khi được cấp. |
| `protection` | string | Mức bảo vệ của nội dung bạn nhận được. `private-encrypted` với các tập được cấp phép. Chỉ bán tập trả phí khi giá trị là `private-encrypted`. |
| `subtitles` | array | Phụ đề gắn với tập. Rỗng khi chưa có phụ đề nào. |
| `subtitles[].lang` | string | Mã ngôn ngữ, ví dụ `en`, `vi` hoặc `pt-BR`. |
| `subtitles[].url` | string | URL của file phụ đề. Không gắn với thời hạn của vé. |
| `subtitles[].format` | string | Luôn là `webvtt`. |

Phụ đề được trả về theo thứ tự `lang`.

## Ví dụ response

```json
{
  "vid": "v0d2f8c1a9e4b7",
  "play_auth_token": "eyJhbGciOi...placeholder...",
  "expires_at": "2026-10-01T12:30:00.000Z",
  "protection": "private-encrypted",
  "subtitles": [
    { "lang": "en", "url": "https://cdn.example.com/subtitles/heiress-ep1-en.vtt", "format": "webvtt" },
    { "lang": "vi", "url": "https://cdn.example.com/subtitles/heiress-ep1-vi.vtt", "format": "webvtt" }
  ]
}
```

## Lỗi

| Status | `error_code` | Khi nào |
|---|---|---|
| 400 | không có | Body không phải JSON hợp lệ, hoặc không có `episode_id` cũng không có `film_ref` kèm `episode_no` dạng số. |
| 400 | `invalid_request` | Chỉ gửi một trong hai `union_info` và `drm_kek`. |
| 400 | `invalid_device_info` | Một giá trị thiết bị trông như rỗng hoặc quá ngắn. Mảng `fields` nêu tên giá trị đó. |
| 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 `playback`. |
| 403 | `not_entitled` | Tổ chức của bạn hiện không được phép stream phim này. `reason` là một trong `org_suspended`, `entitlement_not_found`, `entitlement_suspended`, `streaming_disabled`, `outside_window`. |
| 404 | `not_ready` | Phim hoặc tập không tồn tại, chưa publish, hoặc chưa sẵn sàng để stream. |
| 409 | `sdk_required` | Tập đã mã hoá và request không có giá trị thiết bị. Body còn kèm `protection`. |
| 429 | `rate_limited` | Đã chạm rate limit của playback. Xem `Retry-After`. |
| 502 | không có | Không chuẩn bị được stream hoặc không ký được play token. Retry với backoff. |
| 503 | `not_ready` | Hiện chưa có phiên bản phát được của tập. |
| 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. |
| 503 | `service_unavailable` | Nhà cung cấp video chưa trả lời kịp (`The video provider did not answer in time — retry`). Tập phim không bị thiếu: thử lại sau số giây trong `Retry-After`, có backoff. |

Ví dụ `403`:

```json
{
  "error": "outside_window",
  "error_code": "not_entitled",
  "reason": "outside_window"
}
```

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