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#
- Cache Categories - Categories rarely change, consider caching for extended periods
- Use External IDs - External IDs are more stable than internal IDs for lookups
- Search Efficiently - Use search text to filter before client-side processing
- Handle Empty Results - Some tenants may have no categories configured
- Sort by Popularity - Use
itemCountto prioritize commonly used categories - 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())
);
}
}
Related Documentation#
- Stories API - Stories also use categories for organization
- Clips API - Clips are organized using categories
- Executing Workflows - Use category IDs in workflow metadata
- Authentication - API key setup and usage