Skip to content

Assortment Publish Integration

Overview

This document outlines the architecture for integrating external systems with VibeIQ's Assortment Publish functionality. When users publish changes from a Plan, the system generates an AssortmentPublishChange event that downstream systems can consume to synchronize data.

Authentication & API Access

Authenticating with VibeIQ

VibeIQ provides three programmatic access patterns:

  • JavaScript/TypeScript SDK - @contrail/sdk
  • Java SDK
  • HTTPS REST API

Generating an API Key

To authenticate, you'll need an API key. Generate one using the Contrail CLI:

contrail app getApiKey

This generates a key in the format: app:uEL7GCBZ7Xhd4w2z

Note

VibeIQ can provide an access token directly if using the CLI is not preferred.

Authentication Examples

import { login } from '@contrail/sdk';

await login({
  apiKey: 'app:uEL7GCBZ7Xhd4w2z',
  orgSlug: 'your-org-slug'
});

After successful authentication, all SDK functions requiring authentication will work automatically.

curl --location 'https://api.vibeiq.com/prod/api/items/:itemId' \
--header 'X-Api-Key: app:uEL7GCBZ7Xhd4w2z' \
--header 'X-Api-Org: your-org-slug'

For HTTPS REST requests, pass the API key via the X-Api-Key header and organization slug via X-Api-Org header.

Authenticating from External Systems

If your integration requires VibeIQ to call your external system, you should provide:

  • API endpoint URL for receiving data
  • Preferred authentication scheme (e.g., Personal Access Tokens, API keys)
  • Any IP allowlisting requirements if needed

Understanding Assortment Publishes

Publish Workflow

  1. Users create and modify Items (Products, Materials, etc.) in VibeIQ Plans
  2. Users adopt Images, Colors, and other attributes onto these Items
  3. To persist changes to send them to downstream systems, users publish the Plan
  4. The system saves Plan rows to the underlying Assortment
  5. An AssortmentPublishChange object is created as a snapshot of those changes
  6. External systems can consume this change data

Key Concepts

Item

A global object representing a Product Family or Product Option (Colorway). Properties are global and apply everywhere in VibeIQ. They are typically referred to by other *Item types that indicate tighter levels of seasonal or assortment-level scopes.

  • Roles determine the type:
    • roles: ['family'] - Family-level Item
    • roles: ['option'] - Option-level Item
  • Unique identifier: id (string)
    • Example: Item abc = Item abc

ProjectItem

A seasonal object representing an Item within a specific Project (Season).

  • Unique identifier: id - concatenation of Project.id:Item.id
    • Example: Project 123 + Item abc = ProjectItem 123:abc
    • Properties are seasonal and apply only within that Project

AssortmentItem

A seasonal object representing an Item within a specific Assortment.

  • Unique identifier: id - concatenation of Assortment.id:Item.id
    • Example: Assortment 789 + Item xyz = AssortmentItem 789:xyz
    • Properties are applicable only to this Assortment, even if there are multiple Assortments in the same Project, and the same Item lives in those Assortments.

Publish Events

AssortmentPublish Event

When a Plan is published, VibeIQ generates an assortment|publish event:

interface AssortmentPublish {
    assortmentId: string;
    assortmentPublishChangeId: string;
}

Fields:

  • assortmentId - Identifier of the published Assortment
  • assortmentPublishChangeId - Identifier of the AssortmentPublishChange object containing detailed a detailed snapshot of the publish data

AssortmentPublishChange Object

interface AssortmentPublishChange {
  id: string;
  orgId?: string;
  assortmentId?: string;
  detailDownloadLink?: string;
  detail?: AssortmentPublishChangeDetail;
  assortmentBaselineDownloadLink?: string;
  deleteDataDownloadLink?: string;
  hydratedDetailDownloadLink?: string;
  createdOn?: Date;
  updatedOn?: Date;
  createdById?: string;
  updatedById?: string;
}

Key Properties:

