Skip to content

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 + pageSize or legacy-style skipCount + maxResultCount.

In legacy mode, omitted aliases fall back to skipCount=0 and maxResultCount=10. Because integrations responses still return currentPage, skipCount must 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#

  1. Cache Templates - Templates don't change frequently, cache them for better performance
  2. Validate Metadata - Always validate user input against template property requirements
  3. Handle Types Properly - Convert string inputs to proper types based on property definitions
  4. Required vs Optional - Clearly distinguish between required and optional properties in your UI
  5. Use Template Codes - Reference templates by their stable code rather than title
  6. Preview Images - Use preview images to help users select appropriate templates
  7. 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 workflows
  • src/components/PublishToStoryteller/ - Complete form implementation