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#
- Filter Active Collections - Focus on active collections for content placement
- Monitor Collection Size - Avoid overly large collections that become unwieldy
- Use Search Efficiently - Search by title to quickly find relevant collections
- Track Update Activity - Monitor
updatedAtto identify actively managed collections - Category Alignment - Match content categories with collection categories
- 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 };
}
}
Related Documentation#
- Stories API - Stories can be organized in collections
- Clips API - Clips can be part of collections
- Categories API - Collections are categorized using categories
- Executing Workflows - Use collection IDs in workflow metadata
- Authentication - API key setup and usage