Skip to content

Payload Validation API#

The Payload Validation endpoint allows you to validate workflow execution payloads before actually executing them. This is useful for troubleshooting during integration development and ensuring your requests will succeed.

Endpoint Details#

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

Headers#

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

Request Body#

The request body uses the same structure as the Execute Workflow endpoint:

{
  "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": "Best title available for this asset"
    }
  },
  "workflowProcessingWebhookUrl": "",
  "workflowProgressWebhookUrl": "",
  "workflowProcessedWebhookUrl": "",
  "workflowFailedWebhookUrl": ""
}

Request Parameters#

All parameters are identical to the Execute Workflow endpoint:

Field Type Required Description
workflows array Yes Array of workflow IDs to validate
mediaUrls array Yes Array of publicly accessible media URLs
metadata object Yes Key-value pairs of metadata keyed by media URL
workflowProcessingWebhookUrl string No Processing webhook URL (validated but not called)
workflowProgressWebhookUrl string No Progress webhook URL (validated but not called)
workflowProcessedWebhookUrl string No Success webhook URL (validated but not called)
workflowFailedWebhookUrl string No Failure webhook URL (validated but not called)
operationId string No Optional operation ID for updates

Response#

Success Response (200 OK) - Valid Payload#

{
  "isValid": true,
  "message": "Payload is valid and ready for execution",
  "validationResults": {
    "workflows": {
      "isValid": true,
      "validWorkflows": ["add-media"],
      "invalidWorkflows": []
    },
    "mediaUrls": {
      "isValid": true,
      "accessibleUrls": ["https://cdn.coverr.co/videos/coverr-overhead-view-of-the-highway-8903/1080p.mp4"],
      "inaccessibleUrls": []
    },
    "metadata": {
      "isValid": true,
      "validMetadata": {
        "https://cdn.coverr.co/videos/coverr-overhead-view-of-the-highway-8903/1080p.mp4": {
          "Title": "Best title available for this asset"
        }
      },
      "missingRequiredFields": {},
      "invalidFields": {}
    },
    "webhooks": {
      "isValid": true,
      "validWebhooks": [],
      "invalidWebhooks": []
    }
  }
}

Error Response (400 Bad Request) - Invalid Payload#

{
  "isValid": false,
  "message": "Payload validation failed",
  "validationResults": {
    "workflows": {
      "isValid": false,
      "validWorkflows": [],
      "invalidWorkflows": ["invalid-workflow-id"],
      "errors": [
        "Workflow 'invalid-workflow-id' not found or not available"
      ]
    },
    "mediaUrls": {
      "isValid": false,
      "accessibleUrls": [],
      "inaccessibleUrls": ["https://example.com/nonexistent.mp4"],
      "errors": [
        "Media URL 'https://example.com/nonexistent.mp4' is not accessible (HTTP 404)"
      ]
    },
    "metadata": {
      "isValid": false,
      "validMetadata": {},
      "missingRequiredFields": {
        "https://example.com/nonexistent.mp4": ["Description"]
      },
      "invalidFields": {
        "https://example.com/nonexistent.mp4": {
          "InvalidField": "This field is not supported by the workflow"
        }
      },
      "errors": [
        "Required field 'Description' missing for workflow 'add-media'",
        "Invalid field 'InvalidField' for workflow 'add-media'"
      ]
    },
    "webhooks": {
      "isValid": true,
      "validWebhooks": [],
      "invalidWebhooks": []
    }
  }
}

Response Fields#

Main Response#

Field Type Description
isValid boolean Overall validation result
message string Human-readable validation summary
validationResults object Detailed validation results by category

Validation Results Structure#

Field Type Description
workflows object Workflow validation results
mediaUrls object Media URL accessibility results
metadata object Metadata validation results
webhooks object Webhook URL validation results

Each validation category includes: - isValid - Boolean indicating if this category passed validation - Specific arrays/objects with valid and invalid items - errors array with detailed error messages

Code Examples#

```bash

curl -X POST "https://integrations.usestoryteller.com/api/workflows/validate" \ -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": "Test Video" } } }'

=== "JavaScript"
    ```javascript
