> For the complete documentation index, see [llms.txt](https://docs.firework.com/firework-for-developers/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.firework.com/firework-for-developers/api/livestreams.md).

# Livestreams

### 1. Overview

The Firework Livestream API lets you manage livestreams end to end. The public API supports the following workflows:

* **Create**: Schedule a single livestream through a public API endpoint
* **Read**: Fetch livestream details, current status, schedule, and thumbnail image
* **Update**: Modify mutable livestream metadata, schedule, trailer, and tagged products
* **Write**: Pin or unpin products in real time and force-end live sessions when needed
* **Export**: Download post-event interaction results and livestream comments
* **List & report**: List a channel's livestreams, page live/replay chat messages, and pull consolidated reporting stats

Use these endpoints to create interactive commerce moments while keeping operational control of every broadcast.

**Base URL**: `https://api.firework.com`

### 2. Authentication

The Firework Livestream API uses OAuth 2.0 for authentication. Before using this API, you must obtain an access token.

**Authentication Method:**

* **Client Credentials**: OAuth 2.0 Client Credentials flow for server-to-server authentication (machine-to-machine)

> 📖 **Documentation:**
>
> * [Client Credentials OAuth](/firework-for-developers/api/authentication.md) - Server-to-server authentication for OAuth apps

***

### 3. Endpoint Summary

| Endpoint                                                                                | Scope               | Notes                                              |
| --------------------------------------------------------------------------------------- | ------------------- | -------------------------------------------------- |
| `POST /api/v1/live_streams`                                                             | `livestreams:write` | Create a single scheduled livestream               |
| `GET /api/v1/live_streams`                                                              | `livestreams:read`  | List a channel's primary livestreams (cursor)      |
| `GET /api/v1/live_streams/{live_stream_id}`                                             | `livestreams:read`  | Get livestream info and export interaction IDs     |
| `GET /api/v1/live_streams/{live_stream_id}/products`                                    | `livestreams:read`  | Get the complete configured product snapshot       |
| `GET /api/v1/live_streams/{live_stream_id}/messages`                                    | `livestreams:read`  | List live/replay chat messages (cursor)            |
| `PATCH /api/v1/live_streams/{live_stream_id}`                                           | `livestreams:write` | Update mutable livestream fields                   |
| `POST /api/v1/live_streams/{id}/pin_product`                                            | `livestreams:write` | Pin products during livestream                     |
| `POST /api/v1/live_streams/{id}/unpin_product`                                          | `livestreams:write` | Unpin products during livestream                   |
| `PATCH /api/v1/live_streams/{id}/end`                                                   | `livestreams:write` | Force end an active livestream                     |
| `GET /api/v1/live_streams/{live_stream_id}/interactions/{interaction_id}/responses_csv` | `livestreams:read`  | Download interaction response results as CSV       |
| `GET /api/v1/live_streams/{live_stream_id}/comments_csv`                                | `livestreams:read`  | Download livestream comments as CSV                |
| `GET /api/v1/stats/live_streams/{live_stream_id}`                                       | `livestreams:read`  | Consolidated reporting stats (meta/live/replay)    |
| `GET /api/v1/live_streams/{live_stream_id}/detail`                                      | `livestreams:read`  | Viewer-side livestream details (legacy)            |
| `GET /api/v1/live_streams/playlists/{playlist_id}/videos`                               | `livestreams:read`  | List a livestream playlist's videos (legacy, page) |

***

### 4. Create Livestream

Schedule a single livestream through the public API.

> **Scope of v1**: This endpoint is intentionally single-create only. If you need to create many livestreams, call this endpoint multiple times and respect the documented rate limit. A bulk/job-based create API is not part of this version of the spec.

**Endpoint**: `POST /api/v1/live_streams` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:write` (for OAuth apps) **Rate Limit**: 20 create requests per 5 minutes per channel **Content Type**: `application/json` **Status**: Implemented

> **Scope note**: Creating the livestream requires `livestreams:write`. If you want to upload a custom trailer and pass it as `s3_key`, the upload step uses the public video upload APIs and requires `videos:write`.

#### 4.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |
| `Content-Type`  | Must be `application/json`            | ✅        |

#### 4.2. Request Body

| Field                          | Type          | Required | Default                  | Description                                                                           |
| ------------------------------ | ------------- | -------- | ------------------------ | ------------------------------------------------------------------------------------- |
| `caption`                      | string        | ✅        | None                     | Livestream title shown to viewers                                                     |
| `channel_id`                   | string        | ✅        | None                     | Encoded channel ID where the livestream will be created                               |
| `description`                  | string        | ❌        | None                     | Livestream description                                                                |
| `scheduled_at`                 | string        | ✅        | None                     | ISO 8601 datetime when the livestream is scheduled to start                           |
| `time_zone`                    | string        | ❌        | None                     | IANA timezone identifier for the scheduled event, for example `America/New_York`      |
| `stream_source`                | string        | ✅        | None                     | Source of the livestream. Supported values for v1: `"fw"` and `"external"`            |
| `s3_key`                       | string        | ❌        | Firework default trailer | Pre-uploaded trailer key to use while the livestream is idle                          |
| `business_store_id`            | string        | ❌        | Use first one            | Encoded business store ID used to resolve `product_ids`                               |
| `product_ids`                  | string\[]     | ❌        | `[]`                     | Initial products to tag to the livestream trailer/video                               |
| `access_code`                  | string / null | ❌        | `null`                   | Access code used to lock the video/livestream. Use `null` to leave it unlocked        |
| `announcement`                 | string / null | ❌        | `null`                   | Announcement banner text shown to viewers                                             |
| `auto_response`                | string        | ❌        | `"disabled"`             | Chat auto-response mode. Supported values: `"always_on"`, `"moderated"`, `"disabled"` |
| `chat_enabled`                 | boolean       | ❌        | `true`                   | Whether live chat is enabled                                                          |
| `chat_in_replay_enabled`       | boolean       | ❌        | `true`                   | Whether chat messages are displayed during replay                                     |
| `chat_moderation_enabled`      | boolean       | ❌        | `false`                  | Whether chat moderation is enabled                                                    |
| `hearts_count_enabled`         | boolean       | ❌        | `false`                  | Whether like count is shown                                                           |
| `fomo_product_viewing_enabled` | boolean       | ❌        | `false`                  | Whether real-time product-viewing FOMO cues are shown                                 |
| `replay_enabled`               | boolean       | ❌        | `true`                   | Whether replay is available after the livestream ends                                 |
| `replay_interactions_enabled`  | boolean       | ❌        | `true`                   | Whether replay interactions are enabled                                               |
| `replay_messaging_enabled`     | boolean       | ❌        | `false`                  | Whether viewers can post messages during replay                                       |
| `replay_viewers_count_enabled` | boolean       | ❌        | `false`                  | Whether viewer count is shown during replay                                           |
| `trailer_announcement_enabled` | boolean       | ❌        | `false`                  | Whether the announcement is shown while viewers watch the trailer                     |
| `trailer_messaging_enabled`    | boolean       | ❌        | `false`                  | Whether viewers can post messages while watching the trailer                          |
| `transcription_enabled`        | boolean       | ❌        | `false`                  | Whether live transcription is enabled, when supported by the provider/source          |
| `viewers_count_enabled`        | boolean       | ❌        | `true`                   | Whether viewer count is shown                                                         |
| `viewers_count_mode`           | string        | ❌        | `"accumulated"`          | Viewer-count mode. Supported values: `"accumulated"` and `"concurrent"`               |

**Request Rules:**

* If `s3_key` is omitted, Firework uses the default livestream trailer.
* If you want a custom trailer, upload it first using the public upload APIs documented in [Firework Video API - Public](/firework-for-developers/api/videos.md), then pass the returned `key` value as `s3_key`.
* `product_ids` follows the same identifier rules as `POST /api/v1/videos`.
* `scheduled_at` should be provided in UTC ISO 8601 format.
* `stream_source` is required at create time. It can only be updated before the livestream enters the 5-minute warmup window.
* `stream_source` can only be assigned when the livestream is scheduled more than 5 minutes in the future.
* Livestream instance settings do not update channel/feed-level display settings or AI copilot setup.

**How to get `s3_key`:**

1. Call `POST /api/v1/upload_signatures` for a single-request upload, or `POST /api/v1/upload_multipart/signatures` followed by `POST /api/v1/upload_multipart/complete` for multipart upload.
2. Upload the trailer file directly to S3 using the returned signature data.
3. Take the `key` from the upload API response.
4. Pass that exact value as `s3_key` in `POST /api/v1/live_streams`.

#### 4.3. Create Livestream Response

**Success Response**: `201 Created`

| Field                          | Type    | Nullable | Description                                                            |
| ------------------------------ | ------- | -------- | ---------------------------------------------------------------------- |
| `id`                           | string  | ❌        | Unique encoded identifier of the livestream                            |
| `provider`                     | string  | ❌        | Streaming provider selected for the livestream                         |
| `status`                       | string  | ❌        | Initial livestream state. Expected value: `idle`                       |
| `event_name`                   | string  | ❌        | Name of the livestream event                                           |
| `event_description`            | string  | ✅        | Description of the livestream event                                    |
| `thumbnail_url`                | string  | ✅        | URL of the livestream thumbnail image                                  |
| `has_access_code`              | boolean | ❌        | Whether the video/livestream is locked by an access code               |
| `announcement`                 | string  | ✅        | Announcement banner text                                               |
| `auto_response`                | string  | ❌        | Chat auto-response mode                                                |
| `chat_enabled`                 | boolean | ❌        | Whether live chat is enabled                                           |
| `chat_in_replay_enabled`       | boolean | ❌        | Whether chat messages are displayed during replay                      |
| `chat_moderation_enabled`      | boolean | ❌        | Whether chat moderation is enabled                                     |
| `hearts_count_enabled`         | boolean | ❌        | Whether like count is shown                                            |
| `fomo_product_viewing_enabled` | boolean | ❌        | Whether real-time product-viewing FOMO cues are shown                  |
| `replay_enabled`               | boolean | ❌        | Whether replay is available after the livestream ends                  |
| `replay_interactions_enabled`  | boolean | ❌        | Whether replay interactions are enabled                                |
| `replay_messaging_enabled`     | boolean | ❌        | Whether viewers can post messages during replay                        |
| `replay_viewers_count_enabled` | boolean | ❌        | Whether viewer count is shown during replay                            |
| `trailer_announcement_enabled` | boolean | ❌        | Whether the announcement is shown while viewers watch the trailer      |
| `trailer_messaging_enabled`    | boolean | ❌        | Whether viewers can post messages while watching the trailer           |
| `transcription_enabled`        | boolean | ❌        | Whether live transcription is enabled                                  |
| `viewers_count_enabled`        | boolean | ❌        | Whether viewer count is shown                                          |
| `viewers_count_mode`           | string  | ❌        | Viewer-count mode: `"accumulated"` or `"concurrent"`                   |
| `scheduled_at`                 | string  | ✅        | ISO 8601 datetime when livestream is scheduled to start                |
| `started_at`                   | string  | ✅        | ISO 8601 datetime when livestream actually started                     |
| `ended_at`                     | string  | ✅        | ISO 8601 datetime when livestream ended                                |
| `stream_key`                   | string  | ✅        | Publish stream key. Returned for `stream_source: "external"` on create |
| `stream_url`                   | string  | ✅        | Publish RTMP URL. Returned for `stream_source: "external"` on create   |

> **Note**: The create response mostly matches `GET /api/v1/live_streams/{live_stream_id}`. It additionally includes `provider`, and for `stream_source: "external"` it also returns `stream_key` and `stream_url` so clients can publish to the scheduled livestream.

#### 4.4. Create Livestream Error Responses

| Status Code                | Description                                                                                                                |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | Invalid request body, malformed datetime, unsupported `stream_source`, invalid `s3_key`, or maintenance window restriction |
| `401 Unauthorized`         | Invalid or missing authentication token                                                                                    |
| `402 Payment Required`     | Live events are not enabled for the business                                                                               |
| `403 Forbidden`            | The authenticated user or OAuth app does not have access to the channel                                                    |
| `404 Not Found`            | Channel not found                                                                                                          |
| `422 Unprocessable Entity` | Domain validation error from changesets, for example livestream scheduling constraints                                     |
| `429 Too Many Requests`    | Rate limit exceeded (20 create requests per 5 minutes per channel)                                                         |

**Error Response Format**:

Simple request/auth errors return:

```json
{
  "error": "Error Message"
}
```

Validation errors from changesets return:

```json
{
  "errors": {
    "field_name": ["validation message"]
  }
}
```

#### 4.5. Examples

**Example 1: Create Livestream with Default Trailer**

```bash
curl -X POST "https://api.firework.com/api/v1/live_streams" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "X6Xqd6W",
    "caption": "Summer Sale Livestream",
    "description": "Join us for our scheduled commerce event",
    "scheduled_at": "2026-07-20T10:00:00.000000Z",
    "stream_source": "fw",
    "time_zone": "America/New_York"
  }'
