Skip to main content

Products API

Products represent items that can be distributed through experiences. Use these endpoints to create, manage, and organize products with their variants and media assets.

Create Product

Create a new product or update an existing one (upsert when sync source is provided). POST /api/v1/products

Authentication

  • Secret key required

Request Body

interface CreateProductRequest {
  name: string;
  description?: string;
  sku?: string;
  price?: number;
  currencyCode?: string;
  status?: "ACTIVE" | "DRAFT" | "ARCHIVED";
  metadata?: Record<string, unknown>;
  productMediaAssets?: ProductMediaAssetInput[];
  primarySyncSource?: SyncSourceInput;
}

interface ProductMediaAssetInput {
  mediaAsset: {
    url: string;
    type: "IMAGE" | "VIDEO";
    altText?: string;
    width?: number;
    height?: number;
  };
  purpose: "PRODUCT" | "VARIANT" | "THUMBNAIL" | "GALLERY";
  position: number;
}

interface SyncSourceInput {
  syncSource: string;
  syncSourceId: string;
  syncStatus?: "PENDING" | "SUCCESS" | "FAILED";
  lastSyncedAt?: string;
  sourceData?: Record<string, unknown>;
}

Response

interface Product {
  id: string;
  organizationId: string;
  name: string;
  description: string | null;
  sku: string | null;
  price: number | null;
  currencyCode: string | null;
  status: "ACTIVE" | "DRAFT" | "ARCHIVED";
  metadata: Record<string, unknown> | null;
  primarySyncSourceId: string | null;
  createdAt: string;
  updatedAt: string;
  variants?: ProductVariant[];
  mediaAssets?: ProductMediaAsset[];
}

Example

curl -X POST https://admin.fanfare.io/api/v1/products \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Limited Edition Sneakers",
    "description": "Exclusive release",
    "sku": "SNKR-001",
    "price": 199.99,
    "currencyCode": "USD",
    "status": "ACTIVE",
    "productMediaAssets": [
      {
        "mediaAsset": {
          "url": "https://cdn.example.com/sneakers-main.jpg",
          "type": "IMAGE",
          "altText": "Limited Edition Sneakers"
        },
        "purpose": "PRODUCT",
        "position": 0
      }
    ]
  }'
Response:
{
  "id": "prod_01HXYZ123456789",
  "organizationId": "org_01HXYZ123456789",
  "name": "Limited Edition Sneakers",
  "description": "Exclusive release",
  "sku": "SNKR-001",
  "price": 199.99,
  "currencyCode": "USD",
  "status": "ACTIVE",
  "metadata": null,
  "primarySyncSourceId": null,
  "createdAt": "2024-12-01T10:00:00Z",
  "updatedAt": "2024-12-01T10:00:00Z",
  "variants": [],
  "mediaAssets": [
    {
      "id": "pma_01HXYZ123456789",
      "productId": "prod_01HXYZ123456789",
      "mediaAssetId": "ma_01HXYZ123456789",
      "purpose": "PRODUCT",
      "position": 0
    }
  ]
}

Upsert with Sync Source

When a sync source is provided, the endpoint will update an existing product with matching sync source:
curl -X POST https://admin.fanfare.io/api/v1/products \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated Sneakers",
    "price": 249.99,
    "primarySyncSource": {
      "syncSource": "shopify",
      "syncSourceId": "gid://shopify/Product/12345",
      "syncStatus": "SUCCESS",
      "sourceData": {
        "handle": "limited-edition-sneakers",
        "vendor": "Nike"
      }
    }
  }'

List Products

List all products in your organization. GET /api/v1/products

Authentication

  • Secret key required

Query Parameters

ParameterTypeDescription
includestringRelations to include (variants, mediaAssets)

Example

curl -X GET "https://admin.fanfare.io/api/v1/products?include=variants,mediaAssets" \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"
Response:
[
  {
    "id": "prod_01HXYZ123456789",
    "name": "Limited Edition Sneakers",
    "sku": "SNKR-001",
    "price": 199.99,
    "status": "ACTIVE",
    "variants": [...],
    "mediaAssets": [...]
  },
  {
    "id": "prod_01HXYZ123456790",
    "name": "Exclusive T-Shirt",
    "sku": "TSHIRT-001",
    "price": 49.99,
    "status": "ACTIVE"
  }
]

