> 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/playlist.md).

# Playlist

### 1. Overview

The Firework Playlist API lets you manage playlists on a channel: list a channel's playlists, create new playlists, update them, delete them, and manage the videos they contain (list, add, reorder, remove, and pin/unpin).

A playlist belongs to a single channel (its `creator`) and holds an ordered list of video IDs. Use the playlist `video_ids` to cross-reference with the [Video API](/firework-for-developers/api/videos.md) (`GET /api/v1/videos/{id}`), or call `GET /api/v1/playlists/{id}/videos` to page through the full video objects directly.

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

### 2. Authentication

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

**Authentication Methods Supported:**

* **Client Credentials**: OAuth 2.0 Client Credentials flow for server-to-server authentication (machine-to-machine)
* **User Authentication**: Standard OAuth 2.0 user authorization flow

Read operations require the `playlists:read` scope; write operations require `playlists:write` (which implicitly grants read access). The `api:admin` scope bypasses all scope checks.

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

***

### 3. Endpoint Summary

| Endpoint                                       | Method   | Scope             | Notes                                           |
| ---------------------------------------------- | -------- | ----------------- | ----------------------------------------------- |
| `/api/v1/playlists`                            | `GET`    | `playlists:read`  | List a channel's playlists (cursor pagination)  |
| `/api/v1/playlists`                            | `POST`   | `playlists:write` | Create a playlist in a channel                  |
| `/api/v1/playlists/{id}`                       | `PATCH`  | `playlists:write` | Update a playlist                               |
| `/api/v1/playlists/{id}`                       | `DELETE` | `playlists:write` | Delete a playlist                               |
| `/api/v1/playlists/{id}/videos`                | `GET`    | `playlists:read`  | List a playlist's videos (page pagination)      |
| `/api/v1/playlists/{id}/videos`                | `POST`   | `playlists:write` | Add a video to a playlist                       |
| `/api/v1/playlists/{id}/videos`                | `PUT`    | `playlists:write` | Reorder a playlist's videos                     |
| `/api/v1/playlists/{id}/videos/{video_id}`     | `DELETE` | `playlists:write` | Remove a video from a playlist                  |
| `/api/v1/playlists/{id}/videos/{video_id}/pin` | `POST`   | `playlists:write` | Pin a video to the top (AI-feed playlists only) |
| `/api/v1/playlists/{id}/videos/{video_id}/pin` | `DELETE` | `playlists:write` | Unpin a video (AI-feed playlists only)          |

All write scopes (`playlists:write`) implicitly satisfy the `playlists:read` requirement.

***

### 4. Playlist Object

The create, update, add-video, and reorder endpoints return a single playlist object as the top-level response body. The list endpoint returns an array of these objects under `playlists`.

| Field         | Type      | Nullable | Description                                   |
| ------------- | --------- | -------- | --------------------------------------------- |
| `id`          | string    | ❌        | Encoded playlist ID                           |
| `name`        | string    | ❌        | Name of the playlist                          |
| `description` | string    | ✅        | Description of the playlist                   |
| `updated_at`  | string    | ❌        | ISO 8601 timestamp of last update             |
| `video_ids`   | string\[] | ❌        | Array of encoded video IDs, in playlist order |

> **Note:** On the list endpoint (`GET /api/v1/playlists`), `video_ids` contains only currently available videos — deleted or unapproved videos are filtered out. The create/update/add/reorder responses echo the stored order without that filtering.

***

### 5. List Channel Playlists

Retrieve a paginated list of playlists for a specific channel. The authenticated user or OAuth app must have access to the channel's business. Only enabled, non-archived playlists are returned. Results are ordered by sort position descending (newest first).

**Endpoint**: `GET /api/v1/playlists`\
**Authentication**: Bearer token required\
**Required Scope**: `playlists:read` (for OAuth apps; `playlists:write` also satisfies this)

#### 5.1. Request Headers

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

#### 5.2. Query Parameters

This endpoint uses **cursor pagination**. Treat `after`/`before` values as opaque tokens issued by the server (via `pagination.cursor` or `links.next`) — do not construct them yourself.