```

**Example 2: Create Livestream with Custom Trailer**

```bash
curl -X POST "https://api.firework.com/api/v1/live_streams" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "X6Xqd6W",
    "caption": "Bajaj Daily Livestream",
    "scheduled_at": "2026-07-20T12:30:00.000000Z",
    "stream_source": "external",
    "s3_key": "medias/business/AbCdEfG/channel/HiJkLmN/videos/public/original/1738152000-abcdefgh-trailer.mp4",
    "product_ids": ["GeEk8y", "ABC123"]
  }'
```

**Example 3: Create Livestream with Display Settings**

```bash
curl -X POST "https://api.firework.com/api/v1/live_streams" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "X6Xqd6W",
    "caption": "Private VIP Livestream",
    "scheduled_at": "2026-07-20T12:30:00.000000Z",
    "stream_source": "fw",
    "access_code": "VIP123",
    "announcement": "Doors open in 5 minutes",
    "replay_enabled": true,
    "replay_viewers_count_enabled": true,
    "trailer_announcement_enabled": true,
    "viewers_count_enabled": true,
    "viewers_count_mode": "accumulated",
    "hearts_count_enabled": false,
    "fomo_product_viewing_enabled": false,
    "transcription_enabled": true
  }'
```

**Example Response**

```json
{
  "id": "616dOp",
  "provider": "ivs",
  "status": "idle",
  "event_name": "Summer Sale Livestream",
  "event_description": "Join us for our scheduled commerce event",
  "thumbnail_url": "https://cdn.firework.com/live_streams/616dOp/thumbnail.jpg",
  "has_access_code": false,
  "announcement": null,
  "auto_response": "disabled",
  "chat_enabled": true,
  "chat_in_replay_enabled": true,
  "chat_moderation_enabled": false,
  "hearts_count_enabled": false,
  "fomo_product_viewing_enabled": false,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_enabled": false,
  "replay_viewers_count_enabled": false,
  "trailer_announcement_enabled": false,
  "trailer_messaging_enabled": false,
  "transcription_enabled": false,
  "viewers_count_enabled": true,
  "viewers_count_mode": "accumulated",
  "scheduled_at": "2026-07-20T10:00:00.000000Z",
  "started_at": null,
  "ended_at": null,
  "stream_key": null,
  "stream_url": null
}
```

***

### 5. Get Livestream Info

Retrieve information about a specific livestream room including its current status, event details, scheduling information, and thumbnail image.

> **Note**: This is the endpoint for fetching livestream info.

**Endpoint**: `GET /api/v1/live_streams/{live_stream_id}` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:read` (for OAuth apps)

#### 5.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |

#### 5.2. Path Parameters

| Parameter        | Type   | Required | Description                             |
| ---------------- | ------ | -------- | --------------------------------------- |
| `live_stream_id` | string | ✅        | The unique identifier of the livestream |

#### 5.3. Get Livestream Response

**Success Response**: `200 OK`

| Field                          | Type      | Nullable | Description                                                       |
| ------------------------------ | --------- | -------- | ----------------------------------------------------------------- |
| `id`                           | string    | ❌        | Unique encoded identifier of the livestream                       |
| `status`                       | string    | ❌        | Current livestream state (see Status Values below)                |
| `event_name`                   | string    | ❌        | Name of the livestream event                                      |
| `event_description`            | string    | ✅        | Description of the livestream event                               |
| `thumbnail_url`                | string    | ✅        | URL of the livestream thumbnail image                             |
| `has_access_code`              | boolean   | ❌        | Whether the video/livestream is locked by an access code          |
| `announcement`                 | string    | ✅        | Announcement banner text                                          |
| `auto_response`                | string    | ❌        | Chat auto-response mode                                           |
| `chat_enabled`                 | boolean   | ❌        | Whether live chat is enabled                                      |
| `chat_in_replay_enabled`       | boolean   | ❌        | Whether chat messages are displayed during replay                 |
| `chat_moderation_enabled`      | boolean   | ❌        | Whether chat moderation is enabled                                |
| `hearts_count_enabled`         | boolean   | ❌        | Whether like count is shown                                       |
| `fomo_product_viewing_enabled` | boolean   | ❌        | Whether real-time product-viewing FOMO cues are shown             |
| `replay_enabled`               | boolean   | ❌        | Whether replay is available after the livestream ends             |
| `replay_interactions_enabled`  | boolean   | ❌        | Whether replay interactions are enabled                           |
| `replay_messaging_enabled`     | boolean   | ❌        | Whether viewers can post messages during replay                   |
| `replay_viewers_count_enabled` | boolean   | ❌        | Whether viewer count is shown during replay                       |
| `trailer_announcement_enabled` | boolean   | ❌        | Whether the announcement is shown while viewers watch the trailer |
| `trailer_messaging_enabled`    | boolean   | ❌        | Whether viewers can post messages while watching the trailer      |
| `transcription_enabled`        | boolean   | ❌        | Whether live transcription is enabled                             |
| `viewers_count_enabled`        | boolean   | ❌        | Whether viewer count is shown                                     |
| `viewers_count_mode`           | string    | ❌        | Viewer-count mode: `"accumulated"` or `"concurrent"`              |
| `scheduled_at`                 | string    | ✅        | ISO 8601 datetime when livestream is scheduled to start           |
| `started_at`                   | string    | ✅        | ISO 8601 datetime when livestream actually started                |
| `ended_at`                     | string    | ✅        | ISO 8601 datetime when livestream ended                           |
| `interactions`                 | object\[] | ❌        | Non-deleted livestream interactions                               |

Each `interactions` item includes:

| Field                        | Type           | Description                                   |
| ---------------------------- | -------------- | --------------------------------------------- |
| `id`                         | string         | Interaction ID                                |
| `interaction_type`           | string         | Interaction type                              |
| `prompt`                     | string / null  | Prompt shown to viewers, when applicable      |
| `interactive_url`            | string / null  | URL used by link-style interactions           |
| `terms_and_conditions_url`   | string / null  | Terms URL configured for the interaction      |
| `terms_acceptance_required`  | boolean        | Whether terms acceptance is required          |
| `giveaway_celebration_emoji` | string         | Giveaway celebration emoji                    |
| `giveaway_description`       | string / null  | Giveaway description                          |
| `giveaway_winners_count`     | integer / null | Configured winner count                       |
| `collect_name`               | boolean        | Whether the interaction collects viewer name  |
| `collect_email`              | boolean        | Whether the interaction collects viewer email |
| `collect_phone`              | boolean        | Whether the interaction collects viewer phone |
| `sort_id`                    | integer / null | Display order                                 |
| `options`                    | object\[]      | Interaction options                           |
| `option_sets`                | object\[]      | Trivia/question option sets                   |
| `video_id`                   | string / null  | Video ID when linked to a video               |
| `live_stream_id`             | string         | Livestream ID                                 |
| `tally`                      | object / null  | Aggregate counts, when applicable             |
| `interaction_results`        | object\[]      | Always empty; use CSV export for result rows  |

The `interactions` array follows the livestream detail interaction shape and is used to discover `interaction_id` values for `responses_csv`. CSV URLs are not returned in this response.

**Status Values**

| Status      | Description                        |
| ----------- | ---------------------------------- |
| `idle`      | Livestream not yet started         |
| `active`    | Livestream currently in progress   |
| `paused`    | Livestream paused by host          |
| `replay`    | Livestream ended, replay available |
| `completed` | Replay window ended                |
| `expired`   | Livestream never started           |

#### 5.4. Get Livestream Error Responses

| Status Code        | Description                                                                  |
| ------------------ | ---------------------------------------------------------------------------- |
| `400 Bad Request`  | Invalid request format or malformed livestream ID                            |
| `401 Unauthorized` | Invalid or missing authentication token, or access denied to this livestream |
| `404 Not Found`    | Livestream not found                                                         |

**Error Response Format**:

```json
{
  "error": "Error Message"
}
```

#### 5.5. Examples

**CURL Request**