Search Products

Search for products by sync source. GET /api/v1/products/search

Authentication

  • Secret key required

Query Parameters

ParameterTypeDescription
syncSourcestringSync source type (e.g., shopify)
syncSourceIdstringExternal ID from the sync source

Example

curl -X GET "https://admin.fanfare.io/api/v1/products/search?syncSource=shopify&syncSourceId=gid://shopify/Product/12345" \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"

Get Product

Get a single product by ID. GET /api/v1/products/:productId

Authentication

  • Secret key required

Path Parameters

ParameterTypeDescription
productIdstringThe product ID

Query Parameters

ParameterTypeDescription
includestringRelations to include (default: mediaAssets)

Example

curl -X GET "https://admin.fanfare.io/api/v1/products/prod_01HXYZ123456789?include=variants,mediaAssets" \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"

Error Responses

StatusErrorDescription
404Product not foundProduct ID does not exist

Update Product

Update an existing product. PUT /api/v1/products/:productId

Authentication

  • Secret key required

Path Parameters

ParameterTypeDescription
productIdstringThe product ID

Request Body

interface UpdateProductRequest {
  name?: string;
  description?: string | null;
  sku?: string | null;
  price?: number | null;
  currencyCode?: string | null;
  status?: "ACTIVE" | "DRAFT" | "ARCHIVED";
  metadata?: Record<string, unknown> | null;
}

Example

curl -X PUT https://admin.fanfare.io/api/v1/products/prod_01HXYZ123456789 \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "price": 179.99,
    "status": "DRAFT"
  }'

Delete Product

Archive a product. DELETE /api/v1/products/:productId

Authentication

  • Secret key required

Path Parameters

ParameterTypeDescription
productIdstringThe product ID

Example

curl -X DELETE https://admin.fanfare.io/api/v1/products/prod_01HXYZ123456789 \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"

Product Media Assets

Add Media Asset

Add a media asset to a product. POST /api/v1/products/:productId/media-assets

Request Body

interface AddMediaAssetRequest {
  mediaAsset?: {
    url: string;
    type: "IMAGE" | "VIDEO";
    altText?: string;
    width?: number;
    height?: number;
  };
  mediaAssetId?: string; // Use existing asset
  position: number;
  purpose: "PRODUCT" | "VARIANT" | "THUMBNAIL" | "GALLERY";
}

Example

curl -X POST https://admin.fanfare.io/api/v1/products/prod_01HXYZ123456789/media-assets \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "mediaAsset": {
      "url": "https://cdn.example.com/sneakers-side.jpg",
      "type": "IMAGE",
      "altText": "Side view"
    },
    "position": 1,
    "purpose": "GALLERY"
  }'

List Media Assets

GET /api/v1/products/:productId/media-assets

Update Media Asset

PUT /api/v1/products/:productId/media-assets/:assetId
interface UpdateMediaAssetRequest {
  position?: number;
  purpose?: "PRODUCT" | "VARIANT" | "THUMBNAIL" | "GALLERY";
}

Reorder Media Assets

PUT /api/v1/products/:productId/media-assets/reorder
interface ReorderRequest {
  assetOrder: string[]; // Array of media asset IDs in desired order
}

Example

curl -X PUT https://admin.fanfare.io/api/v1/products/prod_01HXYZ123456789/media-assets/reorder \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "assetOrder": ["ma_01HXYZ3", "ma_01HXYZ1", "ma_01HXYZ2"]
  }'

Remove Media Asset

DELETE /api/v1/products/:productId/media-assets/:assetId

Product Variant Options

Get Assigned Options

Get variant options assigned to a product. GET /api/v1/products/:productId/variant-options

Response

interface VariantOptionsResponse {
  assignments: Array<{
    organizationId: string;
    productId: string;
    optionId: string;
    required: boolean;
    sortOrder: number;
    option: VariantOption;
    values: VariantOptionValue[];
  }>;
}

interface VariantOption {
  id: string;
  name: string;
  displayName: string | null;
}

interface VariantOptionValue {
  id: string;
  optionId: string;
  value: string;
  displayValue: string | null;
  sortOrder: number;
}

Example