| Parameter    | Type    | Required | Description                                                                            |
| ------------ | ------- | -------- | -------------------------------------------------------------------------------------- |
| `channel_id` | string  | ✅        | Firework encoded channel ID                                                            |
| `page_size`  | integer | ❌        | Number of playlists per page (default: 10, max: 100; values above the max are clamped) |
| `after`      | string  | ❌        | Opaque cursor: return the page after this cursor (ascending order)                     |
| `before`     | string  | ❌        | Opaque cursor: return the page before this cursor (descending order)                   |

`after` and `before` are mutually exclusive — supplying both returns `400 Bad Request` with `{"error": "after and before are mutually exclusive"}`.

> **Deprecated (legacy).** The raw `since_id` and `before_id` params are still accepted as aliases for `after` and `before` respectively, but only during the `paging` deprecation window — they retire together with the legacy `paging` response object. New integrations should use the opaque `after`/`before` cursors above and ignore `since_id`/`before_id`.

#### 5.3. List Channel Playlists Response

**Success Response**: `200 OK`

```json
{
  "playlists": [
    {
      "id": "5mpaqv",
      "name": "My Playlist",
      "description": null,
      "updated_at": "2022-04-27T01:22:39.431667Z",
      "video_ids": ["o9lYEq", "o8V2Rb"]
    }
  ],
  "links": {
    "next": "/api/v1/playlists?channel_id=7RXwK8k&page_size=10&before=Q3Vyc29yOjEyMzQ1"
  },
  "pagination": {
    "cursor": "Q3Vyc29yOjEyMzQ1",
    "has_more": true
  },
  "paging": {
    "next": "/api/v1/playlists?channel_id=7RXwK8k&page_size=10&before_id=Dal1amypVY7"
  }
}
```

**Response Envelope**

| Field        | Type      | Nullable | Description                                                                                                                                 |
| ------------ | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `playlists`  | object\[] | ❌        | Array of playlist objects (see the Playlist Object section above)                                                                           |
| `links`      | object    | ❌        | Pagination links. Always present. Follow `next` verbatim; it is `null` on the last page. This is a forward-only feed, so `prev` is omitted. |
| `pagination` | object    | ❌        | Pagination state. Always present.                                                                                                           |
| `paging`     | object    | ✅        | **Deprecated.** Legacy pagination object retained during the migration window. Use `links` + `pagination` instead.                          |

**`links` object**

| Field  | Type          | Description                                                        |
| ------ | ------------- | ------------------------------------------------------------------ |
| `next` | string / null | Path to the next page (opaque). `null` when there is no next page. |

**`pagination` object**

| Field      | Type          | Description                                                                                |
| ---------- | ------------- | ------------------------------------------------------------------------------------------ |
| `cursor`   | string / null | Opaque cursor for the next round; pass it back as `after`/`before`. `null` when exhausted. |
| `has_more` | boolean       | `true` when another page is available.                                                     |

> **Note:** Clients should prefer following `links.next` verbatim. The `paging` object is deprecated and will be removed in a future release; new integrations should not depend on it.

#### 5.4. List Channel Playlists Error Responses

| Status Code        | Description                                                         |
| ------------------ | ------------------------------------------------------------------- |
| `400 Bad Request`  | Missing `channel_id`, conflicting cursors, or invalid parameters    |
| `401 Unauthorized` | Missing, invalid, or expired authentication token                   |
| `403 Forbidden`    | Missing `playlists:read` scope or channel belongs to other business |
| `404 Not Found`    | Channel not found                                                   |

**Error Response Format**:

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

#### 5.5. Examples

**CURL Request**

```bash
curl -X GET "https://api.firework.com/api/v1/playlists?channel_id=7RXwK8k&page_size=10" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Paginated Request (next page)**

```bash
curl -X GET "https://api.firework.com/api/v1/playlists?channel_id=7RXwK8k&page_size=10&before=Q3Vyc29yOjEyMzQ1" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

