# Videos

### 1. Overview <a href="#id-1.-overview" id="id-1.-overview"></a>

The Firework Video API allows you to upload videos to the Firework platform programmatically. This API supports video file uploads with rich metadata including product associations.

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

### 2. Authentication <a href="#id-2.-authentication" id="id-2.-authentication"></a>

The Firework Video 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)

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

***

### 3. Endpoint Summary <a href="#id-3.-endpoint-summary" id="id-3.-endpoint-summary"></a>

| Endpoint                                   | Scope          | Notes                                           |
| ------------------------------------------ | -------------- | ----------------------------------------------- |
| `POST /api/v1/upload_signatures`           | `videos:write` | Get pre-signed credentials for S3 upload        |
| `POST /api/v1/upload_multipart/signatures` | `videos:write` | Initiate multipart upload and get signed parts  |
| `POST /api/v1/upload_multipart/complete`   | `videos:write` | Complete a multipart upload with ETags          |
| `POST /api/v1/videos`                      | `videos:write` | Video creation with file upload                 |
| `POST /api/v1/videos`                      | `videos:write` | Video creation from URL (sync, default)         |
| `POST /api/v1/videos`                      | `videos:write` | Video creation from URL (async: `"async":true`) |
| `POST /api/v1/videos`                      | `videos:write` | Video creation from S3 key (application/json)   |
| `GET /api/v1/videos/imports/{id}`          | `videos:read`  | Get video import job status                     |
| `GET /api/v1/videos/{id}`                  | `videos:read`  | Get video by ID                                 |
| `PATCH /api/v1/videos/{id}`                | `videos:write` | Video updates                                   |

***

### 4. Upload Signature (Single File) <a href="#id-4.-upload-signature-single-file" id="id-4.-upload-signature-single-file"></a>

Get pre-signed credentials to upload a video directly to AWS S3 using a single POST request. This enables a two-step upload process suitable for files under \~100MB.

