> 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}/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                                                           |
| `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                                       |
| `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                                            |
| `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                        |
| `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,
    "viewers_count_enabled": true,
    "viewers_count_mode": "accumulated",
    "hearts_count_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,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_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                                  |
| `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              |
| `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,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_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,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_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,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_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                                                                      |
| `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                                                  |
| `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                                                                 |
| `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                                             |
| `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,
    "viewers_count_enabled": true,
    "viewers_count_mode": "accumulated",
    "hearts_count_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,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_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,
  "replay_enabled": true,
  "replay_interactions_enabled": true,
  "replay_messaging_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. 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`. |
| `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                                  |
| `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              |
| `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,
      "replay_enabled": true,
      "replay_interactions_enabled": true,
      "replay_messaging_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                                  |
| `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`).                           |

#### 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
  }
}
```
