Skip to content

Categories API#

The Categories endpoints allow you to retrieve existing categories from your Storyteller tenant. Categories are used for content organization and can be required metadata for certain workflows that need a categoryId.

Endpoints#

Get All Categories#

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

Get Category by External ID#

GET https://integrations.usestoryteller.com/api/categories/{externalId}

Headers#

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

Get All Categories#

Query Parameters#

Parameter Type Required Default Description
searchText string No - Filter categories by name
externalId string No - Filter categories by external ID
currentPage integer No 1 Page number for pagination
pageSize integer No 10 Number of items per page
sort string No - Sort order: AlphabeticalAsc, LastModifiedDesc

Response#

Success Response (200 OK)#

{
  "categories": [
    {
      "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "title": "Entertainment",
      "externalId": "entertainment",
      "description": "Entertainment content and media",
      "color": "#FF6B35",
      "itemCount": 25,
      "createdAt": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-20T14:22:00Z"
    },
    {
      "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
      "title": "Education",
      "externalId": "education",
      "description": "Educational and learning content",
      "color": "#4ECDC4",
      "itemCount": 12,
      "createdAt": "2024-01-10T08:15:00Z",
      "updatedAt": "2024-01-18T16:45:00Z"
    }
  ],
  "pageSize": 10,
  "currentPage": 1,
  "totalPages": 1
}

Get Category by External ID#

Path Parameters#

Parameter Type Required Description
externalId string Yes The external ID of the category

Response (200 OK)#

Returns a single category object with the same structure as shown above (without pagination fields).

Response (404 Not Found)#

{
  "status": 404,
  "type": "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4",
  "title": "Not Found",
  "detail": "Category with externalId entertainment not found",
  "instance": ""
}

Response Fields#

Category Object#

Field Type Description
id string Unique category identifier (use this for categoryId in workflow metadata)
title string Category display name
externalId string External identifier for the category
description string Category description (may be null)
color string Hex color code for category display
itemCount integer Number of items assigned to this category
createdAt string ISO timestamp when category was created
updatedAt string ISO timestamp when category was last modified

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#

# Get all categories (first page)
curl -X GET "https://integrations.usestoryteller.com/api/categories" \
  -H "x-storyteller-api-key: your-api-key-here"

# Search for specific categories
curl -X GET "https://integrations.usestoryteller.com/api/categories?searchText=entertainment&pageSize=20" \
  -H "x-storyteller-api-key: your-api-key-here"

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