> For files over 100MB, use the [Multipart Upload API](#id-5.-multipart-upload) instead, which supports parallel and resumable uploads.

**Upload Flow:**

```
┌──────────┐     1. Get Signature      ┌──────────────┐
│  Client  │ ─────────────────────────▶│  Firework    │
│          │◀───────────────────────── │  API         │
└──────────┘   (signature + S3 key)    └──────────────┘
     │
     │  2. Upload file to S3
     │     (using signature)
     ▼
┌──────────┐
│  AWS S3  │
└──────────┘
     │
     │  3. Create video with S3 key
     ▼
┌──────────┐                           ┌──────────────┐
│  Client  │ ─────────────────────────▶│  Firework    │
│          │◀───────────────────────── │  API         │
└──────────┘      (video created)      └──────────────┘

```

**Endpoint**: `POST /api/v1/upload_signatures`\
**Authentication**: Bearer token required\
**Scope**: `videos:write`

#### 4.1. Request Headers <a href="#id-4.1.-request-headers" id="id-4.1.-request-headers"></a>

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

#### 4.2. Request Body <a href="#id-4.2.-request-body" id="id-4.2.-request-body"></a>

| Parameter    | Type   | Required | Description                                                  |
| ------------ | ------ | -------- | ------------------------------------------------------------ |
| `filename`   | string | ✅        | The name of the video file (e.g., `"my_video.mp4"`)          |
| `mime_type`  | string | ✅        | The MIME type of the video: `video/mp4` or `video/quicktime` |
| `channel_id` | string | ✅        | The encoded channel ID where the video will be uploaded      |

#### 4.3. Video Limits <a href="#id-4.3.-video-limits" id="id-4.3.-video-limits"></a>

The upload signature enforces the following limits:

| Limit                 | Value     |
| --------------------- | --------- |
| **Minimum file size** | 25 KB     |
| **Maximum file size** | 5 GB      |
| **Minimum duration**  | 3 seconds |
| **Maximum duration**  | 1 hour    |

#### 4.4. Upload Signature Response <a href="#id-4.4.-upload-signature-response" id="id-4.4.-upload-signature-response"></a>

**Success Response**: `201 Created`

| Field        | Type   | Description                                                                       |
| ------------ | ------ | --------------------------------------------------------------------------------- |
| `key`        | string | The S3 object key where the file will be stored. **Save this** for video creation |
| `post_url`   | string | The S3 URL to POST the file to                                                    |
| `policy`     | string | Base64-encoded policy document                                                    |
| `signature`  | string | The AWS Signature V4 value (`X-Amz-Signature`)                                    |
| `date`       | string | The signing date (`X-Amz-Date`), e.g., `"20250129T120000Z"`                       |
| `credential` | string | The AWS credential scope (`X-Amz-Credential`)                                     |
| `algorithm`  | string | Always `"AWS4-HMAC-SHA256"`                                                       |
| `acl`        | string | Always `"private"`                                                                |

#### 4.5. Upload Signature Error Responses <a href="#id-4.5.-upload-signature-error-responses" id="id-4.5.-upload-signature-error-responses"></a>

| Status Code        | Description                                                          |
| ------------------ | -------------------------------------------------------------------- |
| `400 Bad Request`  | Invalid `mime_type` - only `video/mp4` and `video/quicktime` allowed |
| `401 Unauthorized` | Invalid or missing authentication token                              |
| `403 Forbidden`    | Insufficient permissions to upload to the specified channel          |
| `404 Not Found`    | Channel not found                                                    |

#### 4.6. Uploading to S3 <a href="#id-4.6.-uploading-to-s3" id="id-4.6.-uploading-to-s3"></a>

After receiving the signature, upload the file directly to S3 using a `multipart/form-data` POST request.

> ⚠️ **Important**: The form fields must be sent in the correct order, with the `file` field **last**.

**Required Form Fields (in order):**

| Field              | Value                             |
| ------------------ | --------------------------------- |
| `key`              | From signature response           |
| `acl`              | From signature response           |
| `X-Amz-Algorithm`  | From signature `algorithm`        |
| `X-Amz-Credential` | From signature `credential`       |
| `X-Amz-Date`       | From signature `date`             |
| `Policy`           | From signature `policy`           |
| `X-Amz-Signature`  | From signature `signature`        |
| `Content-Type`     | Same as request `mime_type`       |
| `file`             | The video file (**must be last**) |

**S3 Response:**

| Status | Description                                                               |
| ------ | ------------------------------------------------------------------------- |
| `204`  | Success - file uploaded                                                   |
| `400`  | Bad request - file size outside limits (< 25 KB or > 5 GB), or form error |
| `403`  | Forbidden - signature invalid or expired (expires after 60 min)           |

#### 4.7. Upload Signature Examples <a href="#id-4.7.-upload-signature-examples" id="id-4.7.-upload-signature-examples"></a>

**4.7.1. Get Signature Request**

```
curl -X \
 POST "<https://api.firework.com/api/v1/upload_signatures>" \ -H \
 "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H \
 "Content-Type: application/json" \ -d '{ "filename": "product_demo.mp4", "mime_type": "video/mp4", "channel_id": "X6Xqd6W" }'
```

**4.7.2. Get Signature Response**

```
{
    "key": "medias/business/AbCdEfG/channel/HiJkLmN/videos/public/original/1738152000-abcdefgh-product_demo.mp4",
    "post_url": "<https://firework-assets.s3-accelerate.amazonaws.com>",
    "policy": "eyJjb25kaXRpb25zIjpbeyJYLUFtei1BbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In0sLi4u",
    "signature": "a1b2c3d4e5f6g7h8i9j0...",
    "date": "20250129T120000Z",
    "credential": "AKIAIOSFODNN7EXAMPLE/20250129/us-west-2/s3/aws4_request",
    "algorithm": "AWS4-HMAC-SHA256",
    "acl": "private"
}
```

**4.7.3. Upload to S3**

Use the `post_url` from the Get Signature response as the upload endpoint. Submit a `POST` request with the signature fields and your video file:

```
curl -X \
 POST "{post_url}" \ -F \
 "key=medias/business/AbCdEfG/channel/HiJkLmN/videos/public/original/1738152000-abcdefgh-product_demo.mp4" \ -F \
 "acl=private" \ -F \
 "X-Amz-Algorithm=AWS4-HMAC-SHA256" \ -F \
 "X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20250129/us-west-2/s3/aws4_request" \ -F \
 "X-Amz-Date=20250129T120000Z" \ -F \
 "Policy=eyJjb25kaXRpb25zIjpbeyJYLUFtei1BbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In0sLi4u" \ -F \
 "X-Amz-Signature=a1b2c3d4e5f6g7h8i9j0..." \ -F \
 "Content-Type=video/mp4" \ -F \
 "file=@/path/to/product_demo.mp4"
```

***

### 5. Multipart Upload <a href="#id-5.-multipart-upload" id="id-5.-multipart-upload"></a>

Upload large video files (100MB+) to AWS S3 using multipart upload. This splits the file into multiple parts that can be uploaded **in parallel** and **resumed** if a part fails, avoiding gateway timeouts.

> ⚠️ **AWS S3 Part Size Requirements**:
>
> * Each part (except the last) must be **≥ 5 MB** (5,242,880 bytes)
> * Last part can be any size
> * Maximum 100 parts per upload
> * If parts are too small, the complete step will fail with "Multipart upload failed"

**Multipart Upload Flow:**

```
┌──────────┐  1. Initiate Multipart     ┌──────────────┐
│  Client  │ ──────────────────────────▶│  Firework    │
│          │◀────────────────────────── │  API         │
└──────────┘  (key, upload_id, parts    └──────────────┘
               with presigned URLs)
     │
     │  2. Upload each part to S3
     │     (using presigned PUT URLs)
     │     ← can be done in parallel →
     ▼
┌──────────┐
│  AWS S3  │  ← returns ETag per part
└──────────┘
     │
     │  3. Complete multipart upload
     ▼
┌──────────┐                            ┌──────────────┐
│  Client  │ ──────────────────────────▶│  Firework    │
│          │◀────────────────────────── │  API         │
└──────────┘      (204 No Content)      └──────────────┘
     │
     │  4. Create video with S3 key
     ▼
┌──────────┐                            ┌──────────────┐
│  Client  │ ──────────────────────────▶│  Firework    │
│          │◀────────────────────────── │  API         │
└──────────┘      (video created)       └──────────────┘
```

#### 5.1. Initiate Multipart Upload <a href="#id-5.1.-initiate-multipart-upload" id="id-5.1.-initiate-multipart-upload"></a>

Start a multipart upload session. Returns an `upload_id` and presigned URLs for each part.

**Endpoint**: `POST /api/v1/upload_multipart/signatures`\
**Authentication**: Bearer token required\
**Scope**: `videos:write`

**5.1.1. Request Headers**

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

**5.1.2. Request Body**

| Parameter     | Type    | Required | Description                                                  |
| ------------- | ------- | -------- | ------------------------------------------------------------ |
| `filename`    | string  | ✅        | The name of the video file (e.g., `"my_video.mp4"`)          |
| `mime_type`   | string  | ✅        | The MIME type of the video: `video/mp4` or `video/quicktime` |
| `channel_id`  | string  | ✅        | The encoded channel ID where the video will be uploaded      |
| `parts_count` | integer | ✅        | Number of parts to split the file into (1-100)               |

> **Choosing** `parts_count`: Calculate based on your file size to ensure each part is ≥ 5 MB:
>
> * **Formula**: `parts_count = file_size_mb / 5` (round down)
> * **Example 1**: 100 MB file → max 20 parts (100 / 5 = 20)
> * **Example 2**: 500 MB file → max 100 parts (500 / 5 = 100)
> * **Example 3**: 15 MB file → max 3 parts (15 / 5 = 3)
> * **Important**: Each part (except last) must be ≥ 5 MB, or upload will fail

**5.1.3. Initiate Response**

**Success Response**: `201 Created`

| Field       | Type   | Description                                                                       |
| ----------- | ------ | --------------------------------------------------------------------------------- |
| `key`       | string | The S3 object key where the file will be stored. **Save this** for video creation |
| `upload_id` | string | The multipart upload session ID. Required for uploading parts and completion      |
| `parts`     | array  | Array of part objects, one per requested part                                     |

**Each element in** `parts`:

| Field               | Type    | Description                                          |
| ------------------- | ------- | ---------------------------------------------------- |
| `part`              | integer | The part number (1-based)                            |
| `signature`         | object  | Signature object containing the presigned PUT URL    |
| `signature.put_url` | string  | Presigned URL to PUT-upload this part directly to S3 |
| `signature.key`     | string  | The S3 object key                                    |

**5.1.4. Initiate Error Responses**

| Status Code        | Description                                                          |
| ------------------ | -------------------------------------------------------------------- |
| `400 Bad Request`  | Invalid `mime_type` - only `video/mp4` and `video/quicktime` allowed |
| `400 Bad Request`  | Invalid `parts_count` - must be between 1 and 100                    |
| `401 Unauthorized` | Invalid or missing authentication token                              |
| `403 Forbidden`    | Insufficient permissions to upload to the specified channel          |
| `404 Not Found`    | Channel not found                                                    |

#### 5.2. Upload Parts to S3 <a href="#id-5.2.-upload-parts-to-s3" id="id-5.2.-upload-parts-to-s3"></a>

After initiating the multipart upload, upload each part directly to S3 using the presigned PUT URLs from the response.

> **Parts can be uploaded in parallel** for faster uploads. Each part returns an `ETag` header that you must save for the completion step.

**For each part:**

```
curl -X \
 PUT "{part.signature.put_url}" \ -H \
 "Content-Type: video/mp4" \ --data \
-binary @part_file
```

**S3 Response:**

| Status | Description                                                   |
| ------ | ------------------------------------------------------------- |
| `200`  | Success - part uploaded. **Save the** `ETag` response header. |
| `403`  | Forbidden - signature invalid or expired                      |

> **Important**: The `ETag` header value returned by S3 for each part is required for the completion step. It is typically a quoted MD5 hash, e.g., `"d41d8cd98f00b204e9800998ecf8427e"`.

#### 5.3. Complete Multipart Upload <a href="#id-5.3.-complete-multipart-upload" id="id-5.3.-complete-multipart-upload"></a>

After all parts have been uploaded to S3, call this endpoint to assemble them into the final file.

**Endpoint**: `POST /api/v1/upload_multipart/complete`\
**Authentication**: Bearer token required\
**Scope**: `videos:write`

**5.3.1. Request Headers**

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

**5.3.2. Request Body**

| Parameter   | Type   | Required | Description                                           |
| ----------- | ------ | -------- | ----------------------------------------------------- |
| `key`       | string | ✅        | The S3 key returned from the initiate step            |
| `upload_id` | string | ✅        | The upload session ID returned from the initiate step |
| `parts`     | array  | ✅        | Array of completed part objects (see below)           |

**Each element in** `parts`:

| Field  | Type    | Required | Description                                                              |
| ------ | ------- | -------- | ------------------------------------------------------------------------ |
| `part` | integer | ✅        | The part number (1-100, must match the initiate response, no duplicates) |
| `etag` | string  | ✅        | The ETag returned by S3 when the part was uploaded (non-empty)           |

> **Validation Rules**: The `parts` array must be non-empty, contain at most 100 elements, have no duplicate part numbers, and each `etag` must be a non-empty string.

**5.3.3. File Size Validation**

After assembly, the server validates the total file size against the same limits used for single-file uploads:

| Limit                 | Value |
| --------------------- | ----- |
| **Minimum file size** | 25 KB |
| **Maximum file size** | 5 GB  |

If the assembled file is outside these bounds, the server deletes the object from S3 and returns an error with a descriptive message: `"File too small (min 25KB)"` (400) or `"File too large (max 5GB)"` (413).

**5.3.4. Complete Response**

**Success Response**: `204 No Content`

No response body. The file has been assembled on S3 and is ready to be used with the [Create Video API](#id-6.-create-video) using the `s3_key` parameter.

**5.3.5. Complete Error Responses**

| Status Code                    | Error Message                 | Description                                                                                                                                                     |
| ------------------------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`              |                               | Invalid or missing parameters (key, upload\_id, or parts), empty parts list, duplicate part numbers, invalid part numbers (must be 1-100), or empty etag values |
| `400 Bad Request`              | `"File too small (min 25KB)"` | Assembled file is below minimum size (25 KB)                                                                                                                    |
| `400 Bad Request`              | `"Multipart upload failed"`   | AWS rejected the upload (e.g., parts < 5 MB)                                                                                                                    |
| `401 Unauthorized`             |                               | Invalid or missing authentication token                                                                                                                         |
| `403 Forbidden`                |                               | Insufficient permissions                                                                                                                                        |
| `413 Request Entity Too Large` | `"File too large (max 5GB)"`  | Assembled file exceeds the maximum size (5 GB)                                                                                                                  |
| `500 Internal Server Error`    |                               | Unexpected server error during upload completion - retry the request                                                                                            |

> ⚠️ **Common Failure**: If you receive "Multipart upload failed" with a `400` status, it's usually because one or more parts (except the last) were smaller than 5 MB. Recalculate `parts_count` to ensure each part is at least 5 MB. A `500` status indicates a transient server issue - retry the request.

#### 5.4. Multipart Upload Examples <a href="#id-5.4.-multipart-upload-examples" id="id-5.4.-multipart-upload-examples"></a>

**5.4.1. Step 1: Initiate Multipart Upload**

```
curl -X \
 POST "<https://api.firework.com/api/v1/upload_multipart/signatures>" \ -H \
 "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H \
 "Content-Type: application/json" \ -d '{ "filename": "large_product_demo.mp4", "mime_type": "video/mp4", "channel_id": "X6Xqd6W", "parts_count": 3 }'
```

**Response:**

```
{
    "key": "medias/business/AbCdEfG/channel/HiJkLmN/videos/public/original/1738152000-abcdefgh-large_product_demo.mp4",
    "upload_id": "VXBsb2FkIElEIGZvciBlbHZpbmcncyBt",
    "parts": [
    {
        "part": 1,
        "signature":
        {
            "put_url": "<https://firework-assets.s3-accelerate.amazonaws.com/medias/...?uploadId=VXBsb2...&partNumber=1&X-Amz-Signature=abc123...>",
            "key": "medias/business/AbCdEfG/channel/HiJkLmN/videos/public/original/1738152000-abcdefgh-large_product_demo.mp4"
        }
    },
    {
        "part": 2,
        "signature":
        {
            "put_url": "<https://firework-assets.s3-accelerate.amazonaws.com/medias/...?uploadId=VXBsb2...&partNumber=2&X-Amz-Signature=def456...>",
            "key": "medias/business/AbCdEfG/channel/HiJkLmN/videos/public/original/1738152000-abcdefgh-large_product_demo.mp4"
        }
    },
    {
        "part": 3,
        "signature":
        {
            "put_url": "<https://firework-assets.s3-accelerate.amazonaws.com/medias/...?uploadId=VXBsb2...&partNumber=3&X-Amz-Signature=ghi789...>",
            "key": "medias/business/AbCdEfG/channel/HiJkLmN/videos/public/original/1738152000-abcdefgh-large_product_demo.mp4"
        }
    }]
}
```

**5.4.2. Step 2: Upload Parts to S3 (can be parallel)**

Split your file and upload each part using its presigned URL:

```
# Upload part 1 curl -X \
 PUT "<https://firework-assets.s3-accelerate.amazonaws.com/medias/...?uploadId=VXBsb2...&partNumber=1&X-Amz-Signature=abc123...>" \ -H \
 "Content-Type: video/mp4" \ --data \
-binary @part1.bin # Save ETag from response headers: "a1b2c3d4..." # Upload part 2 (in parallel) curl -X \
 PUT "<https://firework-assets.s3-accelerate.amazonaws.com/medias/...?uploadId=VXBsb2...&partNumber=2&X-Amz-Signature=def456...>" \ -H \
 "Content-Type: video/mp4" \ --data \
-binary @part2.bin # Save ETag from response headers: "e5f6g7h8..." # Upload part 3 (in parallel) curl -X \
 PUT "<https://firework-assets.s3-accelerate.amazonaws.com/medias/...?uploadId=VXBsb2...&partNumber=3&X-Amz-Signature=ghi789...>" \ -H \
 "Content-Type: video/mp4" \ --data \
-binary @part3.bin # Save ETag from response headers: "i9j0k1l2..."
```

> **Tip**: To get the ETag from curl, use `-i` or `-D -` to include response headers in the output.

**5.4.3. Step 3: Complete Multipart Upload**

```
curl -X \
 POST "<https://api.firework.com/api/v1/upload_multipart/complete>" \ -H \
 "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H \
 "Content-Type: application/json" \ -d '{ "key": "medias/business/AbCdEfG/channel/HiJkLmN/videos/public/original/1738152000-abcdefgh-large_product_demo.mp4", "upload_id": "VXBsb2FkIElEIGZvciBlbHZpbmcncyBt", "parts": [ {"part": 1, "etag": "\"a1b2c3d4...\""}, {"part": 2, "etag": "\"e5f6g7h8...\""}, {"part": 3, "etag": "\"i9j0k1l2...\""} ] }'