const fetch = require('node-fetch');

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

    const data = await response.json();

    if (response.ok) {
      return {
        success: true,
        isValid: data.isValid,
        results: data.validationResults,
        message: data.message
      };
    } else {
      return {
        success: false,
        isValid: false,
        results: data.validationResults,
        message: data.message,
        errors: data.validationResults ? extractErrors(data.validationResults) : []
      };
    }
  } catch (error) {
    console.error('Error validating payload:', error);
    throw error;
  }
}

function extractErrors(validationResults) {
  const allErrors = [];

  Object.values(validationResults).forEach(category => {
    if (category.errors && Array.isArray(category.errors)) {
      allErrors.push(...category.errors);
    }
  });

  return allErrors;
}

// Usage examples
async function examples() {
  // Valid payload example
  const validPayload = {
    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': 'Test Video'
      }
    }
  };

  const validationResult = await validateWorkflowPayload(validPayload);

  if (validationResult.isValid) {
    console.log('✅ Payload is valid! Ready to execute.');
    // Now you can safely call executeWorkflow() with this payload
  } else {
    console.log('❌ Payload validation failed:');
    validationResult.errors.forEach(error => console.log(`  - ${error}`));
  }
}

```python

import requests import os

def validate_workflow_payload(payload): url = 'https://integrations.usestoryteller.com/api/workflows/validate' headers = { 'x-storyteller-api-key': os.environ.get('STORYTELLER_API_KEY'), 'Content-Type': 'application/json' }

try:
    response = requests.post(url, headers=headers, json=payload)
    data = response.json()

    return {
        'success': response.ok,
        'is_valid': data.get('isValid', False),
        'results': data.get('validationResults', {}),
        'message': data.get('message', ''),
        'errors': extract_errors(data.get('validationResults', {})) if not response.ok else []
    }
except requests.exceptions.RequestException as e:
    print(f'Error validating payload: {e}')
    if hasattr(e.response, 'json'):
        print(f'Error details: {e.response.json()}')
    raise

def extract_errors(validation_results): all_errors = []

for category in validation_results.values():
    if isinstance(category, dict) and 'errors' in category:
        all_errors.extend(category['errors'])

return all_errors

Usage examples#

try: # Valid payload example valid_payload = { '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': 'Test Video' } } }

validation_result = validate_workflow_payload(valid_payload)

if validation_result['is_valid']:
    print('✅ Payload is valid! Ready to execute.')
    # Now you can safely call execute_workflow() with this payload
else:
    print('❌ Payload validation failed:')
    for error in validation_result['errors']:
        print(f'  - {error}')

except Exception as e: print(f'Failed to validate payload: {e}')

=== "C#"
    ```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;

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

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

    public async Task<ValidationResult> ValidateWorkflowPayloadAsync(WorkflowPayload payload)
    {
        try
        {
            var json = JsonConvert.SerializeObject(payload);
            var content = new StringContent(json, Encoding.UTF8, "application/json");

            var response = await _httpClient.PostAsync($"{_baseUrl}/api/workflows/validate", content);
            var responseContent = await response.Content.ReadAsStringAsync();
            var data = JsonConvert.DeserializeObject<ValidationResponse>(responseContent);

            return new ValidationResult
            {
                Success = response.IsSuccessStatusCode,
                IsValid = data?.IsValid ?? false,
                Message = data?.Message ?? "",
                Results = data?.ValidationResults,
                Errors = data?.ValidationResults != null ? ExtractErrors(data.ValidationResults) : new List<string>()
            };
        }
        catch (HttpRequestException ex)
        {
            throw new Exception($"Error validating payload: {ex.Message}", ex);
        }
    }

    private List<string> ExtractErrors(ValidationResults results)
    {
        var errors = new List<string>();

        if (results.Workflows?.Errors != null)
            errors.AddRange(results.Workflows.Errors);
        if (results.MediaUrls?.Errors != null)
            errors.AddRange(results.MediaUrls.Errors);
        if (results.Metadata?.Errors != null)
            errors.AddRange(results.Metadata.Errors);
        if (results.Webhooks?.Errors != null)
            errors.AddRange(results.Webhooks.Errors);

        return errors;
    }
}

