Skip to content

Executing Workflows#

The POST /api/workflows/execute endpoint is the core of the Integration API. It allows you to send media content to Storyteller for processing through specified workflows.

Endpoint Details#

POST https://integrations.usestoryteller.com/api/workflows/execute

Headers#

Header Required Description
x-storyteller-api-key Yes Your API key for authentication
Content-Type Yes application/json

Request Body#

{
  "workflows": ["workflow-id"],
  "mediaUrls": ["https://example.com/video.mp4"],
  "metadata": {
    "https://example.com/video.mp4": {
      "Title": "My Amazing Video",
      "Description": "A great piece of content"
    }
  },
  "workflowProcessingWebhookUrl": "https://your-domain.com/webhooks/processing",
  "workflowProgressWebhookUrl": "https://your-domain.com/webhooks/progress",
  "workflowProcessedWebhookUrl": "https://your-domain.com/webhooks/processed",
  "workflowFailedWebhookUrl": "https://your-domain.com/webhooks/failed",
  "operationId": "optional-update-operation-id"
}

Request Parameters#

Field Type Required Description
workflows array Yes Array of workflow IDs to execute
mediaUrls array Yes Array of publicly accessible media URLs
metadata object Yes Key-value pairs of metadata keyed by media URL
workflowProcessingWebhookUrl string No Called when workflow begins processing
workflowProgressWebhookUrl string No Called during workflow progress updates
workflowProcessedWebhookUrl string No Called when workflow completes successfully
workflowFailedWebhookUrl string No Called if workflow fails
operationId string No For updating previous workflow results

Response#

Success Response (202 Accepted)#

{
  "correlationId": "12345678-1234-1234-1234-123456789012",
  "message": "Workflow execution started",
  "status": "processing"
}

Response Fields#

Field Type Description
correlationId string Unique ID for tracking this workflow execution
message string Human-readable status message
status string Current status of the workflow execution

Code Examples#

curl -X POST "https://integrations.usestoryteller.com/api/workflows/execute" \
  -H "x-storyteller-api-key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "workflows": ["add-media"],
    "mediaUrls": ["https://cdn.coverr.co/videos/coverr-overhead-view-of-the-highway-8903/1080p.mp4"],
    "metadata": {
      "https://cdn.coverr.co/videos/coverr-overhead-view-of-the-highway-8903/1080p.mp4": {
        "Title": "Highway Overhead View"
      }
    }
  }'
const fetch = require('node-fetch');