```

**Response**: `204 No Content`

**5.4.4. Step 4: Create Video with S3 Key**

Use the `key` from the initiate step to create the video:

```
curl -X \
 POST "<https://api.firework.com/api/v1/videos>" \ -H \
 "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H \
 "Content-Type: application/json" \ -d '{ "s3_key": "medias/business/AbCdEfG/channel/HiJkLmN/videos/public/original/1738152000-abcdefgh-large_product_demo.mp4", "channel_id": "X6Xqd6W", "caption": "Large Product Demo", "access": "public" }'
```

See [Section 6. Create Video](#id-6.-create-video) for full details on video creation.

***

### 6. Create Video <a href="#id-6.-create-video" id="id-6.-create-video"></a>

Upload a new video to the Firework platform. Supports direct file upload, video import from URL, and creation from pre-uploaded S3 key.

**Endpoint**: `POST /api/v1/videos`\
**Authentication**: Bearer token required\
**Scope**: `videos:write`\
**Rate Limit**: 20 videos per 5 minutes per channel\
**Content Type**: `multipart/form-data` or `application/json`

#### 6.1. Supported Video Files <a href="#id-6.1.-supported-video-files" id="id-6.1.-supported-video-files"></a>

* **MIME Types**: `video/mp4`, `video/quicktime`
* **File Extensions**: `.mp4`, `.mov`
* **Maximum Size**: 5GB

#### 6.2. Request Headers <a href="#id-6.2.-request-headers" id="id-6.2.-request-headers"></a>

| Name            | Description                                                                            | Required |
| --------------- | -------------------------------------------------------------------------------------- | -------- |
| `Authorization` | Bearer token: `Bearer {ACCESS_TOKEN}`                                                  | ✅        |
| `Content-Type`  | `multipart/form-data` (file upload) or `application/json` (URL import / S3 key upload) | ✅        |

#### 6.3. Request Body <a href="#id-6.3.-request-body" id="id-6.3.-request-body"></a>

**Option 1: File Upload (`multipart/form-data`)**

Upload the video file directly. Best for small files (under 100MB).

| Parameter  | Type   | Required | Description                                    |
| ---------- | ------ | -------- | ---------------------------------------------- |
| `metadata` | string | ✅        | JSON-encoded video metadata (see schema below) |
| `file`     | file   | ✅        | Video file to upload                           |

**Option 2: URL Import (`application/json`)**

Import a video from a publicly accessible URL. Supports two modes:

* **Sync mode** (default): The file is downloaded and uploaded to S3 within the request. Returns `201 Created` with a video object. Best for small files, but may time out on large files or slow URLs.
* **Async mode** (`"async": true`): Returns `202 Accepted` immediately with a video import tracking object. The file is downloaded and processed in the background. **Recommended for large files or unreliable URLs.**

| Parameter                             | Type               | Required           | Default            | Description                                                 |
| ------------------------------------- | ------------------ | ------------------ | ------------------ | ----------------------------------------------------------- |
| `url`                                 | string             | ✅                  | None               | Publicly accessible video URL (http or https)               |
| `async`                               | boolean            | ❌                  | `false`            | Set to `true` for async processing (returns `202 Accepted`) |
| All other fields from metadata schema | <ul><li></li></ul> | <ul><li></li></ul> | <ul><li></li></ul> | See [metadata schema](#id-6.4.-metadata-schema)             |

**URL Validation Rules:**

* Must be `<http://`> or `<https://`> scheme
* Must have a valid hostname (with at least one dot)
* Must have a path component
* The remote server must respond with a `Content-Type` header of `video/mp4`, `video/quicktime`, or `application/octet-stream`. When `application/octet-stream` is returned, the URL path must end with a video file extension (`.mp4` or `.mov`)
* The remote server must include a `Content-Length` header
* Maximum file size: 5 GB
* Video duration: 3 seconds to 1 hour

