Getting Workflows#
The GET /api/workflows endpoint allows you to retrieve the list of available workflows for your Storyteller account. This is typically the first API call you'll make when integrating with Storyteller.
Endpoint Details#
GET https://integrations.usestoryteller.com/api/workflows
Headers#
| Header | Required | Description |
|---|---|---|
x-storyteller-api-key |
Yes | Your API key for authentication |
Content-Type |
Yes | application/json |
Response#
The endpoint returns a JSON object containing an array of available workflows:
{
"workflows": [
{
"id": "add-media",
"title": "Add to Media Library",
"requiredMetadata": [
{
"name": "Title",
"fieldLabel": "Content Title",
"type": "string",
"remarks": "The title or name of the content"
}
],
"optionalMetadata": [
{
"name": "Description",
"fieldLabel": "Content Description",
"type": "string",
"remarks": "Optional description of the content"
},
{
"name": "Tags",
"fieldLabel": "Content Tags",
"type": "string",
"remarks": "Comma-separated tags for categorization"
}
]
},
{
"id": "add-clip",
"title": "Add Clip",
"requiredMetadata": [
{
"name": "Title",
"fieldLabel": "Clip Title",
"type": "string"
}
],
"optionalMetadata": []
},
{
"id": "create-story",
"title": "Create Story",
"requiredMetadata": [
{
"name": "Title",
"fieldLabel": "Story Title",
"type": "string"
}
],
"optionalMetadata": [
{
"name": "Description",
"fieldLabel": "Story Description",
"type": "string",
"remarks": "Brief description of the story content"
}
]
}
]
}
Code Examples#
curl -X GET "https://integrations.usestoryteller.com/api/workflows" \
-H "x-storyteller-api-key: your-api-key-here" \
-H "Content-Type: application/json"
const fetch = require('node-fetch');
async function getWorkflows() {
try {
const response = await fetch('https://integrations.usestoryteller.com/api/workflows', {
method: 'GET',
headers: {
'x-storyteller-api-key': process.env.STORYTELLER_API_KEY,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.workflows;
} catch (error) {
console.error('Error fetching workflows:', error);
throw error;
}
}
// Usage
getWorkflows()
.then(workflows => {
console.log('Available workflows:', workflows.length);
workflows.forEach(workflow => {
console.log(`\nš ${workflow.name} (${workflow.id})`);
console.log(` ${workflow.description}`);
if (workflow.requiredMetadata && workflow.requiredMetadata.length > 0) {
console.log(' Required metadata:');
workflow.requiredMetadata.forEach(meta => {
console.log(` - ${meta.key}: ${meta.description}`);
});
}
if (workflow.optionalMetadata && workflow.optionalMetadata.length > 0) {
console.log(' Optional metadata:');
workflow.optionalMetadata.forEach(meta => {
console.log(` - ${meta.key}: ${meta.description}`);
});
}
});
})
.catch(error => console.error('Failed to get workflows:', error));
import requests
import os
from typing import List, Dict, Optional
def get_workflows() -> List[Dict]:
"""Fetch available workflows from the Storyteller Integration API."""
url = 'https://integrations.usestoryteller.com/api/workflows'
headers = {
'x-storyteller-api-key': os.environ.get('STORYTELLER_API_KEY'),
'Content-Type': 'application/json'
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json()
return data.get('workflows', [])
except requests.exceptions.RequestException as e:
print(f'Error fetching workflows: {e}')
raise
def display_workflow_info(workflows: List[Dict]) -> None:
"""Display formatted workflow information."""
print(f'Available workflows: {len(workflows)}')
for workflow in workflows:
print(f'\nš {workflow["name"]} ({workflow["id"]})')
print(f' {workflow["description"]}')
if workflow.get('requiredMetadata'):
print(' Required metadata:')
for meta in workflow['requiredMetadata']:
print(f' - {meta["key"]}: {meta["description"]}')
if workflow.get('optionalMetadata'):
print(' Optional metadata:')
for meta in workflow['optionalMetadata']:
print(f' - {meta["key"]}: {meta["description"]}')
# Usage
if __name__ == '__main__':
try:
workflows = get_workflows()
display_workflow_info(workflows)
except Exception as e:
print(f'Failed to get workflows: {e}')
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using Newtonsoft.Json;
public class WorkflowsClient : IDisposable
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl = "https://integrations.usestoryteller.com";
public WorkflowsClient(string apiKey)
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("x-storyteller-api-key", apiKey);
_httpClient.DefaultRequestHeaders.Add("Content-Type", "application/json");
}
public async Task<WorkflowsResponse> GetWorkflowsAsync()
{
try
{
var response = await _httpClient.GetAsync($"{_baseUrl}/api/workflows");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<WorkflowsResponse>(content);
}
catch (HttpRequestException ex)
{
throw new Exception($"Error fetching workflows: {ex.Message}", ex);
}
}
public void DisplayWorkflowInfo(WorkflowsResponse response)
{
Console.WriteLine($"Available workflows: {response.Workflows.Length}");
foreach (var workflow in response.Workflows)
{
Console.WriteLine($"\nš {workflow.Name} ({workflow.Id})");
Console.WriteLine($" {workflow.Description}");
if (workflow.RequiredMetadata != null && workflow.RequiredMetadata.Length > 0)
{
Console.WriteLine(" Required metadata:");
foreach (var meta in workflow.RequiredMetadata)
{
Console.WriteLine($" - {meta.Key}: {meta.Description}");
}
}
if (workflow.OptionalMetadata != null && workflow.OptionalMetadata.Length > 0)
{
Console.WriteLine(" Optional metadata:");
foreach (var meta in workflow.OptionalMetadata)
{
Console.WriteLine($" - {meta.Key}: {meta.Description}");
}
}
}
}
public void Dispose()
{
_httpClient?.Dispose();
}
}
public class WorkflowsResponse
{
[JsonProperty("workflows")]
public Workflow[] Workflows { get; set; }
}
public class Workflow
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("requiredMetadata")]
public MetadataField[] RequiredMetadata { get; set; }
[JsonProperty("optionalMetadata")]
public MetadataField[] OptionalMetadata { get; set; }
}
public class MetadataField
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("fieldLabel")]
public string FieldLabel { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
}
// Usage
public class Program
{
public static async Task Main(string[] args)
{
using var client = new WorkflowsClient(Environment.GetEnvironmentVariable("STORYTELLER_API_KEY"));
try
{
var result = await client.GetWorkflowsAsync();
client.DisplayWorkflowInfo(result);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Understanding the Response#
Workflow Object#
Each workflow in the response contains:
| Field | Type | Description |
|---|---|---|
id |
string | Unique identifier for the workflow |
name |
string | Human-readable name of the workflow |
description |
string | Detailed description of what the workflow does |
requiredMetadata |
array | Metadata fields that must be provided |
optionalMetadata |
array | Metadata fields that can optionally be provided |
Metadata Fields#
Both requiredMetadata and optionalMetadata contain arrays of metadata field objects:
| Field | Type | Description |
|---|---|---|
key |
string | The metadata key to use in your execute request |
fieldLabel |
string | Human-readable label for UI display |
description |
string | Explanation of what this metadata field is used for |
Using Workflow Information#
Once you have the list of workflows, you can:
- Present Options to Users - Use the
nameanddescriptionto show workflow options in your UI - Build Metadata Forms - Use
requiredMetadataandoptionalMetadatato create forms - Validate Requests - Ensure required metadata is provided before executing workflows
- Cache Results - Store workflow information to avoid repeated API calls
Example: Building a Metadata Form#
function buildMetadataForm(workflow) {
const form = document.createElement('form');
form.className = 'workflow-form';
// Add workflow information
const workflowInfo = document.createElement('div');
workflowInfo.className = 'workflow-info';
workflowInfo.innerHTML = `
<h3>${workflow.name}</h3>
<p>${workflow.description}</p>
`;
form.appendChild(workflowInfo);
// Add required fields
if (workflow.requiredMetadata && workflow.requiredMetadata.length > 0) {
const requiredSection = document.createElement('div');
requiredSection.className = 'required-fields';
requiredSection.innerHTML = '<h4>Required Information</h4>';
workflow.requiredMetadata.forEach(field => {
const fieldDiv = document.createElement('div');
fieldDiv.className = 'form-field required';
fieldDiv.innerHTML = `
<label for="${field.key}" class="required">
${field.fieldLabel} *
</label>
<input type="text" id="${field.key}" name="${field.key}" required />
<small class="field-description">${field.description}</small>
`;
requiredSection.appendChild(fieldDiv);
});
form.appendChild(requiredSection);
}
// Add optional fields
if (workflow.optionalMetadata && workflow.optionalMetadata.length > 0) {
const optionalSection = document.createElement('div');
optionalSection.className = 'optional-fields';
optionalSection.innerHTML = '<h4>Optional Information</h4>';
workflow.optionalMetadata.forEach(field => {
const fieldDiv = document.createElement('div');
fieldDiv.className = 'form-field optional';
fieldDiv.innerHTML = `
<label for="${field.key}">
${field.fieldLabel}
</label>
<input type="text" id="${field.key}" name="${field.key}" />
<small class="field-description">${field.description}</small>
`;
optionalSection.appendChild(fieldDiv);
});
form.appendChild(optionalSection);
}
// Add submit button
const submitButton = document.createElement('button');
submitButton.type = 'submit';
submitButton.textContent = `Execute ${workflow.name}`;
submitButton.className = 'submit-button';
form.appendChild(submitButton);
return form;
}
// Usage example
getWorkflows()
.then(workflows => {
const workflowSelect = document.getElementById('workflow-select');
const formContainer = document.getElementById('form-container');
// Populate workflow selector
workflows.forEach(workflow => {
const option = document.createElement('option');
option.value = workflow.id;
option.textContent = workflow.name;
workflowSelect.appendChild(option);
});
// Handle workflow selection
workflowSelect.addEventListener('change', (e) => {
const selectedWorkflow = workflows.find(w => w.id === e.target.value);
if (selectedWorkflow) {
formContainer.innerHTML = '';
formContainer.appendChild(buildMetadataForm(selectedWorkflow));
}
});
});
def build_metadata_form_data(workflow: Dict) -> Dict:
"""Build form configuration data for a workflow."""
form_config = {
'workflow_id': workflow['id'],
'workflow_name': workflow['name'],
'workflow_description': workflow['description'],
'required_fields': [],
'optional_fields': []
}
# Process required metadata
if workflow.get('requiredMetadata'):
for field in workflow['requiredMetadata']:
form_config['required_fields'].append({
'key': field['key'],
'label': field['fieldLabel'],
'description': field['description'],
'required': True,
'type': 'text' # Could be enhanced to support different field types
})
# Process optional metadata
if workflow.get('optionalMetadata'):
for field in workflow['optionalMetadata']:
form_config['optional_fields'].append({
'key': field['key'],
'label': field['fieldLabel'],
'description': field['description'],
'required': False,
'type': 'text'
})
return form_config
def generate_html_form(form_config: Dict) -> str:
"""Generate HTML form from workflow configuration."""
html = f"""
<form class="workflow-form" data-workflow-id="{form_config['workflow_id']}">
<div class="workflow-info">
<h3>{form_config['workflow_name']}</h3>
<p>{form_config['workflow_description']}</p>
</div>
"""
# Add required fields
if form_config['required_fields']:
html += '<div class="required-fields"><h4>Required Information</h4>'
for field in form_config['required_fields']:
html += f"""
<div class="form-field required">
<label for="{field['key']}" class="required">
{field['label']} *
</label>
<input type="{field['type']}" id="{field['key']}" name="{field['key']}" required />
<small class="field-description">{field['description']}</small>
</div>
"""
html += '</div>'
# Add optional fields
if form_config['optional_fields']:
html += '<div class="optional-fields"><h4>Optional Information</h4>'
for field in form_config['optional_fields']:
html += f"""
<div class="form-field optional">
<label for="{field['key']}">
{field['label']}
</label>
<input type="{field['type']}" id="{field['key']}" name="{field['key']}" />
<small class="field-description">{field['description']}</small>
</div>
"""
html += '</div>'
html += f"""
<button type="submit" class="submit-button">
Execute {form_config['workflow_name']}
</button>
</form>
"""
return html
# Usage example
try:
workflows = get_workflows()
for workflow in workflows:
form_config = build_metadata_form_data(workflow)
html_form = generate_html_form(form_config)
print(f"Form HTML for {workflow['name']}:")
print(html_form)
print("-" * 50)
except Exception as e:
print(f"Error generating forms: {e}")
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
public class MetadataFormBuilder
{
public class FormField
{
public string Key { get; set; }
public string Label { get; set; }
public string Description { get; set; }
public bool Required { get; set; }
public string Type { get; set; } = "text";
}
public class FormConfiguration
{
public string WorkflowId { get; set; }
public string WorkflowName { get; set; }
public string WorkflowDescription { get; set; }
public List<FormField> RequiredFields { get; set; } = new List<FormField>();
public List<FormField> OptionalFields { get; set; } = new List<FormField>();
}
public FormConfiguration BuildFormConfiguration(Workflow workflow)
{
var config = new FormConfiguration
{
WorkflowId = workflow.Id,
WorkflowName = workflow.Name,
WorkflowDescription = workflow.Description
};
// Process required metadata
if (workflow.RequiredMetadata != null)
{
config.RequiredFields = workflow.RequiredMetadata.Select(field => new FormField
{
Key = field.Key,
Label = field.FieldLabel,
Description = field.Description,
Required = true,
Type = "text"
}).ToList();
}
// Process optional metadata
if (workflow.OptionalMetadata != null)
{
config.OptionalFields = workflow.OptionalMetadata.Select(field => new FormField
{
Key = field.Key,
Label = field.FieldLabel,
Description = field.Description,
Required = false,
Type = "text"
}).ToList();
}
return config;
}
public string GenerateHtmlForm(FormConfiguration config)
{
var html = new StringBuilder();
html.AppendLine($@"<form class=""workflow-form"" data-workflow-id=""{config.WorkflowId}"">");
html.AppendLine($@" <div class=""workflow-info"">");
html.AppendLine($@" <h3>{config.WorkflowName}</h3>");
html.AppendLine($@" <p>{config.WorkflowDescription}</p>");
html.AppendLine($@" </div>");
// Add required fields
if (config.RequiredFields.Any())
{
html.AppendLine($@" <div class=""required-fields"">");
html.AppendLine($@" <h4>Required Information</h4>");
foreach (var field in config.RequiredFields)
{
html.AppendLine($@" <div class=""form-field required"">");
html.AppendLine($@" <label for=""{field.Key}"" class=""required"">");
html.AppendLine($@" {field.Label} *");
html.AppendLine($@" </label>");
html.AppendLine($@" <input type=""{field.Type}"" id=""{field.Key}"" name=""{field.Key}"" required />");
html.AppendLine($@" <small class=""field-description"">{field.Description}</small>");
html.AppendLine($@" </div>");
}
html.AppendLine($@" </div>");
}
// Add optional fields
if (config.OptionalFields.Any())
{
html.AppendLine($@" <div class=""optional-fields"">");
html.AppendLine($@" <h4>Optional Information</h4>");
foreach (var field in config.OptionalFields)
{
html.AppendLine($@" <div class=""form-field optional"">");
html.AppendLine($@" <label for=""{field.Key}"">");
html.AppendLine($@" {field.Label}");
html.AppendLine($@" </label>");
html.AppendLine($@" <input type=""{field.Type}"" id=""{field.Key}"" name=""{field.Key}"" />");
html.AppendLine($@" <small class=""field-description"">{field.Description}</small>");
html.AppendLine($@" </div>");
}
html.AppendLine($@" </div>");
}
html.AppendLine($@" <button type=""submit"" class=""submit-button"">");
html.AppendLine($@" Execute {config.WorkflowName}");
html.AppendLine($@" </button>");
html.AppendLine($@"</form>");
return html.ToString();
}
}
// Usage example
public class Program
{
public static async Task GenerateFormsExample()
{
using var client = new WorkflowsClient(Environment.GetEnvironmentVariable("STORYTELLER_API_KEY"));
var formBuilder = new MetadataFormBuilder();
try
{
var response = await client.GetWorkflowsAsync();
foreach (var workflow in response.Workflows)
{
var formConfig = formBuilder.BuildFormConfiguration(workflow);
var htmlForm = formBuilder.GenerateHtmlForm(formConfig);
Console.WriteLine($"Form HTML for {workflow.Name}:");
Console.WriteLine(htmlForm);
Console.WriteLine(new string('-', 50));
}
}
catch (Exception ex)
{
Console.WriteLine($"Error generating forms: {ex.Message}");
}
}
}
Response Status Codes#
| Status Code | Description |
|---|---|
200 OK |
Successfully retrieved workflows |
401 Unauthorized |
Invalid or missing API key |
403 Forbidden |
API key doesn't have access to workflows |
500 Internal Server Error |
Server error occurred |
Error Handling#
Common Errors#
{
"error": "Unauthorized",
"message": "Invalid API key",
"statusCode": 401
}
Best Practices#
- Always check the response status before processing
- Implement retry logic for temporary failures
- Cache workflow results to reduce API calls
- Validate your API key if you get 401 errors
Next Steps#
Once you have your available workflows:
- Choose the appropriate workflow for your content
- Gather the required metadata
- Execute the workflow using Executing Workflows
Related Documentation#
- Workflows Overview - Learn about workflow concepts
- Executing Workflows - Run workflows with your content
- Authentication - Set up API key authentication