async function executeWorkflow(workflows, mediaUrls, metadata, webhooks = {}) {
  const requestBody = {
    workflows,
    mediaUrls,
    metadata,
    ...webhooks
  };

  try {
    const response = await fetch('https://integrations.usestoryteller.com/api/workflows/execute', {
      method: 'POST',
      headers: {
        'x-storyteller-api-key': process.env.STORYTELLER_API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(requestBody)
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`HTTP ${response.status}: ${errorData.message}`);
    }

    const data = await response.json();
    return data.correlationId;
  } catch (error) {
    console.error('Error executing workflow:', error);
    throw error;
  }
}

// Usage
const workflows = ['add-media'];
const mediaUrls = ['https://example.com/video.mp4'];
const metadata = {
  'https://example.com/video.mp4': {
    'Title': 'My Video Title',
    'Description': 'Optional description'
  }
};

const webhooks = {
  workflowProcessedWebhookUrl: 'https://myapp.com/webhooks/processed',
  workflowFailedWebhookUrl: 'https://myapp.com/webhooks/failed'
};

executeWorkflow(workflows, mediaUrls, metadata, webhooks)
  .then(correlationId => {
    console.log('Workflow started with correlation ID:', correlationId);
    // Store correlationId for status tracking
  })
  .catch(error => console.error('Failed to execute workflow:', error));
import requests
import json
import os

def execute_workflow(workflows, media_urls, metadata, webhooks=None):
    url = 'https://integrations.usestoryteller.com/api/workflows/execute'
    headers = {
        'x-storyteller-api-key': os.environ.get('STORYTELLER_API_KEY'),
        'Content-Type': 'application/json'
    }

    request_body = {
        'workflows': workflows,
        'mediaUrls': media_urls,
        'metadata': metadata
    }

    if webhooks:
        request_body.update(webhooks)

    try:
        response = requests.post(url, headers=headers, json=request_body)
        response.raise_for_status()

        data = response.json()
        return data['correlationId']
    except requests.exceptions.RequestException as e:
        print(f'Error executing workflow: {e}')
        if hasattr(e.response, 'json'):
            print(f'Error details: {e.response.json()}')
        raise

# Usage
workflows = ['add-media']
media_urls = ['https://example.com/video.mp4']
metadata = {
    'https://example.com/video.mp4': {
        'Title': 'My Video Title',
        'Description': 'Optional description'
    }
}

webhooks = {
    'workflowProcessedWebhookUrl': 'https://myapp.com/webhooks/processed',
    'workflowFailedWebhookUrl': 'https://myapp.com/webhooks/failed'
}

try:
    correlation_id = execute_workflow(workflows, media_urls, metadata, webhooks)
    print(f'Workflow started with correlation ID: {correlation_id}')
    # Store correlation_id for status tracking
except Exception as e:
    print(f'Failed to execute workflow: {e}')
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

public class WorkflowExecutor
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl = "https://integrations.usestoryteller.com";

    public WorkflowExecutor(string apiKey)
    {
        _httpClient = new HttpClient();
        _httpClient.DefaultRequestHeaders.Add("x-storyteller-api-key", apiKey);
    }

    public async Task<string> ExecuteWorkflowAsync(WorkflowExecutionRequest request)
    {
        try
        {
            var json = JsonConvert.SerializeObject(request);
            var content = new StringContent(json, Encoding.UTF8, "application/json");

            var response = await _httpClient.PostAsync($"{_baseUrl}/api/workflows/execute", content);
            response.EnsureSuccessStatusCode();

            var responseContent = await response.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<WorkflowExecutionResponse>(responseContent);

            return result.CorrelationId;
        }
        catch (HttpRequestException ex)
        {
            throw new Exception($"Error executing workflow: {ex.Message}", ex);
        }
    }
}

public class WorkflowExecutionRequest
{
    public string[] Workflows { get; set; }
    public string[] MediaUrls { get; set; }
    public Dictionary<string, Dictionary<string, string>> Metadata { get; set; }
    public string WorkflowProcessingWebhookUrl { get; set; }
    public string WorkflowProgressWebhookUrl { get; set; }
    public string WorkflowProcessedWebhookUrl { get; set; }
    public string WorkflowFailedWebhookUrl { get; set; }
    public string OperationId { get; set; }
}

public class WorkflowExecutionResponse
{
    public string CorrelationId { get; set; }
    public string Message { get; set; }
    public string Status { get; set; }
}

// Usage
var executor = new WorkflowExecutor(Environment.GetEnvironmentVariable("STORYTELLER_API_KEY"));

var request = new WorkflowExecutionRequest
{
    Workflows = new[] { "add-media" },
    MediaUrls = new[] { "https://example.com/video.mp4" },
    Metadata = new Dictionary<string, Dictionary<string, string>>
    {
        ["https://example.com/video.mp4"] = new Dictionary<string, string>
        {
            ["Title"] = "My Video Title",
            ["Description"] = "Optional description"
        }
    },
    WorkflowProcessedWebhookUrl = "https://myapp.com/webhooks/processed",
    WorkflowFailedWebhookUrl = "https://myapp.com/webhooks/failed"
};

