Skip to content

Collections API#

The Collections endpoint allows you to retrieve existing collections from your Storyteller tenant. Collections are used for grouping related content and can be required metadata for certain workflows that need a collectionId.

Endpoint Details#

GET https://integrations.usestoryteller.com/api/collections

Headers#

Header Required Description
x-storyteller-api-key Yes Your API key for authentication

Query Parameters#

Parameter Type Required Default Description
searchText string No - Filter collections by title
currentPage integer No 1 Page number for pagination
pageSize integer No 10 Number of items per page

Response#

Success Response (200 OK)#

{
  "collections": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-123456789012",
      "title": "Summer Campaign 2025",
      "description": "Marketing content for summer campaign",
      "thumbnailUrl": "https://media.usestoryteller.com/assets/collections/a1b2c3d4-e5f6-7890-abcd-123456789012/thumbnail.jpeg",
      "itemCount": 15,
      "categories": [
        {
          "title": "Marketing",
          "externalId": "marketing"
        }
      ],
      "isActive": true,
      "createdAt": "2025-01-15T10:30:00Z",
      "updatedAt": "2025-02-01T16:45:00Z"
    },
    {
      "id": "b2c3d4e5-f6g7-8901-bcde-234567890123",
      "title": "Product Tutorials",
      "description": "Step-by-step product guides and tutorials",
      "thumbnailUrl": "https://media.usestoryteller.com/assets/collections/b2c3d4e5-f6g7-8901-bcde-234567890123/thumbnail.jpeg",
      "itemCount": 8,
      "categories": [
        {
          "title": "Education",
          "externalId": "education"
        },
        {
          "title": "Product",
          "externalId": "product"
        }
      ],
      "isActive": true,
      "createdAt": "2024-12-01T09:15:00Z",
      "updatedAt": "2025-01-20T14:30:00Z"
    }
  ],
  "pageSize": 10,
  "currentPage": 1,
  "totalPages": 1
}

Response Fields#

Collection Object#

Field Type Description
id string Unique collection identifier (use this for collectionId in workflow metadata)
title string Collection display name
description string Collection description (may be null)
thumbnailUrl string URL to collection thumbnail image
itemCount integer Number of items in this collection
categories array Array of category objects assigned to the collection
isActive boolean Whether the collection is currently active
createdAt string ISO timestamp when collection was created
updatedAt string ISO timestamp when collection was last modified

Category Object#

Field Type Description
title string Category display name
externalId string External identifier for the category

Pagination Object#

Field Type Description
pageSize integer Number of items per page
currentPage integer Current page number
totalPages integer Total number of pages available

Code Examples#

```bash

Get all collections (first page)#

curl -X GET "https://integrations.usestoryteller.com/api/collections" \ -H "x-storyteller-api-key: your-api-key-here"

Search for specific collections#

curl -X GET "https://integrations.usestoryteller.com/api/collections?searchText=campaign&pageSize=20" \ -H "x-storyteller-api-key: your-api-key-here"

=== "JavaScript"
    ```javascript
const fetch = require('node-fetch');