***

### 6. Create Playlist

Create a playlist in a channel. The authenticated user or OAuth app must have access to the channel's business. If `video_ids` is supplied, every video must belong to the same business as the channel, otherwise the request is rejected with `403 Forbidden`.

**Endpoint**: `POST /api/v1/playlists`\
**Authentication**: Bearer token required\
**Required Scope**: `playlists:write`

#### 6.1. Request Headers

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

#### 6.2. Request Body

| Field         | Type      | Required | Description                                |
| ------------- | --------- | -------- | ------------------------------------------ |
| `channel_id`  | string    | ✅        | Encoded channel ID the playlist belongs to |
| `name`        | string    | ✅        | Playlist name (max 100 characters)         |
| `description` | string    | ❌        | Playlist description (nullable)            |
| `video_ids`   | string\[] | ❌        | Initial encoded video IDs, in order        |

#### 6.3. Create Playlist Response

**Success Response**: `201 Created`

Returns the created playlist object (see the Playlist Object section above).

```json
{
  "id": "5mpaqv",
  "name": "My Playlist",
  "description": "Summer campaign videos",
  "updated_at": "2026-07-02T01:22:39.431667Z",
  "video_ids": ["o9lYEq", "o8V2Rb"]
}
```

#### 6.4. Create Playlist Error Responses

| Status Code                | Description                                                                                                       |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | Missing `channel_id`/`name` or malformed body                                                                     |
| `401 Unauthorized`         | Missing, invalid, or expired authentication token                                                                 |
| `403 Forbidden`            | Missing `playlists:write` scope, channel belongs to another business, or a `video_id` belongs to another business |
| `404 Not Found`            | Channel not found                                                                                                 |
| `422 Unprocessable Entity` | Validation failed (e.g. `name` longer than 100 characters)                                                        |

#### 6.5. Examples

**CURL Request**

```bash
curl -X POST "https://api.firework.com/api/v1/playlists" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "7RXwK8k",
    "name": "My Playlist",
    "description": "Summer campaign videos",
    "video_ids": ["o9lYEq", "o8V2Rb"]
  }'
```

***

### 7. Update Playlist

Update a playlist. Only the fields present in the request body are changed; omitted fields are left untouched. Supplying `video_ids` replaces the playlist's entire video list (and order). Every video in `video_ids` must belong to the playlist's business.

**Endpoint**: `PATCH /api/v1/playlists/{id}`\
**Authentication**: Bearer token required\
**Required Scope**: `playlists:write`

#### 7.1. Request Headers

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

#### 7.2. Path Parameters

| Parameter | Type   | Required | Description         |
| --------- | ------ | -------- | ------------------- |
| `id`      | string | ✅        | Encoded playlist ID |

#### 7.3. Request Body

Only the provided fields are updated.

| Field         | Type      | Required | Description                                               |
| ------------- | --------- | -------- | --------------------------------------------------------- |
| `name`        | string    | ❌        | Playlist name (max 100 characters)                        |
| `description` | string    | ❌        | Playlist description (nullable)                           |
| `video_ids`   | string\[] | ❌        | Full ordered list of videos (replaces the existing order) |

#### 7.4. Update Playlist Response

**Success Response**: `200 OK`

Returns the updated playlist object (see the Playlist Object section above).

```json
{
  "id": "5mpaqv",
  "name": "Renamed Playlist",
  "description": "Updated description",
  "updated_at": "2026-07-02T02:10:11.000000Z",
  "video_ids": ["o8V2Rb", "o9lYEq"]
}
```

#### 7.5. Update Playlist Error Responses

| Status Code                | Description                                                                                                        |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `400 Bad Request`          | Malformed body                                                                                                     |
| `401 Unauthorized`         | Missing, invalid, or expired authentication token                                                                  |
| `403 Forbidden`            | Missing `playlists:write` scope, playlist belongs to another business, or a `video_id` belongs to another business |
| `404 Not Found`            | Playlist not found                                                                                                 |
| `422 Unprocessable Entity` | Validation failed (e.g. `name` longer than 100 characters)                                                         |