```bash
curl -X GET "https://api.firework.com/api/v1/live_streams/616dOp" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Example Response (Scheduled Livestream)**

```json
{
  "id": "616dOp",
  "status": "idle",
  "event_name": "Product Launch Livestream",
  "event_description": "Join us for our exciting new product reveal!",
  "thumbnail_url": "https://cdn.firework.com/live_streams/616dOp/thumbnail.jpg",
  "has_access_code": false,
  "announcement": null,
  "auto_response": "disabled",
  "chat_enabled": true,
  "chat_in_replay_enabled": true,
  "chat_moderation_enabled": false,
  "hearts_count_enabled": false,
  "fomo_product_viewing_enabled": false,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_enabled": false,
  "replay_viewers_count_enabled": false,
  "trailer_announcement_enabled": false,
  "trailer_messaging_enabled": false,
  "transcription_enabled": false,
  "viewers_count_enabled": true,
  "viewers_count_mode": "accumulated",
  "scheduled_at": "2025-03-19T09:41:00.000000Z",
  "started_at": null,
  "ended_at": null,
  "interactions": []
}
```

**Example Response (Active Livestream)**

```json
{
  "id": "616dOp",
  "status": "active",
  "event_name": "Product Launch Livestream",
  "event_description": "Join us for our exciting new product reveal!",
  "thumbnail_url": "https://cdn.firework.com/live_streams/616dOp/thumbnail.jpg",
  "has_access_code": false,
  "announcement": null,
  "auto_response": "disabled",
  "chat_enabled": true,
  "chat_in_replay_enabled": true,
  "chat_moderation_enabled": false,
  "hearts_count_enabled": false,
  "fomo_product_viewing_enabled": false,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_enabled": false,
  "replay_viewers_count_enabled": false,
  "trailer_announcement_enabled": false,
  "trailer_messaging_enabled": false,
  "transcription_enabled": false,
  "viewers_count_enabled": true,
  "viewers_count_mode": "accumulated",
  "scheduled_at": "2025-03-19T09:41:00.000000Z",
  "started_at": "2025-03-19T09:42:15.000000Z",
  "ended_at": null,
  "interactions": []
}
```

**Example Response (Completed Livestream)**

```json
{
  "id": "616dOp",
  "status": "replay",
  "event_name": "Product Launch Livestream",
  "event_description": "Join us for our exciting new product reveal!",
  "thumbnail_url": "https://cdn.firework.com/live_streams/616dOp/thumbnail.jpg",
  "has_access_code": false,
  "announcement": null,
  "auto_response": "disabled",
  "chat_enabled": true,
  "chat_in_replay_enabled": true,
  "chat_moderation_enabled": false,
  "hearts_count_enabled": false,
  "fomo_product_viewing_enabled": false,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_enabled": false,
  "replay_viewers_count_enabled": false,
  "trailer_announcement_enabled": false,
  "trailer_messaging_enabled": false,
  "transcription_enabled": false,
  "viewers_count_enabled": true,
  "viewers_count_mode": "accumulated",
  "scheduled_at": "2025-03-19T09:41:00.000000Z",
  "started_at": "2025-03-19T09:42:15.000000Z",
  "ended_at": "2025-03-19T11:30:00.000000Z",
  "interactions": [
    {
      "id": "7YDaNq",
      "interaction_type": "giveaway",
      "prompt": "Enter your email for the launch giveaway",
      "interactive_url": null,
      "terms_and_conditions_url": null,
      "terms_acceptance_required": false,
      "giveaway_celebration_emoji": "🎉",
      "giveaway_description": null,
      "giveaway_winners_count": null,
      "collect_name": false,
      "collect_email": true,
      "collect_phone": false,
      "sort_id": 1,
      "options": [],
      "option_sets": [],
      "video_id": null,
      "live_stream_id": "616dOp",
      "tally": null,
      "interaction_results": []
    },
    {
      "id": "2wK9Qa",
      "interaction_type": "poll",
      "prompt": "Which product should we demo next?",
      "interactive_url": null,
      "terms_and_conditions_url": null,
      "terms_acceptance_required": false,
      "giveaway_celebration_emoji": "🎉",
      "giveaway_description": null,
      "giveaway_winners_count": null,
      "collect_name": false,
      "collect_email": false,
      "collect_phone": false,
      "sort_id": 2,
      "options": [
        {
          "text": "Vacuum",
          "confetti": null,
          "is_correct": null,
          "entry_condition": null,
          "keyword": null
        },
        {
          "text": "Air purifier",
          "confetti": null,
          "is_correct": null,
          "entry_condition": null,
          "keyword": null
        }
      ],
      "option_sets": [],
      "video_id": null,
      "live_stream_id": "616dOp",
      "tally": {
        "Vacuum": 257,
        "Air purifier": 166
      },
      "interaction_results": []
    }
  ]
}
```

***

### 6. Update Livestream

Update mutable fields on an existing livestream.

> **Scope of v1**: This endpoint updates one livestream at a time. It is intended for metadata, display/settings, schedule, source, trailer, and tagged-product updates. It does not update live session state, streaming provider configuration, channel/feed-level settings, AI copilot setup, or real-time pinned products.

**Endpoint**: `PATCH /api/v1/live_streams/{live_stream_id}` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:write` (for OAuth apps) **Content Type**: `application/json` **Status**: Implemented

> **Scope note**: Updating `s3_key` requires a trailer file that was already uploaded through the public video upload APIs. The upload step requires `videos:write`.

#### 6.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |
| `Content-Type`  | Must be `application/json`            | ✅        |

#### 6.2. Path Parameters

| Parameter        | Type   | Required | Description                             |
| ---------------- | ------ | -------- | --------------------------------------- |
| `live_stream_id` | string | ✅        | The unique identifier of the livestream |

#### 6.3. Request Body

All fields are optional, but the request body must include at least one supported field.

| Field                          | Type          | Required | Before 5-minute warmup | After 5-minute warmup | Description                                                                                      |
| ------------------------------ | ------------- | -------- | ---------------------- | --------------------- | ------------------------------------------------------------------------------------------------ |
| `caption`                      | string        | ❌        | ✅                      | ✅                     | New livestream title shown to viewers                                                            |
| `description`                  | string / null | ❌        | ✅                      | ✅                     | New livestream description. Use `null` to clear the description                                  |
| `access_code`                  | string / null | ❌        | ✅                      | ✅                     | Access code used to lock the video/livestream. Use `null` to unlock                              |
| `announcement`                 | string / null | ❌        | ✅                      | ✅                     | Announcement banner text shown to viewers. Use `null` or an empty string to clear it             |
| `auto_response`                | string        | ❌        | ✅                      | ✅                     | Chat auto-response mode. Supported values: `"always_on"`, `"moderated"`, `"disabled"`            |
| `chat_enabled`                 | boolean       | ❌        | ✅                      | ✅                     | Whether live chat is enabled                                                                     |
| `chat_in_replay_enabled`       | boolean       | ❌        | ✅                      | ✅                     | Whether chat messages are displayed during replay                                                |
| `chat_moderation_enabled`      | boolean       | ❌        | ✅                      | ✅                     | Whether chat moderation is enabled                                                               |
| `hearts_count_enabled`         | boolean       | ❌        | ✅                      | ✅                     | Whether like count is shown                                                                      |
| `fomo_product_viewing_enabled` | boolean       | ❌        | ✅                      | ✅                     | Whether real-time product-viewing FOMO cues are shown                                            |
| `replay_enabled`               | boolean       | ❌        | ✅                      | ✅                     | Whether replay is available after the livestream ends                                            |
| `replay_interactions_enabled`  | boolean       | ❌        | ✅                      | ✅                     | Whether replay interactions are enabled                                                          |
| `replay_messaging_enabled`     | boolean       | ❌        | ✅                      | ✅                     | Whether viewers can post messages during replay                                                  |
| `replay_viewers_count_enabled` | boolean       | ❌        | ✅                      | ✅                     | Whether viewer count is shown during replay                                                      |
| `trailer_announcement_enabled` | boolean       | ❌        | ✅                      | ✅                     | Whether the announcement is shown while viewers watch the trailer                                |
| `trailer_messaging_enabled`    | boolean       | ❌        | ✅                      | ✅                     | Whether viewers can post messages while watching the trailer                                     |
| `transcription_enabled`        | boolean       | ❌        | ✅                      | ✅                     | Whether live transcription is enabled, when supported by the provider/source                     |
| `viewers_count_enabled`        | boolean       | ❌        | ✅                      | ✅                     | Whether viewer count is shown                                                                    |
| `viewers_count_mode`           | string        | ❌        | ✅                      | ✅                     | Viewer-count mode. Supported values: `"accumulated"` and `"concurrent"`                          |
| `scheduled_at`                 | string        | ❌        | ✅                      | ❌                     | New UTC ISO 8601 datetime when the livestream is scheduled to start                              |
| `time_zone`                    | string / null | ❌        | ✅                      | ❌                     | IANA timezone identifier for the scheduled event, for example `America/New_York`                 |
| `stream_source`                | string        | ❌        | ✅                      | ❌                     | Source of the livestream. Supported values for v1: `"fw"` and `"external"`                       |
| `s3_key`                       | string        | ❌        | ✅                      | ❌                     | Pre-uploaded trailer key to replace the idle trailer                                             |
| `business_store_id`            | string        | ❌        | ✅                      | ❌                     | Encoded business store ID used to resolve `product_ids`                                          |
| `product_ids`                  | string\[]     | ❌        | ✅                      | ❌                     | Full replacement list of products tagged to the livestream trailer/video. Use `[]` to remove all |

**Request Rules:**

* This is a partial update. Omitted fields keep their current values.
* The 5-minute warmup window starts 5 minutes before `scheduled_at`.
* `caption`, `description`, `access_code`, and livestream instance settings can be updated before or after the livestream enters the warmup window.
* `scheduled_at`, `time_zone`, `stream_source`, `s3_key`, `business_store_id`, and `product_ids` can only be updated before the livestream enters the 5-minute warmup window.
* Livestream instance settings do not update channel/feed-level display settings or AI copilot setup.
* `scheduled_at` must be provided in UTC ISO 8601 format and must satisfy the same scheduling and maintenance-window restrictions as create.
* `stream_source` can only be changed when the resulting scheduled time is more than 5 minutes in the future.
* `s3_key` must be a valid uploaded video key owned by the target channel.
* `product_ids` replaces the full tagged-product list for the livestream trailer/video. Use the pin and unpin endpoints to change real-time highlighted products during an active livestream.
* `product_ids` follows the same identifier rules as `POST /api/v1/videos` and `POST /api/v1/live_streams`.
* `channel_id`, `provider`, `stream_key`, `stream_url`, `status`, `started_at`, and `ended_at` cannot be updated through this endpoint.

#### 6.4. Update Livestream Response

**Success Response**: `200 OK`

The response matches `GET /api/v1/live_streams/{live_stream_id}`. If the resulting livestream uses `stream_source: "external"`, the response also includes publish credentials.