try
{
    var correlationId = await executor.ExecuteWorkflowAsync(request);
    Console.WriteLine($"Workflow started with correlation ID: {correlationId}");
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

Detailed Parameter Descriptions#

Workflows Array#

  • Must contain valid workflow IDs from the GET /api/workflows endpoint
  • Can specify multiple workflows to run in sequence
  • Each workflow must be available for your account

Media URLs Array#

  • URLs must be publicly accessible over the internet
  • Supported formats: MP4, MOV, AVI (video), JPG, PNG (images)
  • URLs should return proper HTTP status codes and content-type headers
  • Maximum file size varies by account tier

Metadata Object#

  • Keyed by media URL (must match exactly)
  • Contains key-value pairs specific to each workflow
  • Must include all required metadata fields for specified workflows
  • Optional metadata will use defaults if not provided

Webhook URLs#

All webhook URLs are optional but highly recommended for production use:

  • workflowProcessingWebhookUrl - Called when execution starts
  • workflowProgressWebhookUrl - Called during processing milestones
  • workflowProcessedWebhookUrl - Called on successful completion
  • workflowFailedWebhookUrl - Called if any workflow fails

See Webhooks for detailed webhook implementation information.

Operation ID#

  • Optional parameter for updating previous workflow results
  • Use the correlationId from a previous execution
  • Allows overwriting or updating existing content in Storyteller

Response Status Codes#

Status Code Description
202 Accepted Workflow execution started successfully
400 Bad Request Invalid request format or missing required fields
401 Unauthorized Invalid or missing API key
403 Forbidden API key doesn't have access to specified workflows
422 Unprocessable Entity Invalid workflow IDs or unreachable media URLs
500 Internal Server Error Server error occurred

Error Responses#

Missing Required Metadata#

{
  "error": "Validation Error",
  "message": "Required metadata 'Title' missing for workflow 'add-media'",
  "statusCode": 400,
  "details": {
    "workflow": "add-media",
    "missingFields": ["Title"]
  }
}

Invalid Media URL#

{
  "error": "Media Error",
  "message": "Unable to access media URL: https://example.com/invalid.mp4",
  "statusCode": 422,
  "details": {
    "url": "https://example.com/invalid.mp4",
    "reason": "HTTP 404 Not Found"
  }
}

Invalid Workflow#

{
  "error": "Workflow Error",
  "message": "Workflow 'invalid-workflow' not found or not available",
  "statusCode": 422,
  "details": {
    "workflow": "invalid-workflow"
  }
}

Best Practices#

Media URL Validation#

async function validateMediaUrl(url) {
  try {
    const response = await fetch(url, { method: 'HEAD' });
    return response.ok;
  } catch (error) {
    return false;
  }
}

// Validate before sending to Storyteller
const validUrls = [];
for (const url of mediaUrls) {
  if (await validateMediaUrl(url)) {
    validUrls.push(url);
  } else {
    console.warn(`Invalid media URL: ${url}`);
  }
}
import requests

def validate_media_url(url):
    try:
        response = requests.head(url)
        return response.ok
    except requests.exceptions.RequestException:
        return False

# Validate before sending to Storyteller
valid_urls = []
for url in media_urls:
    if validate_media_url(url):
        valid_urls.append(url)
    else:
        print(f"Invalid media URL: {url}")
public async Task<bool> ValidateMediaUrlAsync(string url)
{
    try
    {
        using var client = new HttpClient();
        var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, url));
        return response.IsSuccessStatusCode;
    }
    catch (HttpRequestException)
    {
        return false;
    }
}

// Validate before sending to Storyteller
var validUrls = new List<string>();
foreach (var url in mediaUrls)
{
    if (await ValidateMediaUrlAsync(url))
    {
        validUrls.Add(url);
    }
    else
    {
        Console.WriteLine($"Invalid media URL: {url}");
    }
}
# Test webhook endpoint before using
curl -X POST "https://your-domain.com/webhooks/test" \
  -H "Content-Type: application/json" \
  -d '{"test": true, "timestamp": "2025-01-01T00:00:00Z"}'

Metadata Validation#