public class ValidationResult
{
    public bool Success { get; set; }
    public bool IsValid { get; set; }
    public string Message { get; set; }
    public ValidationResults Results { get; set; }
    public List<string> Errors { get; set; }
}

public class ValidationResponse
{
    public bool IsValid { get; set; }
    public string Message { get; set; }
    public ValidationResults ValidationResults { get; set; }
}

public class ValidationResults
{
    public WorkflowValidation Workflows { get; set; }
    public MediaUrlValidation MediaUrls { get; set; }
    public MetadataValidation Metadata { get; set; }
    public WebhookValidation Webhooks { get; set; }
}

public class WorkflowValidation
{
    public bool IsValid { get; set; }
    public string[] ValidWorkflows { get; set; }
    public string[] InvalidWorkflows { get; set; }
    public string[] Errors { get; set; }
}

public class MediaUrlValidation
{
    public bool IsValid { get; set; }
    public string[] AccessibleUrls { get; set; }
    public string[] InaccessibleUrls { get; set; }
    public string[] Errors { get; set; }
}

public class MetadataValidation
{
    public bool IsValid { get; set; }
    public Dictionary<string, Dictionary<string, string>> ValidMetadata { get; set; }
    public Dictionary<string, string[]> MissingRequiredFields { get; set; }
    public Dictionary<string, Dictionary<string, string>> InvalidFields { get; set; }
    public string[] Errors { get; set; }
}

public class WebhookValidation
{
    public bool IsValid { get; set; }
    public string[] ValidWebhooks { get; set; }
    public string[] InvalidWebhooks { get; set; }
    public string[] Errors { get; set; }
}

// Usage
var validator = new PayloadValidator(Environment.GetEnvironmentVariable("STORYTELLER_API_KEY"));

var payload = new WorkflowPayload
{
    Workflows = new[] { "add-media" },
    MediaUrls = new[] { "https://cdn.coverr.co/videos/coverr-overhead-view-of-the-highway-8903/1080p.mp4" },
    Metadata = new Dictionary<string, Dictionary<string, string>>
    {
        ["https://cdn.coverr.co/videos/coverr-overhead-view-of-the-highway-8903/1080p.mp4"] = new Dictionary<string, string>
        {
            ["Title"] = "Test Video"
        }
    }
};

