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
correlationIdfrom 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/executeendpoint - 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 includingexecuteWorkflows()andlistenForWorkflowStatus()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
Related Documentation#
- Getting Workflows - Get available workflows first
- Workflow Status - Track execution progress
- Webhooks - Set up real-time notifications
- Troubleshooting - Handle common issues