function validateMetadata(workflows, metadata) {
  const errors = [];

  workflows.forEach(workflow => {
    if (workflow.requiredMetadata) {
      workflow.requiredMetadata.forEach(field => {
        Object.keys(metadata).forEach(mediaUrl => {
          if (!metadata[mediaUrl][field.key]) {
            errors.push(`Missing required field '${field.key}' for ${mediaUrl}`);
          }
        });
      });
    }
  });

  return errors;
}
def validate_metadata(workflows, metadata):
    errors = []

    for workflow in workflows:
        if 'requiredMetadata' in workflow:
            for field in workflow['requiredMetadata']:
                for media_url in metadata:
                    if field['key'] not in metadata[media_url] or not metadata[media_url][field['key']]:
                        errors.append(f"Missing required field '{field['key']}' for {media_url}")

    return errors
public List<string> ValidateMetadata(WorkflowDefinition[] workflows, Dictionary<string, Dictionary<string, string>> metadata)
{
    var errors = new List<string>();

    foreach (var workflow in workflows)
    {
        if (workflow.RequiredMetadata != null)
        {
            foreach (var field in workflow.RequiredMetadata)
            {
                foreach (var mediaUrl in metadata.Keys)
                {
                    if (!metadata[mediaUrl].ContainsKey(field.Key) ||
                        string.IsNullOrEmpty(metadata[mediaUrl][field.Key]))
                    {
                        errors.Add($"Missing required field '{field.Key}' for {mediaUrl}");
                    }
                }
            }
        }
    }

    return errors;
}

Correlation ID Storage#

// Store correlation ID for tracking
const correlationIds = new Map();

async function executeAndTrack(workflows, mediaUrls, metadata) {
  const correlationId = await executeWorkflow(workflows, mediaUrls, metadata);

  // Store with timestamp for tracking
  correlationIds.set(correlationId, {
    started: new Date(),
    workflows,
    mediaUrls,
    status: 'processing'
  });

  return correlationId;
}
# Store correlation ID for tracking
correlation_ids = {}

def execute_and_track(workflows, media_urls, metadata):
    correlation_id = execute_workflow(workflows, media_urls, metadata)

    # Store with timestamp for tracking
    correlation_ids[correlation_id] = {
        'started': datetime.now(),
        'workflows': workflows,
        'media_urls': media_urls,
        'status': 'processing'
    }

    return correlation_id
// Store correlation ID for tracking
private readonly Dictionary<string, WorkflowTracking> _correlationIds = new();

public class WorkflowTracking
{
    public DateTime Started { get; set; }
    public string[] Workflows { get; set; }
    public string[] MediaUrls { get; set; }
    public string Status { get; set; }
}

public async Task<string> ExecuteAndTrackAsync(string[] workflows, string[] mediaUrls, Dictionary<string, Dictionary<string, string>> metadata)
{
    var correlationId = await ExecuteWorkflowAsync(workflows, mediaUrls, metadata);

    // Store with timestamp for tracking
    _correlationIds[correlationId] = new WorkflowTracking
    {
        Started = DateTime.Now,
        Workflows = workflows,
        MediaUrls = mediaUrls,
        Status = "processing"
    };

    return correlationId;
}

Complete Implementation Example#

For a complete working example that demonstrates workflow execution with metadata handling and status monitoring, see the Storyteller Sample Integration repository.

This Next.js application demonstrates:

  • Workflow Execution: Complete implementation of the POST /api/workflows/execute endpoint
  • Metadata Handling: Dynamic form generation and metadata collection
  • Status Monitoring: Real-time workflow status tracking with polling
  • Error Handling: Comprehensive error handling and user feedback
  • Template Integration: Working with workflow templates and their properties

Key files to examine:

  • src/api/requests.ts - Complete API integration including executeWorkflows() and listenForWorkflowStatus()
  • src/components/PublishToStoryteller/ - Form component showing metadata collection and workflow execution

Next Steps#

After executing a workflow: 1. Store the returned correlationId for tracking 2. Set up webhook handlers or poll the Workflow Status endpoint 3. Handle success/failure scenarios appropriately 4. Monitor for completion before starting dependent operations