**Sync mode** (`"async"` omitted or `false`): Returns `201 Created` with a video object (same as file upload / S3 key). See [Section 6.8.1](#id-6.8.-create-video-response).

**Async mode** (`"async": true`): Returns `202 Accepted` with a video import object. See [Section 6.8.2](#id-6.8.-create-video-response).

> **Async Processing Flow:**
>
> 1. API validates the URL format, creates a video import job, and enqueues a background worker
> 2. Returns `202 Accepted` with the import job `id` and `status: "running"`
> 3. Background worker downloads the file to S3, creates the video record, and triggers transcoding. The `video_id` is populated at this point while `status` remains `"running"`
> 4. When transcoding completes: `status` becomes `"completed"` and `completed_at` is set
> 5. If processing fails (e.g., download error, invalid format, duration out of range): `status` becomes `"errored"`
>
> **Tracking progress:**
>
> * **Webhooks (recommended):** Configure `video_created`, `video_updated`, and `video_import_failed` webhooks to receive push notifications. The webhook payload includes the `import_id` so you can correlate events back to this import job.
> * **Polling:** Use `GET /api/v1/videos/imports/{id}` to poll for status. We recommend polling at 5–10 second intervals.

**Option 3: S3 Key Upload (`application/json`) - Recommended for Large Files**

Create a video from a file already uploaded to S3 via the [Upload Signature API](#id-4.-upload-signature-single-file) or the [Multipart Upload API](#id-5.-multipart-upload). **Recommended for files over 100MB** to avoid gateway timeouts.

| Parameter                             | Type               | Required           | Default            | Description                                   |
| ------------------------------------- | ------------------ | ------------------ | ------------------ | --------------------------------------------- |
| `s3_key`                              | string             | ✅                  | None               | The S3 key returned from Upload Signature API |
| All other fields from metadata schema | <ul><li></li></ul> | <ul><li></li></ul> | <ul><li></li></ul> | See metadata schema section                   |

#### 6.4. Metadata Schema <a href="#id-6.4.-metadata-schema" id="id-6.4.-metadata-schema"></a>

| Field                         | Type      | Required | Default       | Description                                     | Remarks                                                    |
| ----------------------------- | --------- | -------- | ------------- | ----------------------------------------------- | ---------------------------------------------------------- |
| `channel_id`                  | string    | ✅        | None          | Encoded channel ID where video will be uploaded |                                                            |
| `caption`                     | string    | ✅        | None          | Video title/caption                             |                                                            |
| `description`                 | string    | ❌        | None          | Video description                               |                                                            |
| `access`                      | string    | ❌        | `"public"`    | Video visibility: `"public"` or `"private"`     |                                                            |
| `archived_at`                 | string    | ❌        | None          | ISO 8601 timestamp when video was archived      |                                                            |
| `audio_disabled`              | boolean   | ❌        | `false`       | Whether audio is disabled for the video         |                                                            |
| `hashtags`                    | string\[] | ❌        | `[]`          | Array of hashtag strings                        |                                                            |
| `business_store_id`           | string    | ❌        | use first one | Encoded business store ID                       | See products tagging rules                                 |
| `product_ids`                 | string\[] | ❌        | `[]`          | Array of product identifiers                    | See products tagging rules                                 |
| `custom_fields`               | object    | ❌        | `{}`          | Custom key-value metadata                       | See Metafields spec                                        |
| `display_social_attributions` | boolean   | ❌        | `false`       | Display social media attribution on video       | Requires `external_media` when `true`                      |
| `external_media`              | object    | ❌        | None          | Social media source metadata                    | See External Media Schema below                            |
| `poster_url`                  | string    | ❌        | None          | URL to a custom poster image                    | Set to `null` or `""` to remove. See Custom Poster section |
| `video_hidden`                | boolean   | ❌        | `false`       | Hide video from PDP (Product Detail Page)       | Applies to all product listings. See Product Tagging Rules |

#### 6.5. Custom Poster <a href="#id-6.5.-custom-poster" id="id-6.5.-custom-poster"></a>

The `poster_url` field allows you to specify a custom poster image for the video instead of using the auto-generated one.

**Supported Formats:**

* `jpg`, `png`

**Validation Rules:**

* Must be a valid, publicly accessible URL
* URL must have a valid image file extension (`.jpg`, `.png`)
* The image will be downloaded and stored on Firework's CDN

**Behavior:**

* When provided during video creation, the custom poster replaces the auto-generated poster
* When provided during video update, the custom poster replaces any existing poster
* To remove a custom poster, set `poster_url` to `null` or an empty string `""`
* Omit the field entirely to preserve the existing poster

**Examples:**

Set a custom poster:

```
{
    "channel_id": "X6Xqd6W",
    "caption": "My Video",
    "poster_url": "<https://example.com/my-custom-poster.jpg>"
}
```

Remove the custom poster:

```
{
    "channel_id": "X6Xqd6W",
    "caption": "My Video",
    "poster_url": null
}
```

#### 6.6 Product Identifiers: <a href="#id-6.6-product-identifiers" id="id-6.6-product-identifiers"></a>

The `product_ids` array accepts product identifiers that can be:

* Encoded Firework product ID
* External product ID
* External product unit ID
* Product unit GTIN
* Product unit SKU
* Product unit MPN

**Product Tagging Rules:**

* **When** `product_ids` is provided:
  * While you can use product unit identifiers, it will only tag the **product** to the video
  * It will replace existing products with the specified ones. For example, if a video is currently tagged with product A (`external ID "123"`) and product B (`external ID "234"`), using `product_ids: ["123", "567"]` will:
    * Keep product A tagged to the video
    * Untag product B from the video
    * Tag product C (`external ID "567"`) to the video
  * An empty array `product_ids: []` will untag all products from the video
  * If a specified product identifier cannot be found in the business store, it will be **silently skipped**. Only the valid, resolvable products will be tagged to the video. No error is returned for unrecognized product IDs.
  * Duplicate product identifiers (including the same product referenced by different identifier types) will be silently deduplicated
  * The order of products will follow the order of the array. The sort ID will be set to match the order of the `product_ids` array.

**Examples:**

**Example 1: Tag products using external IDs**

```
{
    "product_ids": ["SHOE-001", "SHIRT-123", "BAG-456"],
    "business_store_id": "encoded_store_id"
}
```

This will tag 3 products to the video in the specified order.

**Example 2: Tag products using Firework product IDs**

```
{
    "product_ids": ["abc123", "def456"],
    "business_store_id": "encoded_store_id"
}
```

This will tag 2 products using their encoded Firework IDs.

**Example 3: Mix of identifier types**

```
{
    "product_ids": ["SHOE-001", "071249656457", "abc123"],
    "business_store_id": "encoded_store_id"
}
```

This uses external ID, GTIN, and Firework ID respectively.

**Example 4: Replace existing product tags**

```
// Video currently has products: ["OLD-001", "OLD-002"] 
{
    "product_ids": ["NEW-001", "OLD-001"],
    "business_store_id": "encoded_store_id"
}
// Result: Video will have products ["NEW-001", "OLD-001"] only 
// "OLD-002" gets untagged, "NEW-001" gets added
```

**Example 5: Untag all products**

```
{
    "product_ids": [],
    "business_store_id": "encoded_store_id"
}
```

This removes all product tags from the video.

**Example 6: Using product unit identifiers**

```
{
    "product_ids": ["UNIT-External-ID-1", "UNIT-External-ID-2"],
    "business_store_id": "encoded_store_id"
}
```

Even though these are unit IDs, only the related **product** get tagged to the video not **product unit**.

**Example 7: Create video with hidden products (hide from PDP)**

```
{
    "product_ids": ["SHOE-001", "SHIRT-123"],
    "business_store_id": "encoded_store_id",
    "video_hidden": true
}
```

This tags products to the video but hides it from the Product Detail Page.

**Example 8: Hide existing video from PDP (update without replacing products)**

```
{
    "video_hidden": true
}
```

When sent to `PATCH /api/v1/videos/{id}` without `product_ids`, this bulk-updates all existing product listings to be hidden.

**Example 9: Un-hide video on PDP**

```
{
    "video_hidden": false
}
```

Sets all existing product listings back to visible on PDP.

* `video_hidden` behavior:
  * When `video_hidden` is provided with `product_ids`, all created/replaced product listings will be marked with the given value
  * When `video_hidden` is provided **without** `product_ids` (update only), it bulk-updates all existing product listings for the video
  * When `product_ids` are provided **without** `video_hidden`, existing product listings preserve their current `video_hidden` state; newly added products default to `false` (visible)
  * The `hidden` field is **not** returned in the Video API response. It is returned in the Product API (`GET /api/v1/products/:id/videos`), scoped to the queried product
  * Default is `false` (visible on PDP)
* `business_store_id` behavior:
  * Optional. If absent, the system will use the first business store of the business
  * If provided, the system will use the specified business store to find the product(s)

#### 6.7. External Media Schema <a href="#id-6.7.-external-media-schema" id="id-6.7.-external-media-schema"></a>

Used for social media attribution. Required when `display_social_attributions` is `true`.

| Field                | Type    | Required | Default | Description                                           |
| -------------------- | ------- | -------- | ------- | ----------------------------------------------------- |
| `source`             | string  | ✅        |         | Platform: `"tiktok"`, `"instagram"`, `"youtube"`, etc |
| `url`                | string  | ✅        |         | URL to the original social media post                 |
| `username`           | string  | ✅        |         | Creator's username/handle                             |
| `navigation_enabled` | boolean | ❌        | `true`  | Whether the URL is clickable in the player            |

**Example:**

```
{
    "display_social_attributions": true,
    "external_media":
    {
        "source": "tiktok",
        "url": "<https://www.tiktok.com/@username/video/1234567890>",
        "username": "creator_handle",
        "navigation_enabled": false
    }
}
```

**Validation Rules:**

* When `display_social_attributions` is `true`, `external_media` must be provided with at least `source` and `url`
* For updates: validation passes if the video already has an existing `external_media` association

#### 6.8. Create Video Response <a href="#id-6.8.-create-video-response" id="id-6.8.-create-video-response"></a>

Two different response shapes depending on the creation method:

**6.8.1. File Upload / S3 Key / URL Import Sync Response (`201 Created`)**

For file upload, S3 key, and URL import (sync mode), the video is created synchronously and returns immediately.

| Field                         | Type      | Nullable | Description                                                    |
| ----------------------------- | --------- | -------- | -------------------------------------------------------------- |
| `id`                          | string    | ❌        | Encoded video ID                                               |
| `access`                      | string    | ❌        | Video visibility level (`"public"`, `"private"`, `"unlisted"`) |
| `audio_disabled`              | boolean   | ❌        | Whether audio is disabled for the video (default: `false`)     |
| `caption`                     | string    | ❌        | Video title/caption                                            |
| `description`                 | string    | ✅        | Video description                                              |
| `hashtags`                    | string\[] | ❌        | Array of hashtag strings (empty if none provided)              |
| `archived_at`                 | string    | ✅        | ISO 8601 timestamp when video was archived                     |
| `product_ids`                 | string\[] | ❌        | Array of firework encoded product id                           |
| `custom_fields`               | object    | ❌        | Custom key-value metadata                                      |
| `display_social_attributions` | boolean   | ❌        | Whether social attribution is displayed                        |
| `external_media`              | object    | ✅        | Social media source metadata (see External Media Schema)       |

**6.8.2. URL Import Async Response (`202 Accepted`)**

When `"async": true` is set, the video file is downloaded and processed **asynchronously**. The response returns a video import object — not a video. The video will be created in the background.

> **Tracking progress:** Configure webhooks to receive `video_created`, `video_updated`, and `video_import_failed` events (recommended), or poll with `GET /api/v1/videos/imports/{id}` at 5–10 second intervals. Webhook payloads include `import_id` to correlate events to this job.

| Field          | Type   | Nullable | Description                                                                                                   |
| -------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------- |
| `id`           | string | ❌        | Encoded import job ID. Use with `GET /api/v1/videos/imports/{id}`                                             |
| `status`       | string | ❌        | Import status (see Import Status Values below)                                                                |
| `video_id`     | string | ✅        | Encoded video ID. `null` initially, populated once the video record is created (before transcoding completes) |
| `created_at`   | string | ❌        | ISO 8601 timestamp when the import was created                                                                |
| `completed_at` | string | ✅        | ISO 8601 timestamp when the import completed. `null` while `running`                                          |

**Import Status Values**

| Status      | Description                                                                                                                                                |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `running`   | Import is in progress: downloading URL, uploading to S3, creating video, or waiting for transcoding. `video_id` may already be populated during this phase |
| `completed` | Transcoding finished successfully. The video is fully ready                                                                                                |
| `errored`   | Import failed (download error, invalid format, duration out of range, transcode error)                                                                     |

#### 6.9. Create Video Error Responses <a href="#id-6.9.-create-video-error-responses" id="id-6.9.-create-video-error-responses"></a>

| Status Code                | Description                                                                                         |
| -------------------------- | --------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | Invalid request parameters, malformed JSON, unsupported file type, file size exceeds 5GB limit, etc |
| `401 Unauthorized`         | Invalid or missing authentication token                                                             |
| `403 Forbidden`            | Insufficient permissions                                                                            |
| `404 Not Found`            | Channel not found or membership not found                                                           |
| `422 Unprocessable Entity` | Video validation errors (e.g., caption too long, duration out of range, invalid values)             |
| `429 Too Many Requests`    | Rate limit exceeded (20 videos per 5 minutes per channel)                                           |

#### 6.10. Examples <a href="#id-6.10.-examples" id="id-6.10.-examples"></a>

**6.10.1. Option 1: File Upload (`multipart/form-data`)**

**CURL Request**

```
curl -X \
 POST "<https://api.firework.com/api/v1/videos>" \ -H \
 "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H \
 "Content-Type: multipart/form-data" \ -F 'metadata={ "channel_id": "X6Xqd6W", "caption": "My Amazing Product Demo", "description": "Check out this awesome product in action!", "access": "public", "audio_disabled": false, "hashtags": ["product", "demo", "fashion"], "business_store_id": "encoded_store_id", "product_ids": ["product_id_1"] }' \
 \ -F \
 "file=@/path/to/your/video.mp4;type=video/mp4"
```

**HTTP Request**

```
POST /api/v1/videos HTTP/1.1 Host: api.firework.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: [calculated_length] ------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data;
name = "metadata"
Content-Type: application/json {
  "channel_id": "X6Xqd6W",
  "caption": "My Amazing Product Demo",
  "description": "Check out this awesome product in action!",
  "access": "public",
  "audio_disabled": false,
  "hashtags": ["product", "demo", "fashion"],
  "business_store_id": "encoded_store_id",
  "product_ids": ["product_id_1"]
}
------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data;
name = "file";
filename = "demo_video.mp4"
Content-Type: video/mp4 [binaryvideofiledata]
------WebKitFormBoundary7MA4YWxkTrZu0gW--
```

**6.10.2. Option 2a: URL Import — Sync (default)**

```
curl
  -X POST
  "<https://api.firework.com/api/v1/videos>"
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
  -H "Content-Type: application/json"
  -d '{ "url": "<https://example.com/videos/demo-video.mp4>", "channel_id": "X6Xqd6W", "caption": "My Amazing Product Demo", "description": "Check out this awesome product in action!", "access": "public", "hashtags": ["product", "demo", "fashion"], "business_store_id": "encoded_store_id", "product_ids": ["product_id_1"], "poster_url": "<https://example.com/my-custom-poster.jpg>" }'
```

**Response**: `201 Created` — same as file upload (see [6.10.4](#id-6.10.-examples))

**6.10.2b. Option 2b: URL Import — Async**

`curl -X POST "<https://api.firework.com/api/v1/videos>" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "<https://example.com/videos/large-product-demo.mp4>", "async": true, "channel_id": "X6Xqd6W", "caption": "My Amazing Product Demo", "description": "Check out this awesome product in action!", "access": "public", "hashtags": ["product", "demo", "fashion"], "business_store_id": "encoded_store_id", "product_ids": ["product_id_1"], "poster_url": "<https://example.com/my-custom-poster.jpg>" }'`

**Response**: `202 Accepted` — see [6.10.5](#id-6.10.-examples)

**6.10.3. Option 3: S3 Key Upload (`application/json`) - Recommended for Large Files**

First, get an upload signature and upload the file to S3 (see [Section 4. Upload Signature](#id-4.-upload-signature-single-file) or [Section 5. Multipart Upload](#id-5.-multipart-upload)), then create the video with the S3 key.

**CURL Request**

```
curl
  -X POST
  "<https://api.firework.com/api/v1/videos>"
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
  -H "Content-Type: application/json"
  -d '{ "s3_key": "medias/AbCdEfG/HiJkLmN/1738152000-abcdefgh/original/product_demo.mp4", "channel_id": "X6Xqd6W", "caption": "My Amazing Product Demo", "description": "Check out this awesome product in action!", "access": "public", "audio_disabled": false, "hashtags": ["product", "demo", "fashion"], "business_store_id": "encoded_store_id", "product_ids": ["product_id_1"] }'
```

**HTTP Request**

```
POST /api/v1/videos HTTP/1.1 Host: api.firework.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Content-Length: [calculated_length]
{
  "s3_key": "medias/AbCdEfG/HiJkLmN/1738152000-abcdefgh/original/product_demo.mp4",
  "channel_id": "X6Xqd6W",
  "caption": "My Amazing Product Demo",
  "description": "Check out this awesome product in action!",
  "access": "public",
  "audio_disabled": false,
  "hashtags": ["product", "demo", "fashion"],
  "business_store_id": "encoded_store_id",
  "product_ids": ["product_id_1"]
}
```

**6.10.4. Success Response (File Upload / S3 Key) - `201 Created`**

```
{
  "id": "encoded_video_id_xyz123",
  "access": "public",
  "audio_disabled": false,
  "caption": "My Amazing Product Demo",
  "description": "Check out this awesome product in action!",
  "hashtags": ["product", "demo", "fashion"],
  "archived_at": null,
  "product_ids": ["product_id_1"],
  "custom_fields": {},
  "display_social_attributions": false,
  "external_media": null
}
```

**6.10.5. Success Response (URL Import Async) - `202 Accepted`**

```
{
  "id": "encoded_import_abc123",
  "status": "running",
  "video_id": null,
  "created_at": "2025-01-29T12:00:00.000000Z",
  "completed_at": null
}
```

***

### 7. Update Video <a href="#id-7.-update-video" id="id-7.-update-video"></a>

> **Update an existing video's data on the Firework platform.**

**Endpoint**: `PATCH /api/v1/videos/{video_id}`\
**Authentication**: Bearer token required\
**Scope**: `videos:write`\
**Content Type**: `application/json`

#### 7.1. Request Headers <a href="#id-7.1.-request-headers" id="id-7.1.-request-headers"></a>

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

#### 7.2. URL Parameters <a href="#id-7.2.-url-parameters" id="id-7.2.-url-parameters"></a>

| Parameter  | Type   | Required | Description               |
| ---------- | ------ | -------- | ------------------------- |
| `video_id` | string | ✅        | Firework encoded video ID |

#### 7.3. Request Body <a href="#id-7.3.-request-body" id="id-7.3.-request-body"></a>

| Parameter                     | Type      | Required | Description                                                                                                                       |
| ----------------------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `caption`                     | string    | ❌        | Video title/caption                                                                                                               |
| `description`                 | string    | ❌        | Video description                                                                                                                 |
| `access`                      | string    | ❌        | Video visibility: `"public"` or `"private"`                                                                                       |
| `archived_at`                 | string    | ❌        | ISO 8601 timestamp when video was archived                                                                                        |
| `audio_disabled`              | boolean   | ❌        | Whether audio is disabled for the video                                                                                           |
| `hashtags`                    | string\[] | ❌        | Array of hashtag strings                                                                                                          |
| `business_store_id`           | string    | ❌        | Encoded business store ID                                                                                                         |
| `product_ids`                 | string\[] | ❌        | Array of product identifiers, see products tagging rules                                                                          |
| `custom_fields`               | object    | ❌        | Custom key-value metadata (replace mode)                                                                                          |
| `display_social_attributions` | boolean   | ❌        | Display social media attribution on video                                                                                         |
| `external_media`              | object    | ❌        | Social media source metadata (see External Media Schema)                                                                          |
| `poster_url`                  | string    | ❌        | URL to custom poster. Set to `null` or `""` to remove. Omit to preserve.                                                          |
| `video_hidden`                | boolean   | ❌        | Hide video from PDP. With `product_ids`: applies to all listings. Without: bulk-updates existing. Omit to preserve existing state |

#### 7.4. Update Video Response <a href="#id-7.4.-update-video-response" id="id-7.4.-update-video-response"></a>

**Success Response**: `200 OK`

| Field                         | Type      | Nullable | Description                                                    |
| ----------------------------- | --------- | -------- | -------------------------------------------------------------- |
| `id`                          | string    | ❌        | Encoded video ID                                               |
| `caption`                     | string    | ❌        | Video title/caption                                            |
| `description`                 | string    | ✅        | Video description                                              |
| `access`                      | string    | ❌        | Video visibility level (`"public"`, `"private"`, `"unlisted"`) |
| `archived_at`                 | string    | ✅        | ISO 8601 timestamp when video was archived                     |
| `audio_disabled`              | boolean   | ❌        | Whether audio is disabled for the video                        |
| `hashtags`                    | string\[] | ❌        | Array of hashtag strings (empty if none provided)              |
| `product_ids`                 | string\[] | ❌        | Array of firework encoded product id                           |
| `custom_fields`               | object    | ❌        | Custom key-value metadata                                      |
| `display_social_attributions` | boolean   | ❌        | Whether social attribution is displayed                        |
| `external_media`              | object    | ✅        | Social media source metadata (see External Media Schema)       |

#### 7.5. Update Video Error Responses <a href="#id-7.5.-update-video-error-responses" id="id-7.5.-update-video-error-responses"></a>

| Status Code                | Description                                                      |
| -------------------------- | ---------------------------------------------------------------- |
| `400 Bad Request`          | Invalid request parameters, malformed JSON, or validation errors |
| `401 Unauthorized`         | Invalid or missing authentication token                          |
| `403 Forbidden`            | Insufficient permissions                                         |
| `404 Not Found`            | Video not found                                                  |
| `422 Unprocessable Entity` | Video validation errors (e.g., caption too long, invalid values) |

#### 7.6. Update Examples <a href="#id-7.6.-update-examples" id="id-7.6.-update-examples"></a>

**7.6.1. CURL Request**

```
curl
  -X PATCH
  "<https://api.firework.com/api/v1/videos/encoded_video_id_xyz123>"
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
  -H "Content-Type: application/json"
  -d '{ "caption": "Updated Amazing Product Demo", "description": "This updated description showcases even more amazing features!", "audio_disabled": true, "hashtags": ["updated", "product", "demo", "trending"], "business_store_id": "encoded_store_id", "product_ids": ["product_id_1", "SKU-67890"] }'
```

**7.6.2. HTTP Request**

```
PATCH /api/v1/videos/encoded_video_id_xyz123 HTTP/1.1 Host: api.firework.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Content-Length: [calculated_length]
{
  "caption": "Updated Amazing Product Demo",
  "description": "This updated description showcases even more amazing features!",
  "audio_disabled": true,
  "hashtags": ["updated", "product", "demo", "trending"],
  "business_store_id": "encoded_store_id",
  "product_ids": ["product_id_1", "SKU-67890"]
}
```

**7.6.3. Success Response**

```
{
  "id": "encoded_video_id_xyz123",
  "caption": "Updated Amazing Product Demo",
  "description": "This updated description showcases even more amazing features!",
  "access": "public",
  "audio_disabled": true,
  "hashtags": ["updated", "product", "demo", "trending"],
  "archived_at": null,
  "product_ids": ["product_id_1", "product_id_2"],
  "custom_fields": {},
  "display_social_attributions": false,
  "external_media": null
}
```

**7.6.4. Remove Custom Poster**

To remove a custom poster from a video, set `poster_url` to `null` or an empty string:

```
curl
  -X PATCH
  "<https://api.firework.com/api/v1/videos/encoded_video_id_xyz123>"
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
  -H "Content-Type: application/json"
  -d '{ "poster_url": null }'
```

Or with an empty string:

```
curl
  -X PATCH
  "<https://api.firework.com/api/v1/videos/encoded_video_id_xyz123>"
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
  -H "Content-Type: application/json"
  -d '{ "poster_url": "" }'
```

***

### 8. Get Video <a href="#id-8.-get-video" id="id-8.-get-video"></a>

> **Retrieve a video's details from the Firework platform.**

**Endpoint**: `GET /api/v1/videos/{video_id}`\
**Authentication**: Bearer token required\
**Scope**: `videos:read`\
**Content Type**: N/A (no request body)

#### 8.1. Request Headers <a href="#id-8.1.-request-headers" id="id-8.1.-request-headers"></a>

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

#### 8.2. URL Parameters <a href="#id-8.2.-url-parameters" id="id-8.2.-url-parameters"></a>

| Parameter  | Type   | Required | Description               |
| ---------- | ------ | -------- | ------------------------- |
| `video_id` | string | ✅        | Firework encoded video ID |

#### 8.3. Get Video Response <a href="#id-8.3.-get-video-response" id="id-8.3.-get-video-response"></a>

**Success Response**: `200 OK`

> **Note**: This endpoint returns a video that has been created. For videos imported via URL, use `GET /api/v1/videos/imports/{id}` to track import status. Once the import completes, the `video_id` from the import response can be used with this endpoint.

| Field                         | Type      | Nullable | Description                                                           |
| ----------------------------- | --------- | -------- | --------------------------------------------------------------------- |
| `id`                          | string    | ❌        | Encoded video ID                                                      |
| `caption`                     | string    | ❌        | Video title/caption                                                   |
| `description`                 | string    | ✅        | Video description                                                     |
| `access`                      | string    | ❌        | Video visibility level (`"public"`, `"private"`, `"unlisted"`)        |
| `archived_at`                 | string    | ✅        | ISO 8601 timestamp when video was archived                            |
| `audio_disabled`              | boolean   | ❌        | Whether audio is disabled for the video                               |
| `hashtags`                    | string\[] | ❌        | Array of hashtag strings (empty if none)                              |
| `product_ids`                 | string\[] | ❌        | Array of firework encoded product IDs                                 |
| `custom_fields`               | object    | ❌        | Custom key-value metadata                                             |
| `thumbnail_url`               | string    | ✅        | CDN URL for the video thumbnail image (540x960)                       |
| `display_social_attributions` | boolean   | ❌        | Whether social attribution is displayed                               |
| `external_media`              | object    | ✅        | Social media source metadata (see External Media Schema)              |
| `video_posters`               | array     | ❌        | Array of video poster images (empty if none). See Video Poster Schema |

**Video Poster Schema**

Each object in the `video_posters` array contains:

| Field          | Type    | Nullable | Description                                        |
| -------------- | ------- | -------- | -------------------------------------------------- |
| `url`          | string  | ❌        | CDN URL for the poster image                       |
| `aspect_ratio` | string  | ❌        | Aspect ratio (e.g. `"9:16"`, `"16:9"`, `"1:1"`)    |
| `format`       | string  | ❌        | Image format (`"jpg"`, `"webp"`, `"gif"`, `"png"`) |
| `width`        | integer | ❌        | Image width in pixels                              |
| `height`       | integer | ❌        | Image height in pixels                             |

#### 8.4. Get Video Error Responses <a href="#id-8.4.-get-video-error-responses" id="id-8.4.-get-video-error-responses"></a>

| Status Code        | Description                             |
| ------------------ | --------------------------------------- |
| `401 Unauthorized` | Invalid or missing authentication token |
| `403 Forbidden`    | Insufficient permissions                |
| `404 Not Found`    | Video not found                         |

#### 8.5. Get Video Examples <a href="#id-8.5.-get-video-examples" id="id-8.5.-get-video-examples"></a>

**8.5.1. CURL Request**

```
curl
  -X GET
  "<https://api.firework.com/api/v1/videos/encoded_video_id_xyz123>"
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**8.5.2. HTTP Request**

```
GET /api/v1/videos/encoded_video_id_xyz123 HTTP/1.1 Host: api.firework.com
Authorization: Bearer YOUR_ACCESS_TOKEN
```

**8.5.3. Success Response**

```
{
  "id": "encoded_video_id_xyz123",
  "caption": "My Amazing Product Demo",
  "description": "Check out this awesome product in action!",
  "access": "public",
  "archived_at": null,
  "audio_disabled": false,
  "hashtags": ["product", "demo", "fashion"],
  "product_ids": ["product_id_1", "product_id_2"],
  "custom_fields": {
    "creator_id": "2436372",
    "tracking_id": "390724V"
  },
  "thumbnail_url": "<https://cdn.firework.com/medias/2026/3/18/abc123/540_960/thumb.jpg>",
  "display_social_attributions": true,
  "external_media": {
    "source": "tiktok",
    "url": "<https://www.tiktok.com/@username/video/1234567890>",
    "username": "creator_handle",
    "navigation_enabled": true
  },
  "video_posters": [
    {
      "url": "<https://cdn.firework.com/medias/2026/3/18/abc123/transcoded/poster-9x16.jpg>",
      "aspect_ratio": "9:16",
      "format": "jpg",
      "width": 1080,
      "height": 1920
    },
    {
      "url": "<https://cdn.firework.com/medias/2026/3/18/abc123/transcoded/poster-16x9.jpg>",
      "aspect_ratio": "16:9",
      "format": "jpg",
      "width": 1920,
      "height": 1080
    }
  ]
}
```

***

### 9. Get Video Import Status <a href="#id-9.-get-video-import-status" id="id-9.-get-video-import-status"></a>

> **Track the status of an async URL import.**

Use this endpoint to check the progress of a video import initiated via the URL import method. Once the import completes successfully, the response includes the `video_id` which can be used with the [Get Video](#id-8.-get-video) and [Update Video](#id-7.-update-video) endpoints.

> **Tip:** For real-time notifications instead of polling, configure webhooks. The [`video_created`](/firework-for-developers/api/webhooks.md#video_created), [`video_updated`](/firework-for-developers/api/webhooks.md#video_updated), and [`video_import_failed`](/firework-for-developers/api/webhooks.md#video_import_failed) events include `import_id` so you can correlate events back to this import job.

**Endpoint**: `GET /api/v1/videos/imports/{id}`\
**Authentication**: Bearer token required\
**Scope**: `videos:read`

#### 9.1. Request Headers <a href="#id-9.1.-request-headers" id="id-9.1.-request-headers"></a>

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

#### 9.2. URL Parameters <a href="#id-9.2.-url-parameters" id="id-9.2.-url-parameters"></a>

| Parameter | Type   | Required | Description                                   |
| --------- | ------ | -------- | --------------------------------------------- |
| `id`      | string | ✅        | Encoded import job ID (from the 202 response) |

#### 9.3. Get Import Status Response <a href="#id-9.3.-get-import-status-response" id="id-9.3.-get-import-status-response"></a>

**Success Response**: `200 OK`

| Field          | Type   | Nullable | Description                                                                                                       |
| -------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `id`           | string | ❌        | Encoded import job ID                                                                                             |
| `status`       | string | ❌        | Import status: `"running"`, `"completed"`, or `"errored"`                                                         |
| `video_id`     | string | ✅        | Encoded video ID. `null` initially, populated once the video record is created (may appear while still `running`) |
| `created_at`   | string | ❌        | ISO 8601 timestamp when the import was created                                                                    |
| `completed_at` | string | ✅        | ISO 8601 timestamp when the import completed. `null` while `running`                                              |

#### 9.4. Get Import Status Error Responses <a href="#id-9.4.-get-import-status-error-responses" id="id-9.4.-get-import-status-error-responses"></a>

| Status Code        | Description                             |
| ------------------ | --------------------------------------- |
| `401 Unauthorized` | Invalid or missing authentication token |
| `403 Forbidden`    | Insufficient permissions                |
| `404 Not Found`    | Import job not found                    |

#### 9.5. Get Import Status Examples <a href="#id-9.5.-get-import-status-examples" id="id-9.5.-get-import-status-examples"></a>

**9.5.1. CURL Request**

```
curl
  -X GET
  "<https://api.firework.com/api/v1/videos/imports/encoded_import_abc123>"
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**9.5.2. Response (Running)**

```
{
  "id": "encoded_import_abc123",
  "status": "running",
  "video_id": null,
  "created_at": "2025-01-29T12:00:00.000000Z",
  "completed_at": null
}
```

**9.5.3. Response (Completed)**

```
{
  "id": "encoded_import_abc123",
  "status": "completed",
  "video_id": "encoded_video_id_xyz123",
  "created_at": "2025-01-29T12:00:00.000000Z",
  "completed_at": "2025-01-29T12:02:30.000000Z"
}
```

> **Next step**: Use the `video_id` with `GET /api/v1/videos/{video_id}` to get the full video details.

**9.5.4. Response (Errored)**

```
{
  "id": "encoded_import_abc123",
  "status": "errored",
  "video_id": null,
  "created_at": "2025-01-29T12:00:00.000000Z",
  "completed_at": null
}
```

***

### 10. Custom Fields Extension <a href="#id-10.-custom-fields-extension" id="id-10.-custom-fields-extension"></a>

The Video API supports custom metadata through the `custom_fields` parameter. This allows you to attach arbitrary key-value pairs to videos for tracking and analytics purposes.

**Key Points**:

* **Replace Mode**: Providing `custom_fields` replaces ALL existing custom fields
* **Preserve Existing**: Omit `custom_fields` from request to keep existing values
* **Clear All**: Use `custom_fields: {}` to remove all custom fields
* **Validation**: Keys must match `^[a-z0-9_]{1,255}$`, values max 1024 characters

**Example with Custom Fields**:

```
curl
  -X PATCH
  "<https://api.firework.com/api/v1/videos/xyz123>"
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
  -H "Content-Type: application/json"
  -d '{ "caption": "Product Demo", "custom_fields": { "creator_id": "2436372", "publisher_id": "3264535", "tracking_id": "390724V" } }'
```

***


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.firework.com/firework-for-developers/api/videos.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