| Field                          | Type    | Nullable | Description                                                                                 |
| ------------------------------ | ------- | -------- | ------------------------------------------------------------------------------------------- |
| `id`                           | string  | ❌        | Unique encoded identifier of the livestream                                                 |
| `status`                       | string  | ❌        | Current livestream state                                                                    |
| `event_name`                   | string  | ❌        | Name of the livestream event                                                                |
| `event_description`            | string  | ✅        | Description of the livestream event                                                         |
| `thumbnail_url`                | string  | ✅        | URL of the livestream thumbnail image                                                       |
| `has_access_code`              | boolean | ❌        | Whether the video/livestream is locked by an access code                                    |
| `announcement`                 | string  | ✅        | Announcement banner text                                                                    |
| `auto_response`                | string  | ❌        | Chat auto-response mode                                                                     |
| `chat_enabled`                 | boolean | ❌        | Whether live chat is enabled                                                                |
| `chat_in_replay_enabled`       | boolean | ❌        | Whether chat messages are displayed during replay                                           |
| `chat_moderation_enabled`      | boolean | ❌        | Whether chat moderation is enabled                                                          |
| `hearts_count_enabled`         | boolean | ❌        | Whether like count is shown                                                                 |
| `fomo_product_viewing_enabled` | boolean | ❌        | Whether real-time product-viewing FOMO cues are shown                                       |
| `replay_enabled`               | boolean | ❌        | Whether replay is available after the livestream ends                                       |
| `replay_interactions_enabled`  | boolean | ❌        | Whether replay interactions are enabled                                                     |
| `replay_messaging_enabled`     | boolean | ❌        | Whether viewers can post messages during replay                                             |
| `replay_viewers_count_enabled` | boolean | ❌        | Whether viewer count is shown during replay                                                 |
| `trailer_announcement_enabled` | boolean | ❌        | Whether the announcement is shown while viewers watch the trailer                           |
| `trailer_messaging_enabled`    | boolean | ❌        | Whether viewers can post messages while watching the trailer                                |
| `transcription_enabled`        | boolean | ❌        | Whether live transcription is enabled                                                       |
| `viewers_count_enabled`        | boolean | ❌        | Whether viewer count is shown                                                               |
| `viewers_count_mode`           | string  | ❌        | Viewer-count mode: `"accumulated"` or `"concurrent"`                                        |
| `scheduled_at`                 | string  | ✅        | ISO 8601 datetime when livestream is scheduled to start                                     |
| `started_at`                   | string  | ✅        | ISO 8601 datetime when livestream actually started                                          |
| `ended_at`                     | string  | ✅        | ISO 8601 datetime when livestream ended                                                     |
| `stream_key`                   | string  | ✅        | Publish stream key. Returned when the resulting livestream uses `stream_source: "external"` |
| `stream_url`                   | string  | ✅        | Publish RTMP URL. Returned when the resulting livestream uses `stream_source: "external"`   |

#### 6.5. Update Livestream Error Responses

| Status Code                | Description                                                                                                            |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | Invalid request body, no supported fields, malformed datetime, invalid `s3_key`, immutable field, or maintenance block |
| `401 Unauthorized`         | Invalid or missing authentication token                                                                                |
| `403 Forbidden`            | The authenticated user or OAuth app does not have access to the livestream                                             |
| `404 Not Found`            | Livestream not found                                                                                                   |
| `422 Unprocessable Entity` | Domain validation error, invalid products, or an update attempted outside the allowed livestream lifecycle             |

**Error Response Format**:

Simple request/auth errors return:

```json
{
  "error": "Error Message"
}
```

Validation errors from changesets return:

```json
{
  "errors": {
    "field_name": ["validation message"]
  }
}
```

#### 6.6. Examples

**Example 1: Update Livestream Metadata and Schedule**

```bash
curl -X PATCH "https://api.firework.com/api/v1/live_streams/616dOp" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "caption": "Summer Sale Livestream - Encore",
    "description": "Updated schedule and event details",
    "scheduled_at": "2026-07-21T10:00:00.000000Z",
    "time_zone": "America/New_York"
  }'
```

**Example 2: Update Stream Source, Trailer, and Tagged Products**

```bash
curl -X PATCH "https://api.firework.com/api/v1/live_streams/616dOp" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "stream_source": "external",
    "s3_key": "medias/business/AbCdEfG/channel/HiJkLmN/videos/public/original/1738152000-abcdefgh-trailer-v2.mp4",
    "business_store_id": "QwErTy",
    "product_ids": ["GeEk8y", "ABC123"]
  }'
```

**Example 3: Update Display Settings**

```bash
curl -X PATCH "https://api.firework.com/api/v1/live_streams/616dOp" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "replay_enabled": true,
    "replay_viewers_count_enabled": true,
    "trailer_announcement_enabled": true,
    "viewers_count_enabled": true,
    "viewers_count_mode": "accumulated",
    "hearts_count_enabled": false,
    "fomo_product_viewing_enabled": false,
    "transcription_enabled": true,
    "access_code": "VIP123",
    "announcement": "Doors open in 5 minutes"
  }'
```

**Example Response (Metadata and Schedule Update)**

```json
{
  "id": "616dOp",
  "status": "idle",
  "event_name": "Summer Sale Livestream - Encore",
  "event_description": "Updated schedule and event details",
  "thumbnail_url": "https://cdn.firework.com/live_streams/616dOp/thumbnail.jpg",
  "has_access_code": false,
  "announcement": null,
  "auto_response": "disabled",
  "chat_enabled": true,
  "chat_in_replay_enabled": true,
  "chat_moderation_enabled": false,
  "hearts_count_enabled": false,
  "fomo_product_viewing_enabled": false,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_enabled": false,
  "replay_viewers_count_enabled": false,
  "trailer_announcement_enabled": false,
  "trailer_messaging_enabled": false,
  "transcription_enabled": false,
  "viewers_count_enabled": true,
  "viewers_count_mode": "accumulated",
  "scheduled_at": "2026-07-21T10:00:00.000000Z",
  "started_at": null,
  "ended_at": null
}
```

**Example Response (External Stream Source Update)**

```json
{
  "id": "616dOp",
  "status": "idle",
  "event_name": "Summer Sale Livestream",
  "event_description": "Join us for our scheduled commerce event",
  "thumbnail_url": "https://cdn.firework.com/live_streams/616dOp/thumbnail.jpg",
  "has_access_code": false,
  "announcement": null,
  "auto_response": "disabled",
  "chat_enabled": true,
  "chat_in_replay_enabled": true,
  "chat_moderation_enabled": false,
  "hearts_count_enabled": false,
  "fomo_product_viewing_enabled": false,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_enabled": false,
  "replay_viewers_count_enabled": false,
  "trailer_announcement_enabled": false,
  "trailer_messaging_enabled": false,
  "transcription_enabled": false,
  "viewers_count_enabled": true,
  "viewers_count_mode": "accumulated",
  "scheduled_at": "2026-07-20T10:00:00.000000Z",
  "started_at": null,
  "ended_at": null,
  "stream_key": "sk_live_abc123",
  "stream_url": "rtmps://global-live.mux.com:443/app"
}
```

#### 6.7. Error Responses

```json
// 400 Bad Request - No supported fields
{
  "error": "At least one supported field is required"
}

// 400 Bad Request - Immutable field
{
  "error": "status cannot be updated"
}

// 422 Unprocessable Entity - Livestream is too close to start
{
  "error": "scheduled_at, time_zone, stream_source, s3_key, business_store_id, product_ids can only be updated before the warmup window"
}

// 422 Unprocessable Entity - stream_source update too close to start
{
  "error": "stream_source cannot be changed less than 300 seconds prior to the livestream"
}
```

***

### 7. Pin Products to Livestream

Pin (highlight) one or more products during an active livestream. The pinned products will be prominently displayed to all viewers watching the livestream in real-time.

**Endpoint**: `POST /api/v1/live_streams/{live_stream_id}/pin_product` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:write` (for OAuth apps) **Content Type**: `application/json`

> **Important**: This endpoint can only be used when the livestream status is `active` or `paused`. Products cannot be pinned to livestreams in `idle`, `replay`, `completed`, or `expired` status.

> **Note**: The system supports up to 3 products pinned simultaneously(override).

#### 7.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |
| `Content-Type`  | Must be `application/json`            | ✅        |

#### 7.2. Path Parameters

| Parameter        | Type   | Required | Description                             |
| ---------------- | ------ | -------- | --------------------------------------- |
| `live_stream_id` | string | ✅        | The unique identifier of the livestream |

#### 7.3. Request Body

| Parameter     | Type      | Required | Description                                              |
| ------------- | --------- | -------- | -------------------------------------------------------- |
| `product_ids` | string\[] | ✅        | Array of Firework product IDs or product unit IDs to pin |

> **Note**: Both Firework product IDs and product unit IDs are supported. You can obtain these IDs from the Firework product catalog or product management APIs. The array must contain at least 1 item and maximum 3 items per request.

#### 7.4. Pin Products Response

**Success Response**: `200 OK`

| Field                | Type      | Description                                           |
| -------------------- | --------- | ----------------------------------------------------- |
| `pinned_product_ids` | string\[] | Array of product IDs that were pinned in this request |

> **Note**: Upon receiving this success response, all products have already been broadcasted to all viewers. There is no delay - the pins are live immediately.

#### 7.5. Pin Products Error Responses

| Status Code                | Description                                                                  |
| -------------------------- | ---------------------------------------------------------------------------- |
| `400 Bad Request`          | Invalid request format, missing product\_ids, or malformed IDs               |
| `401 Unauthorized`         | Invalid or missing authentication token, or access denied to this livestream |
| `404 Not Found`            | Livestream not found or no valid products found                              |
| `422 Unprocessable Entity` | Livestream is not active (must be in `active` or `paused` status)            |

#### 7.6. Example Requests

**Example 1: Pin Products**

```bash
curl -X POST "https://api.firework.com/api/v1/live_streams/616dOp/pin_product" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "product_ids": ["GeEk8y", "ABC123", "XYZ789"]
  }'
```

**Success Response (200 OK)**

```json
{
  "pinned_product_ids": ["GeEk8y", "ABC123", "XYZ789"]
}
```

**Example 2: Pin Product Unit (Variant/SKU)**

```bash
curl -X POST "https://api.firework.com/api/v1/live_streams/616dOp/pin_product" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "product_ids": ["082YOm"]
  }'
```

**Success Response (200 OK)**

```json
{
  "pinned_product_ids": ["082YOm"]
}
```

> **Note**: In this example, `082YOm` is a product unit ID representing a specific variant (e.g., "Black / Standard"). Pinning a product unit highlights that specific variant during the livestream.

#### 7.7. Error Responses

```json
// 422 Unprocessable Entity - Livestream not active
{
  "error": "Livestream must be active or paused to pin products"
}

// 404 Not Found - Livestream not found
{
  "error": "Livestream not found"
}

// 400 Bad Request - Missing product_ids
{
  "error": "product_ids is required"
}

// 400 Bad Request - Empty array
{
  "error": "product_ids cannot be empty"
}

// 400 Bad Request - More than 3 products
{
  "error": "Maximum 3 products allowed per request"
}
```

***

### 8. Unpin Products from Livestream

Remove one or more pinned (highlighted) products from a livestream. This clears the product highlights for all viewers.

**Endpoint**: `POST /api/v1/live_streams/{live_stream_id}/unpin_product` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:write` (for OAuth apps) **Content Type**: `application/json`

#### 8.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |
| `Content-Type`  | Must be `application/json`            | ✅        |

#### 8.2. Path Parameters

| Parameter        | Type   | Required | Description                             |
| ---------------- | ------ | -------- | --------------------------------------- |
| `live_stream_id` | string | ✅        | The unique identifier of the livestream |

#### 8.3. Request Body

| Parameter     | Type      | Required | Description                                                |
| ------------- | --------- | -------- | ---------------------------------------------------------- |
| `product_ids` | string\[] | ✅        | Array of Firework product IDs or product unit IDs to unpin |

> **Note**: Provide the Firework product IDs or product unit IDs (variant/SKU IDs) you want to remove from the highlighted set. The array must contain at least 1 item and maximum 3 items per request.

#### 8.4. Unpin Products Response

**Success Response**: `200 OK`