Property Structure Description
assortmentBaselineDownloadLink
{
  "assortmentItems": [
    {
      "id": "assortmentId:itemId",
      "item": {
        "id": "itemId",
        "name": "Item Name",
        "itemFamily": {
          "id": "familyId",
          "name": "Family Name"
        }
      },
      "projectItem": {
        "id": "projectId:itemId"
      },
      "familyProjectItem": {
        "id": "projectId:familyId"
      }
    }
  ]
}
An AWS S3 URL pointing to a JSON file containing the Assortment's complete state at publish time. This URL expires after 24 hours. Retrieving the AssortmentPublishChange object again with another API call will refresh it.

  • assortmentItems: An array of AssortmentItem objects that indicate the Items inside of this Assortment. They include any AssortmentItem properties, if specified.
  • assortmentItems[n].item - The Item entity of the AssortmentItem This includes all Item-level properties for the Item.
    • This is almost always an Item Option instead of a Family. However, in rare cases only the Family Item may be in the Assortment. If this occurs, this will be the Item entity of the assortmentItems[n].item.itemFamily as if it were an Option, and assortmentItems[n].item.itemFamily will be null.
  • assortmentItems[n].item.itemFamily - The Item entity of the Family Item for the Option Item at assortmentItems[n].item. This includes all Item-level properties for the Family Item.
    • This will be null if only the Family Item is in the Assortment (rare case).
  • assortmentItems[n].projectItem - The ProjectItem entity for the Option Item at assortmentItems[n].item. This includes all ProjectItem-level properties for the Option Item within this specific Project (Season).
  • assortmentItems[n].familyProjectItem - The ProjectItem entity for the Family Item at assortmentItems[n].item.itemFamily. This includes all ProjectItem-level properties for the Family Item within this specific Project (Season).
    • This will be null if only the Family Item is in the Assortment (rare case).

Example request:

curl
curl --location '{{assortmentBaselineDownloadLink}}' \
--header 'X-Api-Key: your-api-key' \
--header 'X-Api-Org: your-org-slug'
JS
const baselineResponse = await fetch(`${assortmentBaselineDownloadLink}`, {
  headers: {
    'X-Api-Key': 'your-api-key',
    'X-Api-Org': 'your-org-slug'
  }
});
const baselineData = await baselineResponse.json();
deleteDataDownloadLink
{
  "assortmentItems": [
    {
      "id": "assortmentId:itemId",
      "item": { },
      "projectItem": { },
      "familyProjectItem": { }
    }
  ]
}
An S3 URL pointing to a JSON file with the same structure as the baseline, but containing only Items that were dropped or deleted in this specific publish.

⚠️ Warning: Delete data does NOT accumulate across publishes. Each AssortmentPublishChange only contains deletes from that individual publish.
detail
{
  "adds": [
    { "id": "assortmentId:itemId" }
  ],
  "updates": [
    { "id": "assortmentId:itemId" }
  ],
  "deletes": [
    { "id": "assortmentId:itemId" }
  ],
  "familyItemsRemoved": [
    { "id": "familyItemId" }
  ]
}
Contains granular change arrays:

  • adds - AssortmentItems added to the Assortment
  • updates - AssortmentItems updated in the Assortment
  • deletes - AssortmentItems removed from the Assortment
  • familyItemsRemoved - Family-level Items dropped (rare edge case)

ℹ️ Note: If arrays are too large, [name]DownloadURL properties will be provided instead to avoid excessive payload size.

Integration Patterns

Pattern 1: Event-Driven (Push)

VibeIQ pushes publish events to your external system in real-time.

Setup

  1. Provide an API endpoint to receive AssortmentPublish events
  2. Configure an Event Workflow in VibeIQ to forward events to your endpoint
  3. Your system receives the event and calls back to VibeIQ for full change data

Data Flow

sequenceDiagram participant User as VibeIQ App User participant VibeIQ as VibeIQ Rest API participant EventWorkflow as VibeIQ Event Workflow participant ExternalSystem as Your API User->>VibeIQ: Publish Plan VibeIQ->>VibeIQ: Generate AssortmentPublishChange VibeIQ->>EventWorkflow: Trigger AssortmentPublish event EventWorkflow->>ExternalSystem: POST AssortmentPublish ExternalSystem->>VibeIQ: GET AssortmentPublishChange VibeIQ->>ExternalSystem: Return change data ExternalSystem->>ExternalSystem: Process and ingest data

