Templates API#
The Templates endpoint allows you to retrieve workflow templates from your Storyteller tenant. Workflow templates define reusable configurations for content processing with customizable properties and parameters.
Endpoint Details#
GET https://integrations.usestoryteller.com/api/templates
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 templates by title or code |
currentPage |
integer | No | 1 | Page number for pagination. Ignored when skipCount or maxResultCount is supplied. |
pageSize |
integer | No | 10 | Number of items per page. Must be between 1 and 1000. Ignored when skipCount or maxResultCount is supplied. |
skipCount |
integer | No | 0 | Legacy offset-style pagination alias. Defaults to 0 when omitted while using legacy pagination. |
maxResultCount |
integer | No | 10 | Legacy offset-style page-size alias. Defaults to 10 when omitted while using legacy pagination. Maximum 1000. |
sort |
string | No | - | Sort order: AlphabeticalAsc, LastModifiedDesc |
You can page this endpoint using either
currentPage+pageSizeor legacy-styleskipCount+maxResultCount.In legacy mode, omitted aliases fall back to
skipCount=0andmaxResultCount=10. Because integrations responses still returncurrentPage,skipCountmust land on a page boundary for the effective page size.
Response#
Success Response (200 OK)#
{
"templates": [
{
"code": "video-to-story",
"title": "Video to Story Template",
"previewImageUrl": "https://media.usestoryteller.com/templates/video-story-preview.jpg",
"properties": [
{
"key": "title",
"remarks": "The title for the story",
"type": "string",
"isOptional": false
},
{
"key": "storyId",
"remarks": "ID of the story to add content to",
"type": "string",
"isOptional": false
},
{
"key": "category",
"remarks": "Category to assign to the content",
"type": "string",
"isOptional": true
},
{
"key": "autoPublish",
"remarks": "Whether to automatically publish the content",
"type": "boolean",
"isOptional": true
}
]
},
{
"code": "image-slideshow",
"title": "Image Slideshow Template",
"previewImageUrl": "https://media.usestoryteller.com/templates/slideshow-preview.jpg",
"properties": [
{
"key": "title",
"remarks": "The title for the slideshow",
"type": "string",
"isOptional": false
},
{
"key": "duration",
"remarks": "Duration per slide in seconds",
"type": "number",
"isOptional": true
},
{
"key": "transition",
"remarks": "Transition effect between slides",
"type": "string",
"isOptional": true
}
]
}
],
"pageSize": 10,
"currentPage": 1,
"totalPages": 1,
"totalCount": 1
}
Response Fields#
Template Object#
| Field | Type | Description |
|---|---|---|
code |
string | Unique template identifier/code |
title |
string | Human-readable template name |
previewImageUrl |
string | URL to template preview image |
properties |
array | Array of configurable template properties |
Property Object#
| Field | Type | Description |
|---|---|---|
key |
string | Property key name for metadata |
remarks |
string | Human-readable description of the property |
type |
string | Property data type: string, number, boolean, datetime |
isOptional |
boolean | Whether the property is optional or required |
Pagination Object#
| Field | Type | Description |
|---|---|---|
pageSize |
integer | Number of items per page |
currentPage |
integer | Current page number |
totalPages |
integer | Total number of pages available |
totalCount |
integer | Exact total number of matching templates |
Code Examples#
const fetch = require('node-fetch');
async function getTemplates(searchText = '', currentPage = 1, pageSize = 10, sort = '') {
const params = new URLSearchParams({
...(searchText && { searchText }),
currentPage: currentPage.toString(),
pageSize: pageSize.toString(),
...(sort && { sort })
});
try {
const response = await fetch(`https://integrations.usestoryteller.com/api/templates?${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.detail}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching templates:', error);
throw error;
}
}
// Usage examples
async function examples() {
// Get all templates
const allTemplates = await getTemplates();
console.log(`Found ${allTemplates.templates.length} templates`);
// Search for video templates
const videoTemplates = await getTemplates('video', 1, 20);
console.log(`Found ${videoTemplates.templates.length} video templates`);
// Get templates sorted alphabetically
const sortedTemplates = await getTemplates('', 1, 50, 'AlphabeticalAsc');
console.log(`Retrieved ${sortedTemplates.templates.length} templates sorted alphabetically`);
// Analyze template properties
allTemplates.templates.forEach(template => {
console.log(`Template: ${template.title} (${template.code})`);
console.log('Required properties:',
template.properties.filter(p => !p.isOptional).map(p => p.key)
);
console.log('Optional properties:',
template.properties.filter(p => p.isOptional).map(p => p.key)
);
});
}
import requests
import os
from urllib.parse import urlencode
def get_templates(search_text='', current_page=1, page_size=10, sort=''):
url = 'https://integrations.usestoryteller.com/api/templates'
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
if sort:
params['sort'] = sort
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 templates: {e}')
if hasattr(e.response, 'json'):
print(f'Error details: {e.response.json()}')
raise
# Usage examples
try:
# Get all templates
all_templates = get_templates()
print(f'Found {len(all_templates["templates"])} templates')
# Search for video templates
video_templates = get_templates(search_text='video', page_size=20)
print(f'Found {len(video_templates["templates"])} video templates')
# Get templates sorted alphabetically
sorted_templates = get_templates(page_size=50, sort='AlphabeticalAsc')
print(f'Retrieved {len(sorted_templates["templates"])} templates sorted alphabetically')
# Analyze template properties
for template in all_templates['templates']:
print(f'Template: {template["title"]} ({template["code"]})')
required_props = [p['key'] for p in template['properties'] if not p['isOptional']]
optional_props = [p['key'] for p in template['properties'] if p['isOptional']]
print(f'Required properties: {required_props}')
print(f'Optional properties: {optional_props}')
except Exception as e:
print(f'Failed to fetch templates: {e}')
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using Newtonsoft.Json;
public class TemplatesClient
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl = "https://integrations.usestoryteller.com";
public TemplatesClient(string apiKey)
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("x-storyteller-api-key", apiKey);
}
public async Task<TemplatesResponse> GetTemplatesAsync(string searchText = "", int currentPage = 1, int pageSize = 10, string sort = "")
{
try
{
var queryParams = new List<string>
{
$"currentPage={currentPage}",
$"pageSize={pageSize}"
};
if (!string.IsNullOrEmpty(searchText))
{
queryParams.Add($"searchText={Uri.EscapeDataString(searchText)}");
}
if (!string.IsNullOrEmpty(sort))
{
queryParams.Add($"sort={sort}");
}
var queryString = string.Join("&", queryParams);
var response = await _httpClient.GetAsync($"{_baseUrl}/api/templates?{queryString}");
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var templates = JsonConvert.DeserializeObject<TemplatesResponse>(responseContent);
return templates;
}
catch (HttpRequestException ex)
{
throw new Exception($"Error fetching templates: {ex.Message}", ex);
}
}
}
public class TemplatesResponse
{
public WorkflowTemplate[] Templates { get; set; }
public int PageSize { get; set; }
public int CurrentPage { get; set; }
public int TotalPages { get; set; }
}
public class WorkflowTemplate
{
public string Code { get; set; }
public string Title { get; set; }
public string PreviewImageUrl { get; set; }
public TemplateProperty[] Properties { get; set; }
}
public class TemplateProperty
{
public string Key { get; set; }
public string Remarks { get; set; }
public string Type { get; set; }
public bool IsOptional { get; set; }
}
// Usage
var client = new TemplatesClient(Environment.GetEnvironmentVariable("STORYTELLER_API_KEY"));
try
{
// Get all templates
var allTemplates = await client.GetTemplatesAsync();
Console.WriteLine($"Found {allTemplates.Templates.Length} templates");
// Search for video templates
var videoTemplates = await client.GetTemplatesAsync("video", 1, 20);
Console.WriteLine($"Found {videoTemplates.Templates.Length} video templates");
// Get templates sorted alphabetically
var sortedTemplates = await client.GetTemplatesAsync("", 1, 50, "AlphabeticalAsc");
Console.WriteLine($"Retrieved {sortedTemplates.Templates.Length} templates sorted alphabetically");
// Analyze template properties
foreach (var template in allTemplates.Templates)
{
Console.WriteLine($"Template: {template.Title} ({template.Code})");
var requiredProps = template.Properties.Where(p => !p.IsOptional).Select(p => p.Key);
var optionalProps = template.Properties.Where(p => p.IsOptional).Select(p => p.Key);
Console.WriteLine($"Required properties: {string.Join(", ", requiredProps)}");
Console.WriteLine($"Optional properties: {string.Join(", ", optionalProps)}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
# Get all templates (first page)
curl -X GET "https://integrations.usestoryteller.com/api/templates" \
-H "x-storyteller-api-key: your-api-key-here"
# Search for video templates
curl -X GET "https://integrations.usestoryteller.com/api/templates?searchText=video&pageSize=20" \
-H "x-storyteller-api-key: your-api-key-here"
# Get templates sorted alphabetically
curl -X GET "https://integrations.usestoryteller.com/api/templates?sort=AlphabeticalAsc&pageSize=50" \
-H "x-storyteller-api-key: your-api-key-here"
# Get second page of templates
curl -X GET "https://integrations.usestoryteller.com/api/templates?currentPage=2&pageSize=10" \
-H "x-storyteller-api-key: your-api-key-here"
Usage with Workflows#
Templates provide the structure for workflow execution. Use template information to build proper metadata:
// 1. Get template information
const templatesData = await getTemplates();
const videoTemplate = templatesData.templates.find(t => t.code === 'video-to-story');
// 2. Build metadata based on template properties
function buildMetadataFromTemplate(template, userValues) {
const metadata = {};
template.properties.forEach(property => {
if (userValues[property.key] !== undefined) {
// Convert value based on property type
switch (property.type) {
case 'boolean':
metadata[property.key] = Boolean(userValues[property.key]);
break;
case 'number':
metadata[property.key] = Number(userValues[property.key]);
break;
default:
metadata[property.key] = String(userValues[property.key]);
}
} else if (!property.isOptional) {
throw new Error(`Required property '${property.key}' is missing`);
}
});
return metadata;
}
// 3. Create metadata from template
const userValues = {
title: 'My Product Demo',
storyId: 'story-123',
category: 'products',
autoPublish: true
};
const metadata = {
'https://example.com/video.mp4': buildMetadataFromTemplate(videoTemplate, userValues)
};
// 4. Execute workflow with template-based metadata
await executeWorkflow([videoTemplate.code], ['https://example.com/video.mp4'], metadata);
Template Validation#
function validateTemplateMetadata(template, metadata) {
const errors = [];
const providedKeys = Object.keys(metadata);
// Check required properties
template.properties.forEach(property => {
if (!property.isOptional && !providedKeys.includes(property.key)) {
errors.push(`Missing required property: ${property.key}`);
}
// Type validation
if (metadata[property.key] !== undefined) {
const value = metadata[property.key];
const expectedType = property.type;
switch (expectedType) {
case 'boolean':
if (typeof value !== 'boolean') {
errors.push(`Property '${property.key}' must be a boolean`);
}
break;
case 'number':
if (typeof value !== 'number' || isNaN(value)) {
errors.push(`Property '${property.key}' must be a number`);
}
break;
case 'string':
if (typeof value !== 'string') {
errors.push(`Property '${property.key}' must be a string`);
}
break;
}
}
});
return errors;
}
// Usage
const errors = validateTemplateMetadata(videoTemplate, userValues);
if (errors.length > 0) {
console.error('Validation errors:', errors);
} else {
console.log('Metadata is valid for template');
}
Dynamic Form Generation#
function generateFormFromTemplate(template) {
console.log(`Template: ${template.title}`);
console.log('Required fields:');
template.properties.forEach(property => {
const required = property.isOptional ? ' (optional)' : ' (required)';
console.log(`- ${property.key} (${property.type})${required}: ${property.remarks}`);
});
}
// Generate form structure for all templates
async function generateForms() {
const templatesData = await getTemplates();
templatesData.templates.forEach(template => {
generateFormFromTemplate(template);
console.log('---');
});
}
Pagination Example#
async function getAllTemplates() {
let allTemplates = [];
let currentPage = 1;
let totalPages = 1;
do {
const response = await getTemplates('', currentPage, 50);
allTemplates.push(...response.templates);
totalPages = response.totalPages;
currentPage++;
} while (currentPage <= totalPages);
return allTemplates;
}
// Usage
const allTemplates = await getAllTemplates();
console.log(`Retrieved ${allTemplates.length} total templates`);
Error Handling#
Common Error Responses#
{
"status": 401,
"type": "https://datatracker.ietf.org/doc/html/rfc7235#section-3.1",
"title": "Unauthorized",
"detail": "No valid API key provided",
"instance": ""
}
{
"status": 400,
"type": "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
"title": "Bad Request",
"detail": "Invalid page size. Must be between 1 and 100",
"instance": ""
}
Best Practices#
- Cache Templates - Templates don't change frequently, cache them for better performance
- Validate Metadata - Always validate user input against template property requirements
- Handle Types Properly - Convert string inputs to proper types based on property definitions
- Required vs Optional - Clearly distinguish between required and optional properties in your UI
- Use Template Codes - Reference templates by their stable code rather than title
- Preview Images - Use preview images to help users select appropriate templates
- Property Documentation - Show property remarks to help users understand what each field does
Template Property Types#
| Type | Description | Example Values |
|---|---|---|
string |
Text value | "My Title", "product-category" |
number |
Numeric value | 42, 3.14, 0 |
boolean |
True/false value | true, false |
datetime |
ISO timestamp | "2024-03-15T12:00:00Z" |
Complete Implementation Example#
For a complete working example that demonstrates how to build a form interface for handling template metadata and properties, see the Storyteller Sample Integration repository.
This Next.js application shows:
- Template Properties Handling: Dynamically generating form fields based on template properties
- Metadata Collection: Building metadata objects from user input
- Form Validation: Handling required vs optional template properties
- Workflow Execution: Submitting templates with metadata to the Integration API
Key files to examine:
src/api/requests.ts- API integration methods for templates and workflowssrc/components/PublishToStoryteller/- Complete form implementation
Related Documentation#
- Executing Workflows - Use templates in workflow execution
- Validation API - Validate workflow payloads before execution
- Getting Workflows - Get available workflows that use templates
- Authentication - API key setup and usage
- Troubleshooting - Common issues and solutions