async function getCategories(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/categories?${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 categories:', error); throw error; } }

// Usage examples async function examples() { // Get all categories const allCategories = await getCategories(); console.log(Found ${allCategories.categories.length} categories);

// Search for specific categories const searchResults = await getCategories('entertainment', 1, 20); console.log(Found ${searchResults.categories.length} categories matching "entertainment");

// Get category IDs for workflow metadata const categoryIds = allCategories.categories.map(cat => cat.id); console.log('Available category IDs:', categoryIds);

// Find popular categories (by item count) const popularCategories = allCategories.categories .sort((a, b) => b.itemCount - a.itemCount) .slice(0, 3); console.log('Most popular categories:', popularCategories.map(cat => ({ title: cat.title, itemCount: cat.itemCount }))); }

=== "Python"
    ```python
import requests
import os
from urllib.parse import urlencode

def get_categories(search_text='', current_page=1, page_size=10):
    url = 'https://integrations.usestoryteller.com/api/categories'
    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 categories: {e}')
        if hasattr(e.response, 'json'):
            print(f'Error details: {e.response.json()}')
        raise

# Usage examples
try:
    # Get all categories
    all_categories = get_categories()
    print(f'Found {len(all_categories["categories"])} categories')

    # Search for specific categories
    search_results = get_categories(search_text='education', page_size=20)
    print(f'Found {len(search_results["categories"])} categories matching "education"')

    # Extract category IDs for workflow usage
    category_ids = [cat['id'] for cat in all_categories['categories']]
    print('Available category IDs:', category_ids)

    # Find categories by external ID
    education_cat = next(
        (cat for cat in all_categories['categories'] if cat['externalId'] == 'education'),
        None
    )
    if education_cat:
        print(f'Education category ID: {education_cat["id"]}')

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

```csharp

using System; using System.Net.Http; using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json;

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

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

public async Task<CategoriesResponse> GetCategoriesAsync(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/categories?{queryString}");
        response.EnsureSuccessStatusCode();

        var responseContent = await response.Content.ReadAsStringAsync();
        var categories = JsonConvert.DeserializeObject<CategoriesResponse>(responseContent);

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

}

public class CategoriesResponse { public Category[] Categories { get; set; } public int PageSize { get; set; } public int CurrentPage { get; set; } public int TotalPages { get; set; } }

public class Category { public string Id { get; set; } public string Title { get; set; } public string ExternalId { get; set; } public string Description { get; set; } public string Color { get; set; } public int ItemCount { get; set; } public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } }

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

try { // Get all categories var allCategories = await client.GetCategoriesAsync(); Console.WriteLine($"Found {allCategories.Categories.Length} categories");

// Search for specific categories
var searchResults = await client.GetCategoriesAsync("entertainment", 1, 20);
Console.WriteLine($"Found {searchResults.Categories.Length} categories matching 'entertainment'");

// Find most popular categories
var popularCategories = allCategories.Categories
    .OrderByDescending(c => c.ItemCount)
    .Take(3)
    .ToArray();

Console.WriteLine("Most popular categories:");
foreach (var category in popularCategories)
{
    Console.WriteLine($"  {category.Title}: {category.ItemCount} items");
}

} catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } ```

Usage with Workflows#

When you have workflows that require a categoryId in their metadata, use this endpoint to get the available category IDs: javascript // 1. Get available categories const categoriesData = await getCategories(); // 2. Find category by external ID const entertainmentCategory = categoriesData.categories.find( cat => cat.externalId === 'entertainment' ); // 3. Use category ID in workflow metadata const workflowMetadata = { 'https://example.com/video.mp4': { 'Title': 'My Entertainment Video', 'categoryId': entertainmentCategory.id } }; // 4. Execute workflow with category metadata await executeWorkflow(['add-clip'], ['https://example.com/video.mp4'], workflowMetadata);

Advanced Usage Examples#

Category Management Dashboard#

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

  async getCategoryStatistics() {
    const categories = await getAllCategories(); // Pagination function below

    return {
      totalCategories: categories.length,
      totalItems: categories.reduce((sum, cat) => sum + cat.itemCount, 0),
      averageItemsPerCategory: categories.reduce((sum, cat) => sum + cat.itemCount, 0) / categories.length,
      mostPopularCategory: categories.reduce((max, cat) => cat.itemCount > max.itemCount ? cat : max),
      emptyCategories: categories.filter(cat => cat.itemCount === 0),
      colorDistribution: this.analyzeColors(categories)
    };
  }

  analyzeColors(categories) {
    const colorCount = {};
    categories.forEach(cat => {
      colorCount[cat.color] = (colorCount[cat.color] || 0) + 1;
    });
    return colorCount;
  }

  async findCategoryByExternalId(externalId) {
    const categories = await getAllCategories();
    return categories.find(cat => cat.externalId === externalId);
  }
}

Content Organization Helper#

async function organizeContentByCategory() {
  const categories = await getAllCategories();
  const clips = await getAllClips(); // From clips API

  // Create content map
  const contentMap = {};

  categories.forEach(category => {
    contentMap[category.title] = {
      category: category,
      clips: clips.filter(clip => 
        clip.categories.some(clipCat => clipCat.externalId === category.externalId)
      )
    };
  });

  // Find uncategorized content
  const uncategorizedClips = clips.filter(clip => clip.categories.length === 0);

  return {
    categorizedContent: contentMap,
    uncategorizedClips: uncategorizedClips,
    summary: {
      totalCategories: categories.length,
      totalClips: clips.length,
      uncategorizedCount: uncategorizedClips.length,
      categorizationRate: ((clips.length - uncategorizedClips.length) / clips.length * 100).toFixed(1) + '%'
    }
  };
}

Pagination Example#

async function getAllCategories() {
  let allCategories = [];
  let currentPage = 1;
  let totalPages = 1;

  do {
    const response = await getCategories('', currentPage, 50);
    allCategories.push(...response.categories);
    totalPages = response.totalPages;
    currentPage++;
  } while (currentPage <= totalPages);

  return allCategories;
}

// Usage
const allCategories = await getAllCategories();
console.log(`Retrieved ${allCategories.length} total categories`);

Category Selection Helper#

async function selectCategoryForContent(contentType, contentTitle) {
  const categories = await getCategories();

  // Smart category suggestions based on content
  const suggestions = categories.categories.filter(cat => {
    const titleLower = contentTitle.toLowerCase();
    const categoryLower = cat.title.toLowerCase();

    return titleLower.includes(categoryLower) || 
           categoryLower.includes(contentType.toLowerCase());
  });

  return {
    allCategories: categories.categories,
    suggestions: suggestions,
    mostPopular: categories.categories.sort((a, b) => b.itemCount - a.itemCount).slice(0, 5)
  };
}

// Usage
const categoryOptions = await selectCategoryForContent('video', 'Cooking Tutorial');
console.log('Suggested categories:', categoryOptions.suggestions.map(cat => cat.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. Cache Categories - Categories rarely change, consider caching for extended periods
  2. Use External IDs - External IDs are more stable than internal IDs for lookups
  3. Search Efficiently - Use search text to filter before client-side processing
  4. Handle Empty Results - Some tenants may have no categories configured
  5. Sort by Popularity - Use itemCount to prioritize commonly used categories
  6. Color Consistency - Use the provided color codes for consistent UI theming

Integration Patterns#

Workflow Metadata Builder#

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

  async initialize() {
    this.categories = await getAllCategories();
  }

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

    if (options.categoryExternalId) {
      const category = this.categories.find(cat => cat.externalId === options.categoryExternalId);
      if (category) {
        metadata.categoryId = category.id;
      }
    }

    if (options.autoSuggestCategory) {
      const suggestedCategory = this.suggestCategory(title);
      if (suggestedCategory) {
        metadata.categoryId = suggestedCategory.id;
      }
    }

    return { [mediaUrl]: metadata };
  }

  suggestCategory(title) {
    if (!this.categories) return null;

    const titleLower = title.toLowerCase();
    return this.categories.find(cat => 
      titleLower.includes(cat.title.toLowerCase()) ||
      titleLower.includes(cat.externalId.toLowerCase())
    );
  }
}