curl -X GET https://admin.fanfare.io/api/v1/products/prod_01HXYZ123456789/variant-options \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"
Response:
{
  "assignments": [
    {
      "organizationId": "org_01HXYZ123456789",
      "productId": "prod_01HXYZ123456789",
      "optionId": "opt_01HXYZ123456789",
      "required": true,
      "sortOrder": 0,
      "option": {
        "id": "opt_01HXYZ123456789",
        "name": "size",
        "displayName": "Size"
      },
      "values": [
        { "id": "val_01HXYZ1", "optionId": "opt_01HXYZ", "value": "S", "displayValue": "Small", "sortOrder": 0 },
        { "id": "val_01HXYZ2", "optionId": "opt_01HXYZ", "value": "M", "displayValue": "Medium", "sortOrder": 1 },
        { "id": "val_01HXYZ3", "optionId": "opt_01HXYZ", "value": "L", "displayValue": "Large", "sortOrder": 2 }
      ]
    }
  ]
}

Assign Options

Assign variant options to a product. POST /api/v1/products/:productId/variant-options

Request Body

interface AssignOptionsRequest {
  options: Array<{
    optionId: string;
    required?: boolean;
    sortOrder?: number;
    valueIds: string[];
  }>;
}

Example

curl -X POST https://admin.fanfare.io/api/v1/products/prod_01HXYZ123456789/variant-options \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "options": [
      {
        "optionId": "opt_01HXYZ123456789",
        "required": true,
        "sortOrder": 0,
        "valueIds": ["val_01HXYZ1", "val_01HXYZ2", "val_01HXYZ3"]
      }
    ]
  }'

Remove Option

Remove a variant option from a product. DELETE /api/v1/products/:productId/variant-options/:optionId

Error Responses

StatusErrorDescription
409Cannot remove: variants existDelete variants using this option first

Product Variants

Create Variant

POST /api/v1/products/:productId/variants

Request Body

interface CreateVariantRequest {
  name: string;
  sku?: string;
  price?: number;
  compareAtPrice?: number;
  inventoryQuantity?: number;
  optionValues: Array<{
    optionId: string;
    valueId: string;
  }>;
  metadata?: Record<string, unknown>;
}

Example

curl -X POST https://admin.fanfare.io/api/v1/products/prod_01HXYZ123456789/variants \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Limited Edition Sneakers - Size M",
    "sku": "SNKR-001-M",
    "price": 199.99,
    "inventoryQuantity": 50,
    "optionValues": [
      {
        "optionId": "opt_01HXYZ123456789",
        "valueId": "val_01HXYZ2"
      }
    ]
  }'

List Variants

GET /api/v1/products/:productId/variants

Update Variant

PUT /api/v1/products/:productId/variants/:variantId

Delete Variant

DELETE /api/v1/products/:productId/variants/:variantId

SDK Usage

import { FanfareAdmin } from "@fanfare/admin-sdk";

const admin = new FanfareAdmin({
  secretKey: process.env.FANFARE_SECRET_KEY,
});

// Create product
const product = await admin.products.create({
  name: "Limited Edition Sneakers",
  sku: "SNKR-001",
  price: 199.99,
  currencyCode: "USD",
  productMediaAssets: [
    {
      mediaAsset: {
        url: "https://cdn.example.com/sneakers.jpg",
        type: "IMAGE",
      },
      purpose: "PRODUCT",
      position: 0,
    },
  ],
});

// List products
const products = await admin.products.list({
  include: ["variants", "mediaAssets"],
});

// Update product
await admin.products.update(product.id, {
  price: 179.99,
});

// Add media asset
await admin.products.addMediaAsset(product.id, {
  mediaAsset: {
    url: "https://cdn.example.com/sneakers-side.jpg",
    type: "IMAGE",
  },
  purpose: "GALLERY",
  position: 1,
});

// Assign variant options
await admin.products.assignVariantOptions(product.id, {
  options: [{ optionId: "opt_size", required: true, valueIds: ["val_s", "val_m", "val_l"] }],
});

// Create variant
await admin.products.createVariant(product.id, {
  name: "Sneakers - Medium",
  sku: "SNKR-001-M",
  price: 199.99,
  inventoryQuantity: 50,
  optionValues: [{ optionId: "opt_size", valueId: "val_m" }],
});

// Delete product
await admin.products.delete(product.id);