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#
- Always Validate First - Use validation before executing workflows in production
- Cache Validation Results - For identical payloads, validation results can be cached briefly
- Handle All Error Types - Check workflows, media URLs, metadata, and webhooks
- Use in CI/CD - Integrate validation into your deployment testing pipeline
- Log Validation Errors - Keep detailed logs of validation failures for debugging
- 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 |
Related Documentation#
- Executing Workflows - The actual workflow execution endpoint
- Getting Workflows - Get valid workflow IDs
- Authentication - API key setup and usage
- Troubleshooting - Common issues and solutions