| Field                  | Type      | Description                                             |
| ---------------------- | --------- | ------------------------------------------------------- |
| `unpinned_product_ids` | string\[] | Array of product IDs that were unpinned in this request |

#### 8.5. Unpin Products Error Responses

| Status Code        | Description                                                                  |
| ------------------ | ---------------------------------------------------------------------------- |
| `400 Bad Request`  | Invalid request format or malformed livestream ID                            |
| `401 Unauthorized` | Invalid or missing authentication token, or access denied to this livestream |
| `404 Not Found`    | Livestream not found or no valid products found                              |

#### 8.6. Example Requests

**Example 1: Unpin Products**

```bash
curl -X POST "https://api.firework.com/api/v1/live_streams/616dOp/unpin_product" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "product_ids": ["GeEk8y", "ABC123", "XYZ789"]
  }'
```

**Success Response (200 OK)**

```json
{
  "unpinned_product_ids": ["GeEk8y", "ABC123", "XYZ789"]
}
```

**Example 2: Unpin Product Unit (Variant/SKU)**

```bash
curl -X POST "https://api.firework.com/api/v1/live_streams/616dOp/unpin_product" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "product_ids": ["082YOm"]
  }'
```

**Success Response (200 OK)**

```json
{
  "unpinned_product_ids": ["082YOm"]
}
```

#### 8.7. Error Responses

```json
// 404 Not Found - Livestream not found
{
  "error": "Livestream not found"
}

// 404 Not Found - No valid products found
{
  "error": "No valid products found to unpin"
}

// 400 Bad Request - Missing product_ids
{
  "error": "product_ids is required"
}

// 400 Bad Request - Empty array
{
  "error": "product_ids cannot be empty"
}
```

***

### 9. End Livestream

End an active or paused livestream. The livestream status will transition to `replay`.

**Endpoint**: `PATCH /api/v1/live_streams/{live_stream_id}/end` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:write` (for OAuth apps) **Content Type**: `application/json`

> **Important**: This action is irreversible. Once ended, the livestream cannot return to `active` status.

#### 9.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |
| `Content-Type`  | Must be `application/json`            | ✅        |

#### 9.2. Path Parameters

| Parameter        | Type   | Required | Description                             |
| ---------------- | ------ | -------- | --------------------------------------- |
| `live_stream_id` | string | ✅        | The unique identifier of the livestream |

#### 9.3. Request Body

No request body is required.

#### 9.4. End Livestream Response

**Success Response**: `200 OK`

| Field            | Type   | Description                                  |
| ---------------- | ------ | -------------------------------------------- |
| `live_stream_id` | string | The livestream that was ended                |
| `status`         | string | Always `"ended"` once the operation succeeds |

#### 9.5. End Livestream Error Responses

| Status Code        | Description                                                                  |
| ------------------ | ---------------------------------------------------------------------------- |
| `400 Bad Request`  | Livestream already ended or in a non-active state                            |
| `401 Unauthorized` | Invalid or missing authentication token, or access denied to this livestream |
| `404 Not Found`    | Livestream not found                                                         |

#### 9.6. Examples

**Example 1: End an Active Livestream**

**CURL Request**

```bash
curl -X PATCH "https://api.firework.com/api/v1/live_streams/616dOp/end" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json"
```

**Success Response (200 OK)**

```json
{
  "live_stream_id": "616dOp",
  "status": "ended"
}
```

**Error Response Examples**

```json
// 404 Not Found - Livestream not found
{
  "error": "Livestream not found"
}

// 400 Bad Request - Livestream already ended
{
  "error": "Livestream must be active or paused to end"
}
```

### 10. Export Livestream Engagement Results

Export interaction results and livestream comments after a livestream has ended. These endpoints mirror the result-download use case that is currently available in CMS, while keeping interaction setup and moderation workflows in CMS.

**Status**: Implemented **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:read` (for OAuth apps)

> **Scope note**: Engagement exports can contain viewer-entered data such as emails, names, answers, and comments. Grant `livestreams:read` only to OAuth apps that are allowed to export post-event livestream result data.

> **Availability note**: CSV result downloads are available only after the livestream has ended. If the livestream is still scheduled, idle, active, paused, or otherwise not ended, the CSV endpoints return `409 Conflict`.

#### 10.1. Recommended Workflow

1. Create and manage the livestream through the existing livestream APIs.
2. End the livestream through CMS or `PATCH /api/v1/live_streams/{live_stream_id}/end`.
3. Call `GET /api/v1/live_streams/{live_stream_id}` and read the `interactions` array.
4. Download each interaction result file by calling `responses_csv` with the livestream ID and interaction ID.
5. Download chat/comment history by calling `comments_csv` with the livestream ID when comment data is needed.

#### 10.2. Download Interaction Responses CSV

Download stored response rows for a single livestream interaction.

**Endpoint**: `GET /api/v1/live_streams/{live_stream_id}/interactions/{interaction_id}/responses_csv` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:read` **Response Content Type**: `text/csv`

**Request Headers**

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |

**Path Parameters**

| Parameter        | Type   | Required | Description                              |
| ---------------- | ------ | -------- | ---------------------------------------- |
| `live_stream_id` | string | ✅        | The unique identifier of the livestream  |
| `interaction_id` | string | ✅        | The unique identifier of the interaction |

**CSV Columns**

| Column               | Description                                                     |
| -------------------- | --------------------------------------------------------------- |
| `id`                 | Interaction response row ID                                     |
| `interaction_id`     | Interaction ID                                                  |
| `interaction_type`   | Interaction type                                                |
| `prompt`             | Interaction prompt                                              |
| `response`           | Viewer response value, such as poll choice or question answer   |
| `email`              | Viewer email, when collected                                    |
| `name`               | Viewer name, when collected                                     |
| `guest_id`           | Guest identifier                                                |
| `user_id`            | Registered user identifier, when available                      |
| `country`            | Viewer country, when available                                  |
| `terms_accepted`     | Whether the viewer accepted terms for the interaction           |
| `winner`             | Whether the row is marked as a giveaway winner, when applicable |
| `is_correct`         | Whether the trivia answer is correct, when applicable           |
| `live_stream_status` | Whether the response was submitted during `active` or `replay`  |
| `submitted_at`       | ISO 8601 timestamp when the response was submitted              |

**Example**

```bash
curl -X GET "https://api.firework.com/api/v1/live_streams/616dOp/interactions/7YDaNq/responses_csv" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

```csv
id,interaction_id,interaction_type,prompt,response,email,name,guest_id,user_id,country,terms_accepted,winner,is_correct,live_stream_status,submitted_at
81NmQp,7YDaNq,giveaway,Enter your email for the launch giveaway,,viewer@example.com,Jamie,guest_123,,US,true,true,,active,2026-05-11T14:25:08Z
```

#### 10.3. Download Livestream Comments CSV

Download chat/comment history for an ended livestream. Use this endpoint for comment exports and for reviewing comment-giveaway source messages.

**Endpoint**: `GET /api/v1/live_streams/{live_stream_id}/comments_csv` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:read` **Response Content Type**: `text/csv`

**Request Headers**

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |

**Path Parameters**

| Parameter        | Type   | Required | Description                             |
| ---------------- | ------ | -------- | --------------------------------------- |
| `live_stream_id` | string | ✅        | The unique identifier of the livestream |

**CSV Columns**

| Column                      | Description                                            |
| --------------------------- | ------------------------------------------------------ |
| `message_id`                | Livestream chat message ID                             |
| `elapsed_time`              | Elapsed livestream time when the message was sent      |
| `user_type`                 | Sender type                                            |
| `private_thread_owner_id`   | Private-thread owner ID, when applicable               |
| `guest_id`                  | Guest identifier                                       |
| `user_id`                   | Registered user identifier, when available             |
| `username`                  | Display name                                           |
| `title`                     | Livestream title                                       |
| `original_text`             | Original comment text                                  |
| `reply_to_message_id`       | Parent message ID, when this message is a reply        |
| `reply_to_message_text`     | Parent message text, when available                    |
| `reply_to_message_username` | Parent message sender, when available                  |
| `inserted_at`               | ISO 8601 timestamp when the message was created        |
| `status`                    | Message moderation status                              |
| `live_stream_status`        | Whether the message was sent during `live` or `replay` |

**Example**

```bash
curl -X GET "https://api.firework.com/api/v1/live_streams/616dOp/comments_csv" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

```csv
message_id,elapsed_time,user_type,private_thread_owner_id,guest_id,user_id,username,title,original_text,reply_to_message_id,reply_to_message_text,reply_to_message_username,inserted_at,status,live_stream_status
msg_123,00:04:18,viewer,,guest_123,,Jamie,Spring Launch,Love this product!,,,,2026-05-11T14:18:42Z,approved,live
```

#### 10.4. Error Responses

| Status Code        | Description                                                                   |
| ------------------ | ----------------------------------------------------------------------------- |
| `400 Bad Request`  | Invalid request, unsupported interaction type, or malformed ID                |
| `401 Unauthorized` | Invalid or missing authentication token                                       |
| `403 Forbidden`    | Missing `livestreams:read` scope or access denied to this business livestream |
| `404 Not Found`    | Livestream not found, or interaction not found on the specified livestream    |
| `409 Conflict`     | Livestream has not ended yet, so result exports are not available             |

**Error Response Examples**

```json
// 403 Forbidden - Missing livestreams:read scope
{
  "error": "insufficient_scope",
  "required_scope": "livestreams:read"
}

// 404 Not Found - Interaction does not belong to livestream
{
  "error": "Interaction not found"
}

// 409 Conflict - Livestream has not ended yet
{
  "error": "Livestream engagement results are available only after the livestream has ended"
}
```

***

### 11. List Livestreams

List the livestreams that belong to a channel. Only primary, non-showroom livestreams are returned. Archived livestreams are excluded by default; set `archived=true` to return only archived livestreams. Use this endpoint to discover `id` values to feed into the reporting stats endpoint (section 13) and the per-livestream read/export endpoints.

**Endpoint**: `GET /api/v1/live_streams` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:read` (for OAuth apps)

#### 11.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |

#### 11.2. Query Parameters

| Parameter    | Type    | Required | Description                                                                                                                                                                        |
| ------------ | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel_id` | string  | ✅        | Encoded channel ID to list livestreams for                                                                                                                                         |
| `statuses`   | string  | ❌        | Comma-separated status filter, for example `active,replay`. Every value must be a valid livestream status (see Status Values in section 5.3); an unrecognized value returns `400`. |
| `archived`   | boolean | ❌        | Omitted or `false` returns only non-archived livestreams; `true` returns only archived livestreams                                                                                 |
| `after`      | string  | ❌        | Opaque cursor for the next page, taken from `pagination.cursor` or `links.next` of a previous response. Ascending order.                                                           |
| `before`     | string  | ❌        | Opaque cursor for the previous page. Descending order. Mutually exclusive with `after`.                                                                                            |
| `page_size`  | integer | ❌        | Items per page. Range 1–100, default 10. Values above the max are clamped.                                                                                                         |