#### 7.6. Examples

**CURL Request**

```bash
curl -X PATCH "https://api.firework.com/api/v1/playlists/5mpaqv" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Renamed Playlist",
    "video_ids": ["o8V2Rb", "o9lYEq"]
  }'
```

***

### 8. Delete Playlist

Delete a playlist. The authenticated user or OAuth app must have access to the playlist's business.

**Endpoint**: `DELETE /api/v1/playlists/{id}`\
**Authentication**: Bearer token required\
**Required Scope**: `playlists:write`

#### 8.1. Request Headers

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

#### 8.2. Path Parameters

| Parameter | Type   | Required | Description         |
| --------- | ------ | -------- | ------------------- |
| `id`      | string | ✅        | Encoded playlist ID |

#### 8.3. Delete Playlist Response

**Success Response**: `204 No Content` (empty body)

#### 8.4. Delete Playlist Error Responses

| Status Code        | Description                                                             |
| ------------------ | ----------------------------------------------------------------------- |
| `401 Unauthorized` | Missing, invalid, or expired authentication token                       |
| `403 Forbidden`    | Missing `playlists:write` scope or playlist belongs to another business |
| `404 Not Found`    | Playlist not found                                                      |

#### 8.5. Examples

**CURL Request**

```bash
curl -X DELETE "https://api.firework.com/api/v1/playlists/5mpaqv" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

***

### 9. List Playlist Videos

Retrieve the full video objects for a playlist, in playlist order. Only approved, non-archived videos are returned.

**Endpoint**: `GET /api/v1/playlists/{id}/videos`\
**Authentication**: Bearer token required\
**Required Scope**: `playlists:read` (for OAuth apps; `playlists:write` also satisfies this)

#### 9.1. Request Headers

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

#### 9.2. Path Parameters

| Parameter | Type   | Required | Description         |
| --------- | ------ | -------- | ------------------- |
| `id`      | string | ✅        | Encoded playlist ID |

#### 9.3. Query Parameters

Unlike `GET /api/v1/playlists`, this endpoint uses **page-number pagination** (playlists have an arbitrary, non-monotonic order, so cursor pagination does not apply).

| Parameter   | Type    | Required | Description                                                                         |
| ----------- | ------- | -------- | ----------------------------------------------------------------------------------- |
| `page`      | integer | ❌        | 1-based page number (default: 1)                                                    |
| `page_size` | integer | ❌        | Number of videos per page (default: 10, max: 100; values above the max are clamped) |

#### 9.4. List Playlist Videos Response

**Success Response**: `200 OK`

Returns a paginated list of video objects. See the [Video API](/firework-for-developers/api/videos.md) for the full video object field reference; the object below is abbreviated.

```json
{
  "videos": [
    {
      "id": "o9lYEq",
      "caption": "Summer Lookbook",
      "description": null,
      "access": "public",
      "hashtags": ["summer", "lookbook"],
      "product_ids": ["p8Vq2R"],
      "thumbnail_url": "https://cdn.firework.com/medias/2026/07/01/abc/thumbnail.jpg",
      "video_posters": []
    }
  ],
  "links": {
    "next": "/api/v1/playlists/5mpaqv/videos?page=2&page_size=10",
    "prev": null
  },
  "pagination": {
    "page": 1,
    "page_size": 10,
    "total_pages": 3,
    "total_entries": 24,
    "has_more": true
  },
  "paging": {
    "next": "/api/v1/playlists/5mpaqv/videos?page=2&page_size=10"
  }
}
```

**Response Envelope**

| Field        | Type      | Nullable | Description                                                                                                        |
| ------------ | --------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `videos`     | object\[] | ❌        | Array of video objects (see the [Video API](/firework-for-developers/api/videos.md))                               |
| `links`      | object    | ❌        | Pagination links. Always present.                                                                                  |
| `pagination` | object    | ❌        | Pagination state. Always present.                                                                                  |
| `paging`     | object    | ✅        | **Deprecated.** Legacy pagination object retained during the migration window. Use `links` + `pagination` instead. |

**`links` object**

| Field  | Type          | Description                                               |
| ------ | ------------- | --------------------------------------------------------- |
| `next` | string / null | Path to the next page. `null` when on the last page.      |
| `prev` | string / null | Path to the previous page. `null` when on the first page. |

**`pagination` object**

| Field           | Type    | Description                              |
| --------------- | ------- | ---------------------------------------- |
| `page`          | integer | 1-based current page number.             |
| `page_size`     | integer | Items per page.                          |
| `total_pages`   | integer | Total number of pages.                   |
| `total_entries` | integer | Total number of videos across all pages. |
| `has_more`      | boolean | `true` when another page is available.   |

#### 9.5. List Playlist Videos Error Responses

| Status Code        | Description                                                            |
| ------------------ | ---------------------------------------------------------------------- |
| `401 Unauthorized` | Missing, invalid, or expired authentication token                      |
| `403 Forbidden`    | Missing `playlists:read` scope or playlist belongs to another business |
| `404 Not Found`    | Playlist not found                                                     |

#### 9.6. Examples

**CURL Request**

```bash
curl -X GET "https://api.firework.com/api/v1/playlists/5mpaqv/videos?page=1&page_size=10" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

