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
pageSize integer No 10 Number of items per page (max 500)
sort string No - Sort order: AlphabeticalAsc, LastModifiedDesc

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
}

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

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