Implementation

Step 1: Receive the event

Your endpoint receives:

{
  "assortmentId": "25uDvGNpYhZHw2nt",
  "assortmentPublishChangeId": "lTuARQLrhgWHNr3k"
}

Step 2: Retrieve full change data

You then go retrieve the AssortmentPublishChange object by its assortmentId and assortmentPublishChangeId.

curl --location 'https://api.vibeiq.com/prod/api/assortments/25uDvGNpYhZHw2nt/history/lTuARQLrhgWHNr3k' \
--header 'X-Api-Key: your-api-key' \
--header 'X-Api-Org: your-org-slug'
import { Entities } from '@contrail/sdk';

const changeHistory = await new Entities().get({
  entityName: 'assortment',
  id: '25uDvGNpYhZHw2nt',
  suffix: 'history/lTuARQLrhgWHNr3k'
});

Step 3: Download baseline and process

Process the response to retrieve the publish data as described in the AssortmentPublishChange Object section.

JavaScript
// Get the baseline data
const baselineUrl = changeHistory.assortmentBaselineDownloadLink;
const baselineResponse = await fetch(baselineUrl, {
  headers: {
    'X-Api-Key': 'your-api-key',
    'X-Api-Org': 'your-org-slug'
  }
});
const baselineData = await baselineResponse.json();

// Process assortment items
for (const assortmentItem of baselineData.assortmentItems) {
  const item = assortmentItem.item;
  const itemFamily = assortmentItem.item.itemFamily;
  const projectItem = assortmentItem.projectItem;

  // Map to your system's data model
  // ...
}

Pattern 2: Polling (Pull)

Your external system periodically checks for new publishes. This makes an assumption that you have kept a record of all Assortment IDs that you'd like to track. You can also call the GET /assortments API and paginate through the response to retrieve all Assortment IDs in your org.

Setup

  1. Maintain a registry of VibeIQ Assortment IDs in your system, or call the GET /assortments API to retrieve all IDs in your org.
  2. Store the timestamp or ID of the last processed publish, or always just grab the latest one.
  3. Periodically poll the history API for new publishes on each Assortment ID.

Data Flow

sequenceDiagram participant ExternalSystem as Your API participant VibeIQ participant User User->>VibeIQ: Publish Plan VibeIQ->>VibeIQ: Generate AssortmentPublishChange loop Every N minutes ExternalSystem->>VibeIQ: GET /assortments/:id/history VibeIQ->>ExternalSystem: Return publish history ExternalSystem->>ExternalSystem: Check for new publishes alt New publish found ExternalSystem->>VibeIQ: GET specific change VibeIQ->>ExternalSystem: Return change data ExternalSystem->>ExternalSystem: Process and ingest end end

Implementation

Step 1: Poll for publishes

curl --location 'https://api.vibeiq.com/prod/api/assortments/:assortmentId/history' \
--header 'X-Api-Key: your-api-key' \
--header 'X-Api-Org: your-org-slug'
import { Entities } from '@contrail/sdk';

const history = await new Entities().get({
  entityName: 'assortment',
  id: assortmentId,
  suffix: 'history'
});

This returns an array of all AssortmentPublishChange objects for the Assortment.

Step 2: Filter for new publishes

JavaScript
const history = await fetch(
  `https://api.vibeiq.com/prod/api/assortments/${assortmentId}/history`,
  {
    headers: {
      'X-Api-Key': apiKey,
      'X-Api-Org': orgSlug
    }
  }
).then(r => r.json());

// Filter for publishes after last processed timestamp
const lastProcessedTime = getLastProcessedTimestamp(assortmentId);
const newPublishes = history.filter(
  change => new Date(change.createdOn) > lastProcessedTime
);

// Process each new publish
for (const change of newPublishes) {
  await processPublishChange(assortmentId, change.id);
  updateLastProcessedTimestamp(assortmentId, change.createdOn);
}

Step 3: Process and ingest data from each publish

Reference the Publish Events section to extract the data you need from the resulting payload.

Additional Resources