***

### 10. Add Video to Playlist

Add a single video to a playlist. The video must belong to the same business as the playlist. By default the video is appended to the end; pass `position` to insert it at a specific 0-based index.

**Endpoint**: `POST /api/v1/playlists/{id}/videos`\
**Authentication**: Bearer token required\
**Required Scope**: `playlists:write`

#### 10.1. Request Headers

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

#### 10.2. Path Parameters

| Parameter | Type   | Required | Description         |
| --------- | ------ | -------- | ------------------- |
| `id`      | string | ✅        | Encoded playlist ID |

#### 10.3. Request Body

| Field      | Type    | Required | Description                                           |
| ---------- | ------- | -------- | ----------------------------------------------------- |
| `video_id` | string  | ✅        | Encoded video ID to add                               |
| `position` | integer | ❌        | 0-based insert index; appends to the end when omitted |

#### 10.4. Add Video Response

**Success Response**: `200 OK`

Returns the updated playlist object (see the Playlist Object section above).

```json
{
  "id": "5mpaqv",
  "name": "My Playlist",
  "description": null,
  "updated_at": "2026-07-02T03:00:00.000000Z",
  "video_ids": ["oNewVid", "o9lYEq", "o8V2Rb"]
}
```

#### 10.5. Add Video Error Responses

| Status Code        | Description                                                                                                     |
| ------------------ | --------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`  | Missing `video_id` or malformed body                                                                            |
| `401 Unauthorized` | Missing, invalid, or expired authentication token                                                               |
| `403 Forbidden`    | Missing `playlists:write` scope, playlist belongs to another business, or the video belongs to another business |
| `404 Not Found`    | Playlist not found                                                                                              |

#### 10.6. Examples

**CURL Request**

```bash
curl -X POST "https://api.firework.com/api/v1/playlists/5mpaqv/videos" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "video_id": "oNewVid",
    "position": 0
  }'
```

***

### 11. Reorder Playlist Videos

Replace the playlist's video order with a full ordered list. Every video in `video_ids` must belong to the playlist's business.

**Endpoint**: `PUT /api/v1/playlists/{id}/videos`\
**Authentication**: Bearer token required\
**Required Scope**: `playlists:write`

#### 11.1. Request Headers

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

#### 11.2. Path Parameters

| Parameter | Type   | Required | Description         |
| --------- | ------ | -------- | ------------------- |
| `id`      | string | ✅        | Encoded playlist ID |

#### 11.3. Request Body

| Field       | Type      | Required | Description                                   |
| ----------- | --------- | -------- | --------------------------------------------- |
| `video_ids` | string\[] | ✅        | Full ordered list of the playlist's video IDs |

#### 11.4. Reorder Response

**Success Response**: `200 OK`

Returns the updated playlist object (see the Playlist Object section above).

```json
{
  "id": "5mpaqv",
  "name": "My Playlist",
  "description": null,
  "updated_at": "2026-07-02T03:15:00.000000Z",
  "video_ids": ["o8V2Rb", "o9lYEq"]
}
```

#### 11.5. Reorder Error Responses

| Status Code        | Description                                                                                                   |
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`  | Missing `video_ids`, wrong type, or malformed body                                                            |
| `401 Unauthorized` | Missing, invalid, or expired authentication token                                                             |
| `403 Forbidden`    | Missing `playlists:write` scope, playlist belongs to another business, or a video belongs to another business |
| `404 Not Found`    | Playlist not found                                                                                            |