> **Pagination**: This endpoint is cursor-paginated. Follow `links.next` verbatim (it is `null` when there is no next page) or pass the opaque `pagination.cursor` back as `after`/`before`. `after` and `before` are mutually exclusive.

#### 11.3. List Livestreams Response

**Success Response**: `200 OK`

| Field          | Type      | Nullable | Description                                                            |
| -------------- | --------- | -------- | ---------------------------------------------------------------------- |
| `live_streams` | object\[] | ❌        | Livestream summaries (see item fields below)                           |
| `links`        | object    | ❌        | Pagination links. `next` is always present (`null` on the last page)   |
| `pagination`   | object    | ❌        | Pagination state: `cursor` (opaque, `null` when exhausted), `has_more` |

Each `live_streams` item matches the `GET /api/v1/live_streams/{live_stream_id}` response (section 5.3) plus `video_id`, `channel_id`, and `stream_source`, and without the `interactions` array:

| Field                          | Type    | Nullable | Description                                                       |
| ------------------------------ | ------- | -------- | ----------------------------------------------------------------- |
| `id`                           | string  | ❌        | Unique encoded identifier of the livestream                       |
| `video_id`                     | string  | ❌        | Encoded identifier of the backing video                           |
| `channel_id`                   | string  | ❌        | Encoded identifier of the channel                                 |
| `status`                       | string  | ❌        | Current livestream state (see Status Values, section 5.3)         |
| `stream_source`                | string  | ❌        | Source of the livestream: `fw`, `external`, or `prerecorded`      |
| `event_name`                   | string  | ✅        | Name of the livestream event                                      |
| `event_description`            | string  | ✅        | Description of the livestream event                               |
| `thumbnail_url`                | string  | ✅        | URL of the livestream thumbnail image                             |
| `has_access_code`              | boolean | ❌        | Whether the video/livestream is locked by an access code          |
| `announcement`                 | string  | ✅        | Announcement banner text                                          |
| `auto_response`                | string  | ✅        | Chat auto-response mode                                           |
| `chat_enabled`                 | boolean | ❌        | Whether live chat is enabled                                      |
| `chat_in_replay_enabled`       | boolean | ❌        | Whether chat messages are displayed during replay                 |
| `chat_moderation_enabled`      | boolean | ❌        | Whether chat moderation is enabled                                |
| `hearts_count_enabled`         | boolean | ❌        | Whether like count is shown                                       |
| `fomo_product_viewing_enabled` | boolean | ❌        | Whether real-time product-viewing FOMO cues are shown             |
| `replay_enabled`               | boolean | ❌        | Whether replay is available after the livestream ends             |
| `replay_interactions_enabled`  | boolean | ❌        | Whether replay interactions are enabled                           |
| `replay_messaging_enabled`     | boolean | ❌        | Whether viewers can post messages during replay                   |
| `replay_viewers_count_enabled` | boolean | ❌        | Whether viewer count is shown during replay                       |
| `trailer_announcement_enabled` | boolean | ❌        | Whether the announcement is shown while viewers watch the trailer |
| `trailer_messaging_enabled`    | boolean | ❌        | Whether viewers can post messages while watching the trailer      |
| `transcription_enabled`        | boolean | ❌        | Whether live transcription is enabled                             |
| `viewers_count_enabled`        | boolean | ❌        | Whether viewer count is shown                                     |
| `viewers_count_mode`           | string  | ✅        | Viewer-count mode: `"accumulated"` or `"concurrent"`              |
| `scheduled_at`                 | string  | ✅        | ISO 8601 datetime when livestream is scheduled to start           |
| `started_at`                   | string  | ✅        | ISO 8601 datetime when livestream actually started                |
| `ended_at`                     | string  | ✅        | ISO 8601 datetime when livestream ended                           |

#### 11.4. List Livestreams Error Responses

| Status Code        | Description                                                             |
| ------------------ | ----------------------------------------------------------------------- |
| `400 Bad Request`  | Missing `channel_id`, or an unrecognized value in `statuses`            |
| `401 Unauthorized` | Invalid or missing authentication token                                 |
| `403 Forbidden`    | The authenticated user or OAuth app does not have access to the channel |
| `404 Not Found`    | Channel not found                                                       |

**Error Response Format**:

```json
{
  "error": "Error Message"
}
```

#### 11.5. Example

**CURL Request**

```bash
curl -X GET "https://api.firework.com/api/v1/live_streams?channel_id=X6Xqd6W&statuses=active,replay&page_size=50" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Example Response**

```json
{
  "live_streams": [
    {
      "id": "616dOp",
      "video_id": "9aQ2Kp",
      "channel_id": "X6Xqd6W",
      "status": "replay",
      "stream_source": "fw",
      "event_name": "Product Launch Livestream",
      "event_description": "Join us for our exciting new product reveal!",
      "thumbnail_url": "https://cdn.firework.com/live_streams/616dOp/thumbnail.jpg",
      "has_access_code": false,
      "announcement": null,
      "auto_response": "disabled",
      "chat_enabled": true,
      "chat_in_replay_enabled": true,
      "chat_moderation_enabled": false,
      "hearts_count_enabled": false,
      "fomo_product_viewing_enabled": false,
      "replay_enabled": true,
      "replay_interactions_enabled": true,
      "replay_messaging_enabled": false,
      "replay_viewers_count_enabled": true,
      "trailer_announcement_enabled": false,
      "trailer_messaging_enabled": false,
      "transcription_enabled": false,
      "viewers_count_enabled": true,
      "viewers_count_mode": "accumulated",
      "scheduled_at": "2026-03-19T09:41:00.000000Z",
      "started_at": "2026-03-19T09:42:15.000000Z",
      "ended_at": "2026-03-19T11:30:00.000000Z"
    }
  ],
  "links": {
    "next": "/api/v1/live_streams?channel_id=X6Xqd6W&statuses=active,replay&after=cursor456&page_size=50"
  },
  "pagination": {
    "cursor": "cursor456",
    "has_more": true
  }
}
```

***

### 12. List Livestream Chat Messages

Cursor-paginated chat messages for a livestream. Messages are available while the livestream is live (`active`) or in replay (`replay`, i.e. ended):

* **Live** (`active`) — forward paging only. The first request (no cursor) starts from a trailing window near the live edge, and the response always returns an `after` cursor and a non-null `links.next` — even at the live edge — so the client can keep polling. Backward paging (`before` / `until`) is rejected.
* **Replay** (`replay`) — both forward (`after` / `since`) and backward (`before` / `until`) paging are supported, defaulting to forward from the start. When paging reaches the end, `pagination.cursor` and `links.next` become `null`.

Any other status returns `400`.

**Endpoint**: `GET /api/v1/live_streams/{live_stream_id}/messages` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:read` (for OAuth apps)

#### 12.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |

#### 12.2. Path Parameters

| Parameter        | Type   | Required | Description                             |
| ---------------- | ------ | -------- | --------------------------------------- |
| `live_stream_id` | string | ✅        | The unique identifier of the livestream |

#### 12.3. Query Parameters

| Parameter   | Type    | Required | Description                                                                                                           |
| ----------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `after`     | string  | ❌        | Opaque forward cursor from a previous response (`pagination.cursor` / `links.next`). Ascending order.                 |
| `before`    | string  | ❌        | Opaque backward cursor (replay only). Descending order. Mutually exclusive with `after`.                              |
| `since`     | number  | ❌        | Elapsed-time window start, in seconds from the start of the stream. Returns messages at/after this offset, ascending. |
| `until`     | number  | ❌        | Elapsed-time window end, in seconds. Returns messages before this offset, descending (replay only).                   |
| `page_size` | integer | ❌        | Items per page. Range 1–100, default 10. Values above the max are clamped.                                            |

**Parameter Rules:**

* `after` / `before` are opaque cursors the server issued; `since` / `until` are elapsed-time values you supply. They are not interchangeable.
* Provide at most one paging direction per request. `after` and `before` are mutually exclusive; conflicting or otherwise invalid combinations return `400`.
* `since` / `after` page forward (ascending); `until` / `before` page backward (descending).
* Backward paging (`before` / `until`) is only valid in replay mode. Requesting it while the livestream is live returns `400`.
* `since` / `until` must parse as numbers.

#### 12.4. List Messages Response

**Success Response**: `200 OK`

| Field        | Type      | Nullable | Description                                                            |
| ------------ | --------- | -------- | ---------------------------------------------------------------------- |
| `messages`   | object\[] | ❌        | Chat messages for the page (see item fields below)                     |
| `links`      | object    | ❌        | Pagination links. `next` is the path to the next page, or `null`       |
| `pagination` | object    | ❌        | Pagination state: `cursor` (opaque, `null` when exhausted), `has_more` |

Each `messages` item:

| Field          | Type   | Nullable | Description                                     |
| -------------- | ------ | -------- | ----------------------------------------------- |
| `id`           | string | ❌        | Chat message ID                                 |
| `elapsed_time` | number | ❌        | Seconds from the start of the stream            |
| `username`     | string | ❌        | Display name of the sender                      |
| `text`         | string | ❌        | Message text                                    |
| `inserted_at`  | string | ❌        | ISO 8601 timestamp when the message was created |

**Client Polling Semantics**

For live feeds, the pair (`links.next`, `pagination.has_more`) tells the client what to do next:

| `links.next` | `has_more` | Meaning                               | Action                                          |
| ------------ | ---------- | ------------------------------------- | ----------------------------------------------- |
| `null`       | —          | No more data ever (e.g. replay ended) | Stop.                                           |
| present      | `true`     | More pages available now              | Fetch `links.next` immediately.                 |
| present      | `false`    | Caught up to the live edge            | Save `links.next`, fetch after a poll interval. |

#### 12.5. List Messages Error Responses

| Status Code        | Description                                                                                                                            |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`  | Livestream is not live or in replay, conflicting/invalid pagination params, backward paging while live, or non-numeric `since`/`until` |
| `401 Unauthorized` | Invalid or missing authentication token                                                                                                |
| `403 Forbidden`    | The authenticated user or OAuth app does not have access to this livestream                                                            |
| `404 Not Found`    | Livestream not found                                                                                                                   |

**Error Response Format**:

```json
{
  "error": "Error Message"
}
```

#### 12.6. Example

**CURL Request**

```bash
curl -X GET "https://api.firework.com/api/v1/live_streams/616dOp/messages?after=cursor123&page_size=100" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Example Response**

```json
{
  "messages": [
    {
      "id": "b3f1Xy",
      "elapsed_time": 132.5,
      "username": "jane_doe",
      "text": "Is this available in size M?",
      "inserted_at": "2026-06-12T12:04:03.986000Z"
    }
  ],
  "links": {
    "next": "/api/v1/live_streams/616dOp/messages?after=cursor456&page_size=100"
  },
  "pagination": {
    "cursor": "cursor456",
    "has_more": true
  }
}
```

***

### 13. Livestream Reporting Stats

Consolidated reporting stats for a single livestream, in a `meta` / `live` / `replay` shape. `meta` carries identity and event metadata, `live` holds the active broadcast's metrics (or `null` when the livestream is not currently live), and `replay` is an array of per-day replay metric rows.