try
{
    var validationResult = await validator.ValidateWorkflowPayloadAsync(payload);

    if (validationResult.IsValid)
    {
        Console.WriteLine("✅ Payload is valid! Ready to execute.");
        // Now you can safely call ExecuteWorkflowAsync() with this payload
    }
    else
    {
        Console.WriteLine("❌ Payload validation failed:");
        foreach (var error in validationResult.Errors)
        {
            Console.WriteLine($"  - {error}");
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

Advanced Usage Patterns#

Pre-execution Validation Wrapper#

async function safeExecuteWorkflow(workflows, mediaUrls, metadata, webhooks = {}) {
  // First validate the payload
  const payload = {
    workflows,
    mediaUrls,
    metadata,
    ...webhooks
  };

  const validation = await validateWorkflowPayload(payload);

  if (!validation.isValid) {
    throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
  }

  console.log('✅ Payload validated successfully, executing workflow...');

  // If validation passes, execute the workflow
  return await executeWorkflow(workflows, mediaUrls, metadata, webhooks);
}

// Usage
try {
  const correlationId = await safeExecuteWorkflow(
    ['add-media'],
    ['https://example.com/video.mp4'],
    { 'https://example.com/video.mp4': { 'Title': 'My Video' } }
  );
  console.log('Workflow started:', correlationId);
} catch (error) {
  console.error('Workflow failed:', error.message);
}

Batch Validation for Multiple Payloads#

async function validateMultiplePayloads(payloads) {
  const results = await Promise.all(
    payloads.map(async (payload, index) => {
      try {
        const validation = await validateWorkflowPayload(payload);
        return {
          index,
          payload,
          isValid: validation.isValid,
          errors: validation.errors || []
        };
      } catch (error) {
        return {
          index,
          payload,
          isValid: false,
          errors: [error.message]
        };
      }
    })
  );

  const valid = results.filter(r => r.isValid);
  const invalid = results.filter(r => !r.isValid);

  return {
    totalCount: payloads.length,
    validCount: valid.length,
    invalidCount: invalid.length,
    validPayloads: valid,
    invalidPayloads: invalid
  };
}

Development Integration Testing#

class WorkflowTester {
  constructor(apiKey) {
    this.apiKey = apiKey;
  }

  async testWorkflowIntegration(testCases) {
    const results = [];

    for (const testCase of testCases) {
      console.log(`Testing: ${testCase.name}`);

      try {
        const validation = await validateWorkflowPayload(testCase.payload);

        results.push({
          name: testCase.name,
          expected: testCase.expectedValid,
          actual: validation.isValid,
          passed: testCase.expectedValid === validation.isValid,
          errors: validation.errors || [],
          details: validation.results
        });

        if (testCase.expectedValid === validation.isValid) {
          console.log(`  ✅ PASS`);
        } else {
          console.log(`  ❌ FAIL - Expected ${testCase.expectedValid}, got ${validation.isValid}`);
        }
      } catch (error) {
        results.push({
          name: testCase.name,
          expected: testCase.expectedValid,
          actual: false,
          passed: false,
          errors: [error.message],
          details: null
        });
        console.log(`  ❌ ERROR - ${error.message}`);
      }
    }

    return results;
  }
}

// Usage
const tester = new WorkflowTester(process.env.STORYTELLER_API_KEY);

const testCases = [
  {
    name: "Valid basic payload",
    expectedValid: true,
    payload: {
      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': 'Test Video'
        }
      }
    }
  },
  {
    name: "Invalid workflow ID",
    expectedValid: false,
    payload: {
      workflows: ['nonexistent-workflow'],
      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': 'Test Video'
        }
      }
    }
  }
];

const testResults = await tester.testWorkflowIntegration(testCases);
console.log(`\nTest Summary: ${testResults.filter(r => r.passed).length}/${testResults.length} passed`);

Common Validation Scenarios#

Missing Required Metadata#

{
  "isValid": false,
  "validationResults": {
    "metadata": {
      "isValid": false,
      "missingRequiredFields": {
        "https://example.com/video.mp4": ["Description", "Category"]
      },
      "errors": [
        "Required field 'Description' missing for workflow 'add-story-page'",
        "Required field 'Category' missing for workflow 'add-story-page'"
      ]
    }
  }
}

Inaccessible Media URLs#

{
  "isValid": false,
  "validationResults": {
    "mediaUrls": {
      "isValid": false,
      "inaccessibleUrls": ["https://broken-link.com/video.mp4"],
      "errors": [
        "Media URL 'https://broken-link.com/video.mp4' is not accessible (HTTP 404)"
      ]
    }
  }
}

Invalid Webhook URLs#

{
  "isValid": false,
  "validationResults": {
    "webhooks": {
      "isValid": false,
      "invalidWebhooks": ["not-a-valid-url"],
      "errors": [
        "Invalid webhook URL format: 'not-a-valid-url'"
      ]
    }
  }
}

Best Practices#

  1. Always Validate First - Use validation before executing workflows in production
  2. Cache Validation Results - For identical payloads, validation results can be cached briefly
  3. Handle All Error Types - Check workflows, media URLs, metadata, and webhooks
  4. Use in CI/CD - Integrate validation into your deployment testing pipeline
  5. Log Validation Errors - Keep detailed logs of validation failures for debugging
  6. Test Edge Cases - Validate error scenarios during development

Error Response Codes#

Status Code Description
200 OK Validation completed (check isValid field for actual result)
400 Bad Request Malformed request body or invalid JSON
401 Unauthorized Invalid or missing API key
500 Internal Server Error Server error during validation