#### 11.6. Examples

**CURL Request**

```bash
curl -X PUT "https://api.firework.com/api/v1/playlists/5mpaqv/videos" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "video_ids": ["o8V2Rb", "o9lYEq"]
  }'
```

***

### 12. Remove Video from Playlist

Remove a single video from a playlist. Removing a video that is not in the playlist is a no-op and still returns `204`.

**Endpoint**: `DELETE /api/v1/playlists/{id}/videos/{video_id}`\
**Authentication**: Bearer token required\
**Required Scope**: `playlists:write`

#### 12.1. Request Headers

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

#### 12.2. Path Parameters

| Parameter  | Type   | Required | Description         |
| ---------- | ------ | -------- | ------------------- |
| `id`       | string | ✅        | Encoded playlist ID |
| `video_id` | string | ✅        | Encoded video ID    |

#### 12.3. Remove Video Response

**Success Response**: `204 No Content` (empty body)

#### 12.4. Remove Video Error Responses

| Status Code        | Description                                                             |
| ------------------ | ----------------------------------------------------------------------- |
| `401 Unauthorized` | Missing, invalid, or expired authentication token                       |
| `403 Forbidden`    | Missing `playlists:write` scope or playlist belongs to another business |
| `404 Not Found`    | Playlist not found                                                      |

#### 12.5. Examples

**CURL Request**

```bash
curl -X DELETE "https://api.firework.com/api/v1/playlists/5mpaqv/videos/o9lYEq" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

***

### 13. Pin / Unpin a Video

Pin a video to the top of a playlist, or remove an existing pin. Pinning is only supported on playlists that have an **AI feed enabled**; calling these endpoints on a playlist without an AI feed returns `422 Unprocessable Entity`. The video must already be in the playlist, otherwise `404 Not Found` is returned.

**Endpoints**:

* `POST /api/v1/playlists/{id}/videos/{video_id}/pin` — pin the video
* `DELETE /api/v1/playlists/{id}/videos/{video_id}/pin` — unpin the video

**Authentication**: Bearer token required\
**Required Scope**: `playlists:write`

#### 13.1. Request Headers

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

#### 13.2. Path Parameters

| Parameter  | Type   | Required | Description         |
| ---------- | ------ | -------- | ------------------- |
| `id`       | string | ✅        | Encoded playlist ID |
| `video_id` | string | ✅        | Encoded video ID    |

#### 13.3. Pin / Unpin Response

**Success Response**: `204 No Content` (empty body)

#### 13.4. Pin / Unpin Error Responses

| Status Code                | Description                                                             |
| -------------------------- | ----------------------------------------------------------------------- |
| `401 Unauthorized`         | Missing, invalid, or expired authentication token                       |
| `403 Forbidden`            | Missing `playlists:write` scope or playlist belongs to another business |
| `404 Not Found`            | Playlist not found, or the video is not in the playlist                 |
| `422 Unprocessable Entity` | The playlist does not have an AI feed enabled                           |

#### 13.5. Examples

**Pin a Video**

```bash
curl -X POST "https://api.firework.com/api/v1/playlists/5mpaqv/videos/o9lYEq/pin" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Unpin a Video**

```bash
curl -X DELETE "https://api.firework.com/api/v1/playlists/5mpaqv/videos/o9lYEq/pin" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