async function getCollections(searchText = '', currentPage = 1, pageSize = 10) {
  const params = new URLSearchParams({
    ...(searchText && { searchText }),
    currentPage: currentPage.toString(),
    pageSize: pageSize.toString()
  });

  try {
    const response = await fetch(`https://integrations.usestoryteller.com/api/collections?${params}`, {
      method: 'GET',
      headers: {
        'x-storyteller-api-key': process.env.STORYTELLER_API_KEY
      }
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`HTTP ${response.status}: ${errorData.message}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error fetching collections:', error);
    throw error;
  }
}

// Usage examples
async function examples() {
  // Get all collections
  const allCollections = await getCollections();
  console.log(`Found ${allCollections.collections.length} collections`);

  // Search for specific collections
  const searchResults = await getCollections('campaign', 1, 20);
  console.log(`Found ${searchResults.collections.length} collections matching "campaign"`);

  // Get collection IDs for workflow metadata
  const collectionIds = allCollections.collections.map(col => col.id);
  console.log('Available collection IDs:', collectionIds);

  // Find active collections with content
  const activeCollections = allCollections.collections
    .filter(col => col.isActive && col.itemCount > 0)
    .sort((a, b) => b.itemCount - a.itemCount);
  console.log('Active collections with content:', activeCollections.map(col => ({
    title: col.title,
    itemCount: col.itemCount
  })));
}

```python

import requests import os from urllib.parse import urlencode

def get_collections(search_text='', current_page=1, page_size=10): url = 'https://integrations.usestoryteller.com/api/collections' headers = { 'x-storyteller-api-key': os.environ.get('STORYTELLER_API_KEY') }

params = {
    'currentPage': current_page,
    'pageSize': page_size
}

if search_text:
    params['searchText'] = search_text

try:
    response = requests.get(url, headers=headers, params=params)
    response.raise_for_status()

    data = response.json()
    return data
except requests.exceptions.RequestException as e:
    print(f'Error fetching collections: {e}')
    if hasattr(e.response, 'json'):
        print(f'Error details: {e.response.json()}')
    raise

Usage examples#

try: # Get all collections all_collections = get_collections() print(f'Found {len(all_collections["collections"])} collections')

# Search for specific collections
search_results = get_collections(search_text='tutorial', page_size=20)
print(f'Found {len(search_results["collections"])} collections matching "tutorial"')

# Extract collection IDs for workflow usage
collection_ids = [col['id'] for col in all_collections['collections']]
print('Available collection IDs:', collection_ids)

# Find collections by category
marketing_collections = [
    col for col in all_collections['collections'] 
    if any(cat['externalId'] == 'marketing' for cat in col['categories'])
]
print(f'Found {len(marketing_collections)} marketing collections')

except Exception as e: print(f'Failed to fetch collections: {e}')

=== "C#"
    ```csharp
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;

public class CollectionsClient
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl = "https://integrations.usestoryteller.com";

    public CollectionsClient(string apiKey)
    {
        _httpClient = new HttpClient();
        _httpClient.DefaultRequestHeaders.Add("x-storyteller-api-key", apiKey);
    }

    public async Task<CollectionsResponse> GetCollectionsAsync(string searchText = "", int currentPage = 1, int pageSize = 10)
    {
        try
        {
            var queryParams = new List<string>
            {
                $"currentPage={currentPage}",
                $"pageSize={pageSize}"
            };

            if (!string.IsNullOrEmpty(searchText))
            {
                queryParams.Add($"searchText={Uri.EscapeDataString(searchText)}");
            }

            var queryString = string.Join("&", queryParams);
            var response = await _httpClient.GetAsync($"{_baseUrl}/api/collections?{queryString}");
            response.EnsureSuccessStatusCode();

            var responseContent = await response.Content.ReadAsStringAsync();
            var collections = JsonConvert.DeserializeObject<CollectionsResponse>(responseContent);

            return collections;
        }
        catch (HttpRequestException ex)
        {
            throw new Exception($"Error fetching collections: {ex.Message}", ex);
        }
    }
}

public class CollectionsResponse
{
    public Collection[] Collections { get; set; }
    public int PageSize { get; set; }
    public int CurrentPage { get; set; }
    public int TotalPages { get; set; }
}

public class Collection
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string ThumbnailUrl { get; set; }
    public int ItemCount { get; set; }
    public Category[] Categories { get; set; }
    public bool IsActive { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
}

public class Category
{
    public string Title { get; set; }
    public string ExternalId { get; set; }
}

// Usage
var client = new CollectionsClient(Environment.GetEnvironmentVariable("STORYTELLER_API_KEY"));

try
{
    // Get all collections
    var allCollections = await client.GetCollectionsAsync();
    Console.WriteLine($"Found {allCollections.Collections.Length} collections");

    // Search for specific collections
    var searchResults = await client.GetCollectionsAsync("campaign", 1, 20);
    Console.WriteLine($"Found {searchResults.Collections.Length} collections matching 'campaign'");

    // Find largest collections
    var largestCollections = allCollections.Collections
        .Where(c => c.IsActive)
        .OrderByDescending(c => c.ItemCount)
        .Take(5)
        .ToArray();

    Console.WriteLine("Largest active collections:");
    foreach (var collection in largestCollections)
    {
        Console.WriteLine($"  {collection.Title}: {collection.ItemCount} items");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

Usage with Workflows#

When you have workflows that require a collectionId in their metadata, use this endpoint to get the available collection IDs:

// 1. Get available collections
const collectionsData = await getCollections();

// 2. Find collection by title or category
const campaignCollection = collectionsData.collections.find(
  col => col.title.toLowerCase().includes('campaign')
);

// 3. Use collection ID in workflow metadata
const workflowMetadata = {
  'https://example.com/video.mp4': {
    'Title': 'My Campaign Video',
    'collectionId': campaignCollection.id
  }
};

// 4. Execute workflow with collection metadata
await executeWorkflow(['add-to-collection'], ['https://example.com/video.mp4'], workflowMetadata);

Advanced Usage Examples#

Collection Management Dashboard#

class CollectionManager {
  constructor(apiKey) {
    this.apiKey = apiKey;
  }

  async getCollectionStatistics() {
    const collections = await getAllCollections(); // Pagination function below

    const stats = {
      totalCollections: collections.length,
      activeCollections: collections.filter(col => col.isActive).length,
      totalItems: collections.reduce((sum, col) => sum + col.itemCount, 0),
      averageItemsPerCollection: 0,
      largestCollection: null,
      emptyCollections: collections.filter(col => col.itemCount === 0),
      categoryDistribution: this.analyzeCategoryUsage(collections),
      recentActivity: this.findRecentlyUpdated(collections, 30) // Last 30 days
    };

    stats.averageItemsPerCollection = stats.totalItems / stats.totalCollections;
    stats.largestCollection = collections.reduce((max, col) => 
      col.itemCount > max.itemCount ? col : max
    );

    return stats;
  }

  analyzeCategoryUsage(collections) {
    const categoryCount = {};
    collections.forEach(col => {
      col.categories.forEach(cat => {
        categoryCount[cat.externalId] = (categoryCount[cat.externalId] || 0) + 1;
      });
    });
    return Object.entries(categoryCount)
      .map(([externalId, count]) => ({ externalId, count }))
      .sort((a, b) => b.count - a.count);
  }

  findRecentlyUpdated(collections, days) {
    const cutoff = new Date();
    cutoff.setDate(cutoff.getDate() - days);

    return collections.filter(col => new Date(col.updatedAt) > cutoff)
      .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
  }

  async findCollectionsByCategory(categoryExternalId) {
    const collections = await getAllCollections();
    return collections.filter(col => 
      col.categories.some(cat => cat.externalId === categoryExternalId)
    );
  }
}

Content Organization Helper#

async function organizeContentIntoCollections(contentItems, strategy = 'category') {
  const collections = await getAllCollections();
  const organization = {};

  switch (strategy) {
    case 'category':
      // Group by collection categories
      collections.forEach(collection => {
        collection.categories.forEach(category => {
          if (!organization[category.externalId]) {
            organization[category.externalId] = {
              collections: [],
              totalItems: 0
            };
          }
          organization[category.externalId].collections.push(collection);
          organization[category.externalId].totalItems += collection.itemCount;
        });
      });
      break;

    case 'size':
      // Group by collection size
      const sizeRanges = {
        'small': collections.filter(col => col.itemCount <= 5),
        'medium': collections.filter(col => col.itemCount > 5 && col.itemCount <= 20),
        'large': collections.filter(col => col.itemCount > 20)
      };

      Object.entries(sizeRanges).forEach(([size, cols]) => {
        organization[size] = {
          collections: cols,
          totalItems: cols.reduce((sum, col) => sum + col.itemCount, 0)
        };
      });
      break;

    case 'activity':
      // Group by recent activity
      const thirtyDaysAgo = new Date();
      thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);

      organization.recent = {
        collections: collections.filter(col => new Date(col.updatedAt) > thirtyDaysAgo),
        totalItems: 0
      };
      organization.older = {
        collections: collections.filter(col => new Date(col.updatedAt) <= thirtyDaysAgo),
        totalItems: 0
      };

      organization.recent.totalItems = organization.recent.collections
        .reduce((sum, col) => sum + col.itemCount, 0);
      organization.older.totalItems = organization.older.collections
        .reduce((sum, col) => sum + col.itemCount, 0);
      break;
  }

  return organization;
}

Pagination Example#

async function getAllCollections() {
  let allCollections = [];
  let currentPage = 1;
  let totalPages = 1;

  do {
    const response = await getCollections('', currentPage, 50);
    allCollections.push(...response.collections);
    totalPages = response.totalPages;
    currentPage++;
  } while (currentPage <= totalPages);

  return allCollections;
}

// Usage
const allCollections = await getAllCollections();
console.log(`Retrieved ${allCollections.length} total collections`);

Collection Selection Helper#

async function selectCollectionForContent(contentTitle, contentType, metadata = {}) {
  const collections = await getCollections();

  // Smart collection suggestions
  const suggestions = {
    titleMatch: collections.collections.filter(col => 
      col.title.toLowerCase().includes(contentTitle.toLowerCase()) ||
      contentTitle.toLowerCase().includes(col.title.toLowerCase())
    ),
    categoryMatch: collections.collections.filter(col => {
      if (metadata.categoryId) {
        return col.categories.some(cat => cat.externalId === metadata.categoryId);
      }
      return false;
    }),
    sizeAppropriate: collections.collections.filter(col => 
      col.itemCount < 50 && col.isActive // Not too crowded
    ),
    recentlyActive: collections.collections
      .filter(col => {
        const weekAgo = new Date();
        weekAgo.setDate(weekAgo.getDate() - 7);
        return new Date(col.updatedAt) > weekAgo;
      })
      .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
  };

  return {
    allCollections: collections.collections,
    suggestions: suggestions,
    recommended: suggestions.titleMatch.length > 0 
      ? suggestions.titleMatch[0]
      : suggestions.categoryMatch[0] || suggestions.sizeAppropriate[0]
  };
}

// Usage
const collectionOptions = await selectCollectionForContent(
  'Summer Beach Video',
  'video',
  { categoryId: 'marketing' }
);

console.log('Recommended collection:', collectionOptions.recommended?.title);

Error Handling#

Common Error Responses#

{
  "error": "Unauthorized",
  "message": "Invalid or missing API key",
  "statusCode": 401
}
{
  "error": "Bad Request",
  "message": "Invalid page size. Must be between 1 and 100",
  "statusCode": 400
}

Best Practices#

  1. Filter Active Collections - Focus on active collections for content placement
  2. Monitor Collection Size - Avoid overly large collections that become unwieldy
  3. Use Search Efficiently - Search by title to quickly find relevant collections
  4. Track Update Activity - Monitor updatedAt to identify actively managed collections
  5. Category Alignment - Match content categories with collection categories
  6. Cache Results - Collections change infrequently, consider caching

Integration Patterns#

Workflow Metadata Builder with Collection Logic#

class CollectionWorkflowBuilder {
  constructor() {
    this.collections = null;
    this.categories = null;
  }

  async initialize() {
    this.collections = await getAllCollections();
    this.categories = await getAllCategories(); // From categories API
  }

  async suggestBestCollection(contentTitle, contentMetadata) {
    if (!this.collections) await this.initialize();

    // Priority 1: Exact title match
    let match = this.collections.find(col => 
      col.title.toLowerCase() === contentTitle.toLowerCase()
    );
    if (match && match.isActive) return match;

    // Priority 2: Category match
    if (contentMetadata.categoryId) {
      const category = this.categories.find(cat => cat.id === contentMetadata.categoryId);
      if (category) {
        match = this.collections.find(col => 
          col.categories.some(cat => cat.externalId === category.externalId) &&
          col.itemCount < 30 && // Not too full
          col.isActive
        );
        if (match) return match;
      }
    }

    // Priority 3: Recent and appropriately sized
    match = this.collections
      .filter(col => col.isActive && col.itemCount < 20)
      .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))[0];

    return match;
  }

  async buildCollectionMetadata(mediaUrl, title, options = {}) {
    const metadata = { Title: title };

    if (options.collectionId) {
      metadata.collectionId = options.collectionId;
    } else if (options.autoSuggestCollection) {
      const suggestedCollection = await this.suggestBestCollection(title, options);
      if (suggestedCollection) {
        metadata.collectionId = suggestedCollection.id;
      }
    }

    return { [mediaUrl]: metadata };
  }
}