# Products

### 1. Overview

The Firework Product API allows you to manage products and retrieve product-related video content on the Firework platform. This API supports full product lifecycle management — listing, retrieving, creating/updating, and deleting products — as well as retrieving videos tagged with a specific product for PDP (Product Detail Page) integration.

Products are scoped to a **business store**. You must have a business store before creating products.

The API supports flexible product identification — you can look up products by Firework ID, external product ID, SKU, GTIN, or MPN.

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

***

### 2. Authentication

The Firework Product 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

**Important**: The access token must be from an authenticated user or OAuth app with permission to access the specified business.

**Scopes:**

| Scope            | Description                                                                       |
| ---------------- | --------------------------------------------------------------------------------- |
| `products:read`  | Read access to products and product videos                                        |
| `products:write` | Write access (create, update, delete products). Implicitly grants `products:read` |

> 📖 **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/products`                     | POST   | `products:write` | Upsert (create or update) a product            |
| `/api/v1/products/{product_id}/videos` | GET    | `products:read`  | Retrieve videos tagged with a specific product |

***

### 4. Product Object Reference

The product object is returned by the List, Get, and Upsert endpoints. All IDs are Firework encoded IDs.

#### 4.1. Product Fields

| Field               | Type      | Nullable | Description                                                                                           |
| ------------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `id`                | string    | ❌        | Encoded unique product identifier                                                                     |
| `external_id`       | string    | ❌        | External product identifier from your system, or a Firework-generated identifier if omitted on create |
| `name`              | string    | ❌        | Display name of the product                                                                           |
| `description`       | string    | ✅        | Product description (plain text)                                                                      |
| `currency`          | string    | ❌        | Three-letter ISO 4217 currency code (e.g., `"USD"`, `"EUR"`)                                          |
| `handle`            | string    | ✅        | URL handle or slug for the product page                                                               |
| `options`           | string\[] | ❌        | Available option names (e.g., `["color", "size"]`). Empty if none                                     |
| `category`          | string    | ✅        | Product category (free-form text)                                                                     |
| `hide_price`        | boolean   | ❌        | Whether to hide price display (default: `false`)                                                      |
| `brand`             | string    | ✅        | Product brand name                                                                                    |
| `subtitle`          | string    | ✅        | Product subtitle (max 75 characters)                                                                  |
| `shipping`          | string    | ✅        | Shipping information                                                                                  |
| `custom_cta`        | object    | ✅        | Custom call-to-action button configuration (see [Custom CTA Object](#id-4.5.-custom-cta-object))      |
| `business_store_id` | string    | ❌        | Encoded ID of the business store this product belongs to                                              |
| `images`            | object\[] | ❌        | Array of product images (see [Image Object](#id-4.2.-image-object))                                   |
| `units`             | object\[] | ❌        | Array of product variants/units (see [Unit Object](#id-4.3.-unit-object))                             |

#### 4.2. Image Object

| Field               | Type      | Nullable | Description                                                          |
| ------------------- | --------- | -------- | -------------------------------------------------------------------- |
| `id`                | string    | ❌        | Encoded unique image identifier                                      |
| `external_id`       | string    | ✅        | External identifier for the image                                    |
| `url`               | string    | ❌        | URL of the image                                                     |
| `position`          | integer   | ❌        | Display position (0-indexed), derived from image order during upsert |
| `unit_external_ids` | string\[] | ❌        | External IDs of units this image applies to. Empty if global         |

#### 4.3. Unit Object

| Field            | Type      | Nullable | Description                                                              |
| ---------------- | --------- | -------- | ------------------------------------------------------------------------ |
| `id`             | string    | ❌        | Encoded unique unit identifier                                           |
| `external_id`    | string    | ✅        | External identifier for the unit/variant                                 |
| `name`           | string    | ❌        | Display name of the unit/variant (e.g., `"Red / Large"`)                 |
| `price`          | number    | ❌        | Current price as a number (e.g., `1338.00`)                              |
| `original_price` | number    | ✅        | Original price before discount                                           |
| `url`            | string    | ❌        | Direct URL to purchase this variant                                      |
| `position`       | integer   | ❌        | Display position (0-indexed). Defaults to `0` if omitted                 |
| `quantity`       | integer   | ✅        | Available stock quantity                                                 |
| `options`        | object\[] | ❌        | Option values for this variant (see [Unit Option](#id-4.4.-unit-option)) |
| `sku`            | string    | ✅        | Stock Keeping Unit                                                       |
| `gtin`           | string    | ✅        | Global Trade Item Number (UPC, EAN, ISBN, etc.)                          |
| `mpn`            | string    | ✅        | Manufacturer Part Number                                                 |
| `barcode`        | string    | ✅        | Barcode value                                                            |

#### 4.4. Unit Option

| Field   | Type   | Nullable | Description                   |
| ------- | ------ | -------- | ----------------------------- |
| `name`  | string | ❌        | Option name (e.g., `"color"`) |
| `value` | string | ❌        | Option value (e.g., `"Red"`)  |

#### 4.5. Custom CTA Object

The custom call-to-action (CTA) object configures an optional action button that appears on the video overlay for this product. A product can have at most one custom CTA. Depending on the value of `hide_default_purchase_button`, the custom CTA either **replaces** the default purchase button or **augments** it (appears alongside it).

| Field                          | Type    | Nullable | Description                                                                                                                                               |
| ------------------------------ | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title`                        | string  | ❌        | One of the supported CTA title keys (see [Supported CTA Titles](#supported-cta-titles)). Must be present together with `url`.                             |
| `url`                          | string  | ❌        | Destination URL when the button is clicked. Max 4000 characters. Must be present together with `title`.                                                   |
| `target`                       | string  | ❌        | Link target behavior. One of `"_blank"` (default), `"_self"`, `"iframe"`.                                                                                 |
| `hide_default_purchase_button` | boolean | ❌        | If `true`, the custom CTA replaces the default purchase button. If `false`, both the default purchase button and the custom CTA render. Default: `false`. |
| `title_translation`            | string  | ❌        | *Response only.* Localized display label for the title, derived from the merchant's locale. Not accepted on input.                                        |

#### **Supported CTA Titles**

| Title key                | Default English label  |
| ------------------------ | ---------------------- |
| `order_now`              | Order Now              |
| `buy_now`                | Buy Now                |
| `sign_up`                | Sign Up                |
| `enter_now`              | Enter Now              |
| `enroll_now`             | Enroll Now >           |
| `claim_sample`           | Claim Sample           |
| `take_the_quiz`          | Take The Quiz          |
| `see_recipe`             | See Recipe             |
| `see_more`               | See More               |
| `see_details`            | See Details            |
| `start_the_journey`      | Start The Journey      |
| `personalize_my_blend`   | Personalize My Blend   |
| `order_your_welcome_kit` | Order Your Welcome Kit |

Display labels are localized per merchant locale via Firework's translation system.

#### **Rendering Behavior**

The combination of `hide_default_purchase_button` and whether `title`/`url` are set determines what renders on the video overlay:

| `hide_default_purchase_button` | `title` / `url` set? | What renders                                            |
| ------------------------------ | -------------------- | ------------------------------------------------------- |
| `false`                        | No                   | Default purchase button only (1 button)                 |
| `false`                        | Yes                  | Default purchase button **and** custom CTA (2 buttons)  |
| `true`                         | Yes                  | Custom CTA only — the default purchase button is hidden |

**Validation Rules**

* `title` and `url` must both be present together or both be absent. Sending only one of them returns `422 Unprocessable Entity`.
* `title` must be one of the supported title keys listed above.
* `target` must be one of `"_blank"`, `"_self"`, `"iframe"`.
* `url` must not exceed 4000 characters.

***

### 5. Upsert Product

Create or update a product in a business store. If a product with the same `external_id` already exists in the specified store, it is updated; otherwise, a new product is created.

**Endpoint**: `POST /api/v1/products` **Authentication**: Bearer token required (OAuth 2.0 Client Credentials) **Required Scope**: `products:write` (for OAuth apps) **Content Type**: `application/json`

#### 5.1. Request Headers

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

#### 5.2. Request Body

**Product Fields**

| Field               | Type      | Required | Default        | Description                                                                                                                                              |
| ------------------- | --------- | -------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `business_store_id` | string    | ✅        | None           | Encoded business store ID to create/update the product in                                                                                                |
| `external_id`       | string    | ❌        | Auto-generated | External product identifier from your system (used for matching). If omitted, a unique ID is auto-generated and the request always creates a new product |
| `name`              | string    | ✅        | None           | Display name of the product                                                                                                                              |
| `description`       | string    | ❌        | None           | Product description (plain text)                                                                                                                         |
| `handle`            | string    | ❌        | None           | URL handle or slug for the product page                                                                                                                  |
| `options`           | string\[] | ❌        | `[]`           | Available option names (e.g., `["color", "size"]`)                                                                                                       |
| `category`          | string    | ❌        | None           | Product category (free-form text)                                                                                                                        |
| `hide_price`        | boolean   | ❌        | `false`        | Whether to hide price display                                                                                                                            |
| `brand`             | string    | ❌        | None           | Product brand name                                                                                                                                       |
| `subtitle`          | string    | ❌        | None           | Product subtitle (max 75 characters)                                                                                                                     |
| `shipping`          | string    | ❌        | None           | Shipping information                                                                                                                                     |
| `custom_cta`        | object    | ❌        | `null`         | Custom call-to-action configuration (see [Custom CTA Input](#id-4.5.-custom-cta-object)). Send `null` to clear an existing CTA.                          |
| `product_images`    | object\[] | ❌        | `[]`           | Array of product images (see [Image Input](#id-4.2.-image-object))                                                                                       |
| `product_units`     | object\[] | ❌        | `[]`           | Array of product variants/units (see [Unit Input](#id-4.3.-unit-object))                                                                                 |

**Image Input**

| Field              | Type      | Required | Default | Description                                 |
| ------------------ | --------- | -------- | ------- | ------------------------------------------- |
| `external_id`      | string    | ❌        | None    | External identifier for the image           |
| `url`              | string    | ✅        | None    | URL of the image                            |
| `unit_identifiers` | string\[] | ❌        | `[]`    | External IDs of units this image applies to |

Image position is derived from the image's order in the `images` array. The request body does not accept a `position` field for images.

**Unit Input**

| Field            | Type      | Required | Default | Description                                              |
| ---------------- | --------- | -------- | ------- | -------------------------------------------------------- |
| `external_id`    | string    | ❌        | None    | External identifier for the unit/variant                 |
| `name`           | string    | ✅        | None    | Display name of the unit/variant                         |
| `price`          | string    | ✅        | None    | Current price as a string (e.g., `"129.99"`)             |
| `original_price` | string    | ❌        | None    | Original price before discount (e.g., `"149.99"`)        |
| `url`            | string    | ✅        | None    | Direct URL to purchase this variant                      |
| `quantity`       | integer   | ❌        | None    | Available stock quantity                                 |
| `options`        | object\[] | ✅        | None    | Option values for this variant (pass `[]` if no options) |
| `sku`            | string    | ❌        | None    | Stock Keeping Unit                                       |
| `gtin`           | string    | ❌        | None    | Global Trade Item Number (UPC, EAN, ISBN, etc.)          |
| `mpn`            | string    | ❌        | None    | Manufacturer Part Number                                 |
| `barcode`        | string    | ❌        | None    | Barcode value                                            |

Unit position is derived from the unit's order in the `product_units` array. The request body does not accept a `position` field for units.

Each unit option object has:

| Field   | Type   | Required | Description                   |
| ------- | ------ | -------- | ----------------------------- |
| `name`  | string | ✅        | Option name (e.g., `"color"`) |
| `value` | string | ✅        | Option value (e.g., `"Gray"`) |

**Custom CTA Input**

Optional. If provided, must be an object matching the [Custom CTA Object](#id-4.5.-custom-cta-object) shape.

| Field                          | Type    | Required | Default    | Description                                                                                                    |
| ------------------------------ | ------- | -------- | ---------- | -------------------------------------------------------------------------------------------------------------- |
| `title`                        | string  | ❌        | None       | One of the supported CTA title keys. Must be present together with `url`.                                      |
| `url`                          | string  | ❌        | None       | Destination URL (max 4000 characters). Must be present together with `title`.                                  |
| `target`                       | string  | ❌        | `"_blank"` | Link behavior: `"_blank"`, `"_self"`, or `"iframe"`.                                                           |
| `hide_default_purchase_button` | boolean | ❌        | `false`    | If `true`, the custom CTA replaces the default purchase button. See [Rendering Behavior](#rendering-behavior). |

**Update and clearing semantics:**

* To **clear** an existing CTA, send `"custom_cta": null`. The embedded CTA is removed from the product.
* To **preserve** an existing CTA unchanged, omit `custom_cta` from the request body entirely.
* **Partial updates are not supported.** Because `title` and `url` must move together, you cannot update only one of them. Always send the full custom CTA object when modifying it.

**Important Update Behavior**

* If `units` is included in an upsert for an existing product, the existing unit collection is replaced by the provided `units` array.
* If `images` is included in an upsert for an existing product, the existing image collection is replaced by the provided `images` array.
* Image-to-unit associations are rebuilt from `images[].unit_external_ids` against the `units` included in the same request. To safely update variant images and their associations, send `images` and `units` together.

#### 5.3. Upsert Product Response

**Success Response**: `201 Created`

The response returns the full product object as described in the [Product Object Reference](#id-4.-product-object-reference).

```
{
  "id": "vWKDjg",
  "external_id": "SHOE-001",
  "name": "Classic Running Shoe",
  "description": "Lightweight running shoe for everyday training",
  "currency": "USD",
  "handle": "https://example.com/products/classic-running-shoe",
  "options": ["color", "size"],
  "category": "Footwear",
  "hide_price": false,
  "brand": "RunCo",
  "subtitle": "Everyday training shoe",
  "shipping": "Free shipping over $50",
  "custom_cta": {
    "title": "buy_now",
    "title_translation": "Buy Now",
    "url": "https://shop.example.com/classic-running-shoe",
    "target": "_blank",
    "hide_default_purchase_button": false
  },
  "business_store_id": "y8PDj8",
  "images": [
    {
      "id": "mK9x2p",
      "external_id": "IMG-001",
      "url": "https://example.com/images/shoe-gray.jpg",
      "position": 0,
      "unit_external_ids": ["SHOE-001-GR-10"]
    }
  ],
  "units": [
    {
      "id": "nP3qRv",
      "external_id": "SHOE-001-GR-10",
      "name": "Gray / Size 10",
      "price": 129.99,
      "original_price": 149.99,
      "url": "https://example.com/products/classic-running-shoe?variant=gray-10",
      "position": 0,
      "quantity": 25,
      "options": [
        { "name": "color", "value": "Gray" },
        { "name": "size", "value": "10" }
      ],
      "sku": "SKU-SHOE-GR10",
      "gtin": "071249656457",
      "mpn": null,
      "barcode": null
    }
  ]
}
```

#### 5.4. Upsert Product Error Responses

| Status Code                | Description                                                                                                                                                                                                                     |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | Missing required fields or malformed JSON                                                                                                                                                                                       |
| `401 Unauthorized`         | Invalid or missing authentication token                                                                                                                                                                                         |
| `403 Forbidden`            | Insufficient scope or no access to the specified business store                                                                                                                                                                 |
| `404 Not Found`            | Business store not found                                                                                                                                                                                                        |
| `422 Unprocessable Entity` | Validation errors (e.g., invalid currency, missing unit fields, malformed nested `units` or `images`, invalid `custom_cta` such as `title` not in enum, `title` without `url` or vice versa, `url` longer than 4000 characters) |

#### 5.5. Examples

**CURL Request**

```
curl -X POST "https://api.firework.com/api/v1/products" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "business_store_id": "y8PDj8",
    "external_id": "SHOE-001",
    "name": "Classic Running Shoe",
    "description": "Lightweight running shoe for everyday training",
    "handle": "https://example.com/products/classic-running-shoe",
    "options": ["color", "size"],
    "category": "Footwear",
    "product_images": [
      {
        "external_id": "IMG-001",
        "url": "https://example.com/images/shoe-gray.jpg",
        "unit_identifiers": ["SHOE-001-GR-10"]
      }
    ],
    "product_units": [
      {
        "external_id": "SHOE-001-GR-10",
        "name": "Gray / Size 10",
        "price": "129.99",
        "original_price": "149.99",
        "url": "https://example.com/products/classic-running-shoe?variant=gray-10",
        "quantity": 25,
        "options": [
          { "name": "color", "value": "Gray" },
          { "name": "size", "value": "10" }
        ],
        "sku": "SKU-SHOE-GR10",
        "gtin": "071249656457"
      }
    ]
  }'
```

**Minimal Upsert (product with no variants)**

```
curl -X POST "https://api.firework.com/api/v1/products" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "business_store_id": "y8PDj8",
    "external_id": "GIFT-CARD-50",
    "name": "$50 Gift Card",
    "product_units": [
      {
        "name": "Default",
        "price": "50.00",
        "url": "https://example.com/products/gift-card-50",
        "options": []
      }
    ]
  }'
```

***

### 6. List Product Videos

Retrieve videos tagged with a specific product. Designed for PDP (Product Detail Page) integration.

**Endpoint**: `GET /api/v1/products/{product_id}/videos` **Authentication**: Bearer token required **Required Scope**: `products:read` (for OAuth apps)

#### 6.1. Request Headers

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

#### 6.2. Path Parameters

| Parameter    | Type   | Required | Description                                                                |
| ------------ | ------ | -------- | -------------------------------------------------------------------------- |
| `product_id` | string | ✅        | Product identifier (see [Product Identifiers](#id-8.-product-identifiers)) |

#### 6.3. Query Parameters

| Parameter           | Type    | Required | Default | Description                                              |
| ------------------- | ------- | -------- | ------- | -------------------------------------------------------- |
| `channel_id`        | string  | ✅        | None    | Firework encoded channel ID                              |
| `business_store_id` | string  | ❌        | None    | Encoded business store ID for product lookup             |
| `before_id`         | string  | ❌        | None    | Cursor for pagination (get entries before this video ID) |
| `since_id`          | string  | ❌        | None    | Cursor for pagination (get entries after this video ID)  |
| `page_size`         | integer | ❌        | 10      | Number of videos per page (max: 100)                     |

#### 6.4. List Product Videos Response

**Success Response**: `200 OK`

| Field    | Type      | Nullable | Description            |
| -------- | --------- | -------- | ---------------------- |
| `videos` | object\[] | ❌        | Array of video objects |
| `paging` | object    | ❌        | Pagination information |

**Video Object**

| Field                         | Type      | Nullable | Description                                                                 |
| ----------------------------- | --------- | -------- | --------------------------------------------------------------------------- |
| `id`                          | string    | ❌        | Encoded video ID                                                            |
| `caption`                     | string    | ❌        | Video title/caption                                                         |
| `description`                 | string    | ✅        | Video description                                                           |
| `hashtags`                    | string\[] | ❌        | Array of hashtag strings (empty if none provided)                           |
| `access`                      | string    | ❌        | Video visibility level (`"public"`, `"private"`, `"unlisted"`)              |
| `audio_disabled`              | boolean   | ❌        | Whether audio is disabled for the video (default: `false`)                  |
| `archived_at`                 | string    | ✅        | ISO 8601 timestamp when video was archived                                  |
| `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 below |
| `hidden`                      | boolean   | ❌        | Whether the video is hidden in the product listing                          |

**Video Poster Schema**

| 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                             |

**Paging Object**

| Field  | Type   | Nullable | Description                                                     |
| ------ | ------ | -------- | --------------------------------------------------------------- |
| `next` | string | ✅        | URL to fetch the next page of results (null if no more results) |

#### 6.5. List Product Videos Error Responses

| Status Code        | Description                                                        |
| ------------------ | ------------------------------------------------------------------ |
| `400 Bad Request`  | Missing `channel_id` parameter or invalid pagination values        |
| `401 Unauthorized` | Missing or invalid token                                           |
| `403 Forbidden`    | Missing `products:read` scope or channel belongs to other business |
| `404 Not Found`    | Product or channel not found                                       |

> **Authorization Note**: The `channel_id` must belong to the same business as the authenticated user or OAuth app. Attempting to access a channel from another business returns 403.

#### 6.6. Examples

**CURL Request**

```
curl -X GET "https://api.firework.com/api/v1/products/vWKDjg/videos?channel_id=z1xg8N" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Example Response**

```
{
  "videos": [
    {
      "id": "Dux9vy8m",
      "caption": "Summer Collection Showcase",
      "description": "Check out our latest summer styles",
      "hashtags": ["summer", "fashion", "newcollection"],
      "access": "public",
      "audio_disabled": false,
      "archived_at": null,
      "product_ids": ["vWKDjg"],
      "custom_fields": {},
      "thumbnail_url": "https://cdn.firework.com/medias/2026/3/18/abc123/540_960/thumb.jpg",
      "display_social_attributions": false,
      "external_media": null,
      "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
        }
      ],
      "hidden": false
    },
    {
      "id": "2aicAl1K",
      "caption": "How to Style This Look",
      "description": null,
      "hashtags": [],
      "access": "public",
      "audio_disabled": false,
      "archived_at": null,
      "product_ids": ["vWKDjg"],
      "custom_fields": {},
      "thumbnail_url": null,
      "display_social_attributions": false,
      "external_media": null,
      "video_posters": [],
      "hidden": true
    }
  ],
  "paging": {
    "next": "/api/v1/products/vWKDjg/videos?channel_id=z1xg8N&before_id=2aicAl1K&page_size=10"
  }
}
```

**Example Response (Last Page)**

```
{
  "videos": [
    {
      "id": "9xKmPq2r",
      "caption": "Product Unboxing",
      "description": "First look at the product",
      "hashtags": ["unboxing"],
      "access": "public",
      "audio_disabled": false,
      "archived_at": null,
      "product_ids": ["vWKDjg"],
      "custom_fields": {},
      "thumbnail_url": "https://cdn.firework.com/medias/2026/3/18/def456/540_960/thumb.jpg",
      "display_social_attributions": false,
      "external_media": null,
      "video_posters": [],
      "hidden": false
    }
  ],
  "paging": {
    "next": null
  }
}
```

***

### 7. Pagination

All list endpoints use **cursor-based pagination** for efficient navigation through large result sets.

**How it works:**

* Use `before_id` to get older entries (descending order by ID)
* Use `since_id` to get newer entries (ascending order by ID)
* Only one of `before_id` or `since_id` may be used per request
* The response includes a `paging` object with the `next` URL if more results exist
* When no more results exist, `paging` will be an empty object: `{}`

**Pagination Examples**

```
# First page (no cursor needed)
GET /api/v1/products/vWKDjg/videos?channel_id=z1xg8N&page_size=10

# Next page using cursor from previous response
GET /api/v1/products/vWKDjg/videos?channel_id=z1xg8N&before_id=2aicAl1K&page_size=10
```

***

### 8. Product Identifiers

The `product_id` path parameter in the Get Product, Delete Product, and List Product Videos endpoints accepts multiple identifier types for flexibility:

* **Firework encoded product ID** — The internal Firework product identifier
* **External product ID** — Your system's product identifier
* **External product unit ID** — Your system's product variant/unit identifier
* **Product unit GTIN** — Global Trade Item Number (UPC, EAN, ISBN, etc.)
* **Product unit SKU** — Stock Keeping Unit
* **Product unit MPN** — Manufacturer Part Number

#### Product Lookup Rules

* The system will attempt to resolve the product using each identifier type in sequence
* If `business_store_id` is provided, the lookup is scoped to that specific store
* If `business_store_id` is omitted, the system uses the first business store of the authenticated business
* If the product cannot be found using any identifier type, a `404 Not Found` is returned

#### Identifier Examples

```
# Using Firework product ID
GET /api/v1/products/vWKDjg

# Using external product ID (with store scoping)
GET /api/v1/products/SHOE-001?business_store_id=y8PDj8

# Using GTIN/UPC
GET /api/v1/products/071249656457

# Using SKU (with store scoping)
GET /api/v1/products/SKU-SHOE-GR10?business_store_id=y8PDj8
```


---

# 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/products.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.