**Endpoint**: `GET /api/v1/stats/live_streams/{live_stream_id}` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:read` (for OAuth apps)

> **Note**: The `live_stream_id` values returned by `GET /api/v1/live_streams` (section 11) are the inputs to this endpoint.

#### 13.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |

#### 13.2. Path Parameters

| Parameter        | Type   | Required | Description                             |
| ---------------- | ------ | -------- | --------------------------------------- |
| `live_stream_id` | string | ✅        | The unique identifier of the livestream |

#### 13.3. Query Parameters

| Parameter | Type   | Required | Description                                                                                           |
| --------- | ------ | -------- | ----------------------------------------------------------------------------------------------------- |
| `from`    | string | ❌        | Replay-window start as an ISO date (`YYYY-MM-DD`, e.g. `2026-01-31`). Bounds only the `replay` array. |
| `to`      | string | ❌        | Replay-window end as an ISO date. Bounds only the `replay` array.                                     |

**Parameter Rules:**

* `from` / `to` bound only the `replay` array. `meta` and `live` are never dropped by the date window.
* All rows are capped at the previous UTC day (current date − 1), so the current, partial day is excluded.
* A `from` / `to` value that is not a valid ISO date (including a bracketed `from[]=...` form) returns `400`.

#### 13.4. Reporting Stats Response

**Success Response**: `200 OK`

| Field    | Type          | Nullable | Description                                                                 |
| -------- | ------------- | -------- | --------------------------------------------------------------------------- |
| `meta`   | object        | ❌        | Livestream identity and event metadata (see below)                          |
| `live`   | object / null | ✅        | Metrics for the active broadcast, or `null` when the livestream is not live |
| `replay` | object\[]     | ❌        | Per-day replay metric rows (see metrics fields below)                       |

`meta` fields:

| Field               | Type    | Nullable | Description                                         |
| ------------------- | ------- | -------- | --------------------------------------------------- |
| `live_stream_id`    | string  | ❌        | Encoded livestream ID                               |
| `video_id`          | string  | ❌        | Encoded backing video ID                            |
| `business_id`       | string  | ❌        | Encoded business ID                                 |
| `channel_id`        | string  | ❌        | Encoded channel ID                                  |
| `start_date`        | string  | ✅        | Report date of the active row (ISO date), or `null` |
| `live_stream_name`  | string  | ✅        | Livestream/event name                               |
| `is_human_hosted`   | boolean | ✅        | Whether the event was human-hosted                  |
| `is_restream`       | boolean | ✅        | Whether the event was a restream                    |
| `event_length_mins` | number  | ✅        | Event length in minutes                             |

The `live` object contains the shared metrics fields below plus two live-only fields:

| Field                          | Type    | Nullable | Description                              |
| ------------------------------ | ------- | -------- | ---------------------------------------- |
| `peak_concurrent_viewer_count` | integer | ❌        | Peak concurrent viewers of the broadcast |
| `total_reactions`              | integer | ❌        | Total reactions during the broadcast     |

Shared metrics fields (present on `live` and on each `replay` row):

| Field                                                    | Type    | Nullable | Description                                                         |
| -------------------------------------------------------- | ------- | -------- | ------------------------------------------------------------------- |
| `report_date`                                            | string  | ❌        | Report date (ISO date)                                              |
| `unique_viewers_count`                                   | integer | ❌        | Unique viewers for the day                                          |
| `viewer_watch_time_breakdown`                            | object  | ❌        | Viewer counts bucketed by watch time (fields below)                 |
| `viewer_watch_time_breakdown.num_watched_0_mins`         | integer | ❌        | Viewers who watched \~0 minutes                                     |
| `viewer_watch_time_breakdown.num_watched_0_to_1_mins`    | integer | ❌        | Viewers who watched 0–1 minutes                                     |
| `viewer_watch_time_breakdown.num_watched_1_to_10_mins`   | integer | ❌        | Viewers who watched 1–10 minutes                                    |
| `viewer_watch_time_breakdown.num_watched_10_to_30_mins`  | integer | ❌        | Viewers who watched 10–30 minutes                                   |
| `viewer_watch_time_breakdown.num_watched_30_to_end_mins` | integer | ❌        | Viewers who watched 30 minutes to the end                           |
| `total_watched_seconds`                                  | number  | ❌        | Total watch time in seconds                                         |
| `engaged_viewers`                                        | integer | ❌        | Engaged viewers for the day                                         |
| `total_video_views`                                      | integer | ❌        | Total video views                                                   |
| `total_chats`                                            | integer | ❌        | Total chat messages                                                 |
| `total_shares`                                           | integer | ❌        | Total shares                                                        |
| `share_targets`                                          | object  | ❌        | Map of share target (e.g. `facebook`) → count; `{}` when none       |
| `total_product_clicks`                                   | integer | ❌        | Total product clicks                                                |
| `total_7d_attributed_gmv`                                | object  | ❌        | Map of currency code → 7-day attributed GMV; `{}` when none         |
| `total_7d_attributed_orders`                             | object  | ❌        | Map of currency code → 7-day attributed order count; `{}` when none |

#### 13.5. Reporting Stats Error Responses

| Status Code        | Description                               |
| ------------------ | ----------------------------------------- |
| `400 Bad Request`  | `from` / `to` is not a valid ISO date     |
| `401 Unauthorized` | Invalid or missing authentication token   |
| `403 Forbidden`    | Access denied to this business livestream |
| `404 Not Found`    | Livestream not found                      |

**Error Response Format**:

```json
{
  "error": "Error Message"
}
```

#### 13.6. Example

**CURL Request**

```bash
curl -X GET "https://api.firework.com/api/v1/stats/live_streams/616dOp?from=2026-03-19&to=2026-03-25" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Example Response**

```json
{
  "meta": {
    "live_stream_id": "616dOp",
    "video_id": "9aQ2Kp",
    "business_id": "Bz1QwE",
    "channel_id": "X6Xqd6W",
    "start_date": "2026-03-19",
    "live_stream_name": "Product Launch Livestream",
    "is_human_hosted": true,
    "is_restream": false,
    "event_length_mins": 108.0
  },
  "live": null,
  "replay": [
    {
      "report_date": "2026-03-20",
      "unique_viewers_count": 1423,
      "viewer_watch_time_breakdown": {
        "num_watched_0_mins": 210,
        "num_watched_0_to_1_mins": 512,
        "num_watched_1_to_10_mins": 480,
        "num_watched_10_to_30_mins": 180,
        "num_watched_30_to_end_mins": 41
      },
      "total_watched_seconds": 1875400.0,
      "engaged_viewers": 902,
      "total_video_views": 1610,
      "total_chats": 388,
      "total_shares": 57,
      "share_targets": { "facebook": 31, "x": 26 },
      "total_product_clicks": 214,
      "total_7d_attributed_gmv": { "USD": 4820.5 },
      "total_7d_attributed_orders": { "USD": 62 }
    }
  ]
}
```

***

### 14. Get Livestream Detail (Legacy)

Fetch the viewer-side detail payload for a livestream — the same data the player/embed uses to render playback, products, interactions, and replays.

> **Legacy endpoint**: This route is served by the viewer-side controller (`NabooWeb.OauthApp.LiveStreamController`) and is retained for backward compatibility. Its response is a large, loosely-typed viewer payload whose exact shape is defined by the internal player view (`NabooWeb.LiveStreamView`) and may include additional fields not listed here. For host/management and reporting use cases, prefer `GET /api/v1/live_streams/{live_stream_id}` (section 5) and the stats endpoint (section 13).

**Endpoint**: `GET /api/v1/live_streams/{live_stream_id}/detail` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:read` (for OAuth apps)

#### 14.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |

#### 14.2. Path Parameters

| Parameter        | Type   | Required | Description                             |
| ---------------- | ------ | -------- | --------------------------------------- |
| `live_stream_id` | string | ✅        | The unique identifier of the livestream |

#### 14.3. Livestream Detail Response

**Success Response**: `200 OK`

The response is a loosely-typed viewer object. Representative top-level fields (non-exhaustive):

| Field                          | Type      | Nullable | Description                                                  |
| ------------------------------ | --------- | -------- | ------------------------------------------------------------ |
| `status`                       | string    | ❌        | Current livestream state (see Status Values, section 5.3)    |
| `stream_source`                | string    | ❌        | Source of the livestream: `fw`, `external`, or `prerecorded` |
| `provider`                     | string    | ❌        | Streaming provider                                           |
| `event_name`                   | string    | ❌        | Name of the livestream event                                 |
| `event_description`            | string    | ✅        | Description of the livestream event                          |
| `playback_url`                 | string    | ✅        | Live playback URL                                            |
| `replay_url`                   | string    | ✅        | Replay playback URL                                          |
| `scheduled_at`                 | string    | ✅        | ISO 8601 datetime when the livestream is scheduled to start  |
| `started_at`                   | string    | ✅        | ISO 8601 datetime when the livestream started                |
| `ended_at`                     | string    | ✅        | ISO 8601 datetime for the scheduled/actual end               |
| `announcement`                 | string    | ✅        | Announcement banner text                                     |
| `chat_enabled`                 | boolean   | ❌        | Whether live chat is enabled                                 |
| `chat_in_replay_enabled`       | boolean   | ❌        | Whether chat is displayed during replay                      |
| `chat_moderation_enabled`      | boolean   | ❌        | Whether chat moderation is enabled                           |
| `hearts_count_enabled`         | boolean   | ❌        | Whether like count is shown                                  |
| `fomo_product_viewing_enabled` | boolean   | ❌        | Whether real-time product-viewing FOMO cues are shown        |
| `replay_enabled`               | boolean   | ❌        | Whether replay is available                                  |
| `replay_interactions_enabled`  | boolean   | ❌        | Whether replay interactions are enabled                      |
| `viewers_count_enabled`        | boolean   | ❌        | Whether viewer count is shown                                |
| `viewers_count_mode`           | string    | ✅        | Viewer-count mode                                            |
| `transcription_enabled`        | boolean   | ❌        | Whether live transcription is enabled                        |
| `products`                     | object\[] | ❌        | Tagged/pinned products and their key moments                 |
| `interactions`                 | object\[] | ❌        | Polls, quizzes, questions, and giveaways                     |
| `deals`                        | object\[] | ❌        | Livestream deals                                             |
| `insights`                     | object    | ❌        | Aggregate counts (likes, viewers)                            |
| `subtitles`                    | object\[] | ❌        | Subtitle tracks                                              |
| `video_id`                     | string    | ❌        | Encoded backing video ID                                     |
| `test_mode`                    | boolean   | ❌        | Whether the livestream is in test mode                       |
| `time_zone`                    | string    | ✅        | IANA timezone identifier for the event                       |

> **Note**: The full payload also includes additional viewer-oriented fields (moderation config, message rate limits, replay playlists, share URLs, and more). Treat unlisted fields as forward-compatible additions.

#### 14.4. Livestream Detail Error Responses

| Status Code        | Description                                                                 |
| ------------------ | --------------------------------------------------------------------------- |
| `401 Unauthorized` | Invalid or missing authentication token                                     |
| `403 Forbidden`    | The authenticated user or OAuth app does not have access to this livestream |
| `404 Not Found`    | Livestream not found                                                        |

**Error Response Format**:

```json
{
  "error": "Error Message"
}
```

#### 14.5. Example

**CURL Request**

```bash
curl -X GET "https://api.firework.com/api/v1/live_streams/616dOp/detail" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

***

### 15. List Livestream Playlist Videos (Legacy)

List the videos (livestreams) contained in a livestream playlist. Unlike the other list endpoints, this endpoint uses **page-based** pagination (`page` / `page_size`), not cursors.

> **Legacy endpoint**: Served by the viewer-side controller (`NabooWeb.OauthApp.LiveStreamController`) and slated for migration to the standard cursor pagination shape. During the migration window the response carries the modern `links` + `pagination` objects **alongside** the deprecated `paging` object; new integrations should read `links` / `pagination` and ignore `paging`.

**Endpoint**: `GET /api/v1/live_streams/playlists/{playlist_id}/videos` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `livestreams:read` (for OAuth apps)

#### 15.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |

#### 15.2. Path Parameters

| Parameter     | Type   | Required | Description                           |
| ------------- | ------ | -------- | ------------------------------------- |
| `playlist_id` | string | ✅        | The unique identifier of the playlist |

#### 15.3. Query Parameters

| Parameter   | Type    | Required | Description                                                                                        |
| ----------- | ------- | -------- | -------------------------------------------------------------------------------------------------- |
| `page`      | integer | ❌        | 1-based page number. Default 1.                                                                    |
| `page_size` | integer | ❌        | Items per page. Range 1–100, default 10. Values above the max are clamped.                         |
| `status`    | string  | ❌        | Filter to a single livestream status (see Status Values, section 5.3).                             |
| `test_mode` | string  | ❌        | Filter test-mode livestreams (`true` / `false`).                                                   |
| `archived`  | boolean | ❌        | Omitted or `false` returns only non-archived livestreams; `true` returns only archived livestreams |

#### 15.4. Playlist Videos Response

**Success Response**: `200 OK`

| Field          | Type      | Nullable | Description                                                                              |
| -------------- | --------- | -------- | ---------------------------------------------------------------------------------------- |
| `live_streams` | object\[] | ❌        | Livestream detailed summaries (see item fields below)                                    |
| `links`        | object    | ❌        | Pagination links (`next`, `prev`)                                                        |
| `pagination`   | object    | ❌        | Pagination state for the page strategy: `page`, `has_more` (and page metadata)           |
| `paging`       | object    | ❌        | **Deprecated** — legacy `{ next, prev }` URL object. Use `links` / `pagination` instead. |

Each `live_streams` item is a loosely-typed livestream detailed summary. Representative fields (non-exhaustive):

| Field                   | Type      | Nullable | Description                                   |
| ----------------------- | --------- | -------- | --------------------------------------------- |
| `id`                    | string    | ❌        | Encoded livestream ID                         |
| `parent_live_stream_id` | string    | ✅        | Encoded parent livestream ID, when applicable |
| `status`                | string    | ❌        | Current livestream state (see Status Values)  |
| `scheduled_at`          | string    | ✅        | ISO 8601 datetime when scheduled to start     |
| `started_at`            | string    | ✅        | ISO 8601 datetime when started                |
| `ended_at`              | string    | ✅        | ISO 8601 datetime when ended                  |
| `scheduled_end_at`      | string    | ✅        | ISO 8601 datetime for the scheduled end       |
| `duration`              | number    | ✅        | Duration in seconds                           |
| `event_name`            | string    | ❌        | Name of the livestream event                  |
| `event_description`     | string    | ✅        | Description of the livestream event           |
| `likes_count`           | integer   | ❌        | Aggregate like count                          |
| `viewers_count`         | integer   | ❌        | Aggregate viewer count                        |
| `video_posters`         | object\[] | ❌        | Poster images for the livestream              |
| `trailer`               | object    | ❌        | The backing video object                      |

> **Note**: The full item also includes formatted date fields and other viewer-oriented attributes. Treat unlisted fields as forward-compatible additions.

#### 15.5. Playlist Videos Error Responses

| Status Code        | Description                                                              |
| ------------------ | ------------------------------------------------------------------------ |
| `401 Unauthorized` | Invalid or missing authentication token                                  |
| `403 Forbidden`    | The authenticated user or OAuth app does not have access to the playlist |
| `404 Not Found`    | Playlist not found                                                       |

**Error Response Format**:

```json
{
  "error": "Error Message"
}
```

#### 15.6. Example

**CURL Request**

```bash
curl -X GET "https://api.firework.com/api/v1/live_streams/playlists/QwErTy/videos?page=1&page_size=20&status=replay" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Example Response**

```json
{
  "live_streams": [
    {
      "id": "616dOp",
      "parent_live_stream_id": null,
      "status": "replay",
      "scheduled_at": "2026-03-19T09:41:00.000000Z",
      "started_at": "2026-03-19T09:42:15.000000Z",
      "ended_at": "2026-03-19T11:30:00.000000Z",
      "scheduled_end_at": "2026-03-19T11:41:00.000000Z",
      "duration": 6705.0,
      "event_name": "Product Launch Livestream",
      "event_description": "Join us for our exciting new product reveal!",
      "likes_count": 3820,
      "viewers_count": 1423,
      "video_posters": [],
      "trailer": {}
    }
  ],
  "links": {
    "next": "/api/v1/live_streams/playlists/QwErTy/videos?page=2&page_size=20&status=replay",
    "prev": null
  },
  "pagination": {
    "page": 1,
    "has_more": true
  },
  "paging": {
    "next": "/api/v1/live_streams/playlists/QwErTy/videos?page=2&page_size=20&status=replay",
    "prev": null
  }
}
```

***

### 16. List Livestream Products

Retrieve the complete configured product snapshot for a livestream, in configured order. This endpoint is intentionally unpaginated: it returns every configured product entry and does not include `links`, `pagination`, or the deprecated `paging` object. Supported public write operations allow at most 100 product-listing entries, but this endpoint does not truncate an existing snapshot that exceeds that limit.

The snapshot includes scheduled, active, and expired deals attached to the configured products. Deals attached to product units that are not configured on the livestream are omitted.

**Endpoint**: `GET /api/v1/live_streams/{live_stream_id}/products`\
**Authentication**: Bearer token required\
**Required Scope**: `livestreams:read` (`livestreams:write` also satisfies this)

#### 16.1. Request Headers

| Name            | Description                           | Required |
| --------------- | ------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}` | ✅        |

#### 16.2. Path Parameters

| Parameter        | Type   | Required | Description                    |
| ---------------- | ------ | -------- | ------------------------------ |
| `live_stream_id` | string | ✅        | Encoded Firework livestream ID |

#### 16.3. Livestream Products Response

**Success Response**: `200 OK`

| Field      | Type      | Nullable | Description                                                                                     |
| ---------- | --------- | -------- | ----------------------------------------------------------------------------------------------- |
| `products` | object\[] | ❌        | Complete configured product snapshot in livestream order; empty when no products are configured |

Each entry in `products` contains:

| Field                       | Type      | Nullable | Description                                                                                                                         |
| --------------------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `product`                   | object    | ❌        | Full public Product object; see the [Product Object Reference](/firework-for-developers/api/products.md#4-product-object-reference) |
| `selected_product_unit_ids` | string\[] | ❌        | Encoded IDs of the product units selected for this livestream, in configured order                                                  |
| `deals`                     | object\[] | ❌        | Deals associated with the configured product or its configured units, ordered by deal ID                                            |

Every deal contains all of the following keys, with nullable values represented explicitly as `null`:

| Field                      | Type   | Nullable | Description                                                                   |
| -------------------------- | ------ | -------- | ----------------------------------------------------------------------------- |
| `id`                       | string | ❌        | Encoded deal ID                                                               |
| `product_unit_id`          | string | ✅        | Encoded product-unit ID, or `null` when the deal applies to the whole product |
| `product_unit_external_id` | string | ✅        | External product-unit ID                                                      |
| `coupon_code`              | string | ✅        | Coupon code associated with the deal                                          |
| `discount_type`            | string | ✅        | `percentage`, `fixed_amount`, or `null`                                       |
| `discount_amount`          | string | ✅        | Decimal discount value serialized as a string                                 |
| `starts_at`                | string | ✅        | ISO 8601 start time                                                           |
| `ends_at`                  | string | ✅        | ISO 8601 end time                                                             |

```json
{
  "products": [
    {
      "product": {
        "id": "vWKDjg",
        "external_id": "SHOE-001",
        "name": "Classic Running Shoe",
        "description": "Lightweight running shoe for everyday training",
        "currency": "USD",
        "handle": "https://example.com/products/classic-running-shoe",
        "options": ["color", "size"],
        "category": "Footwear",
        "hide_price": false,
        "brand": "RunCo",
        "subtitle": "Everyday training shoe",
        "shipping": "Free shipping over $50",
        "custom_cta": null,
        "business_store_id": "y8PDj8",
        "images": [
          {
            "id": "mK9x2p",
            "external_id": "IMG-001",
            "url": "https://example.com/images/shoe-gray.jpg",
            "position": 0,
            "unit_external_ids": ["SHOE-001-GR-10"]
          }
        ],
        "units": [
          {
            "id": "nP3qRv",
            "external_id": "SHOE-001-GR-10",
            "name": "Gray / Size 10",
            "price": 129.99,
            "original_price": 149.99,
            "url": "https://example.com/products/classic-running-shoe?variant=gray-10",
            "position": 0,
            "quantity": 25,
            "options": [
              { "name": "color", "value": "Gray" },
              { "name": "size", "value": "10" }
            ],
            "sku": "SKU-SHOE-GR10",
            "gtin": "071249656457",
            "mpn": null,
            "barcode": null
          }
        ]
      },
      "selected_product_unit_ids": ["nP3qRv"],
      "deals": [
        {
          "id": "dE4lQp",
          "product_unit_id": "nP3qRv",
          "product_unit_external_id": "SHOE-001-GR-10",
          "coupon_code": "LIVE10",
          "discount_type": "fixed_amount",
          "discount_amount": "10",
          "starts_at": "2026-08-24T10:00:00Z",
          "ends_at": "2026-08-24T12:00:00Z"
        }
      ]
    }
  ]
}
```

When no products are configured, the response is:

```json
{
  "products": []
}
```

#### 16.4. Livestream Products Error Responses

| Status Code        | Description                                                                         |
| ------------------ | ----------------------------------------------------------------------------------- |
| `400 Bad Request`  | Malformed `live_stream_id`                                                          |
| `401 Unauthorized` | Missing/invalid authentication token, or the OAuth app cannot access the livestream |
| `403 Forbidden`    | Missing `livestreams:read` scope                                                    |
| `404 Not Found`    | Livestream not found                                                                |

#### 16.5. Example

```bash
curl -X GET "https://api.firework.com/api/v1/live_streams/encoded_live_stream_id/products" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
