Documentation

Veedeo API V3 Examples

Code Examples and Use Cases Last Updated: September 17, 2025

Table of Contents

  1. JavaScript/Node.js Examples
  2. Python Examples
  3. PHP Examples
  4. cURL Examples
  5. Use Case Examples
  6. Error Handling
  7. Testing Examples

JavaScript/Node.js Examples

Basic Client Implementation

class VeedeoClient {
  constructor(apiKey, baseUrl = 'https://api.veedeo.dev/v3') {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
    this.version = '2025-01-17';
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const config = {
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
        'X-Veedeo-Version': this.version,
        ...options.headers
      },
      ...options
    };

    const response = await fetch(url, config);

    if (!response.ok) {
      const error = await response.json();
      throw new VeedeoError(error.error.message, error.error.code, response.status);
    }

    return response.json();
  }

  async createRender(renderRequest) {
    return this.request('/tasks', {
      method: 'POST',
      body: JSON.stringify(renderRequest)
    });
  }

  async getTask(taskId) {
    return this.request(`/tasks/${taskId}`);
  }

  async listTasks(options = {}) {
    const params = new URLSearchParams(options);
    return this.request(`/tasks?${params}`);
  }

  async cancelTask(taskId) {
    return this.request(`/tasks/${taskId}`, { method: 'DELETE' });
  }

  async retryTask(taskId) {
    return this.request(`/tasks/${taskId}/retry`, { method: 'POST' });
  }
}

class VeedeoError extends Error {
  constructor(message, code, statusCode) {
    super(message);
    this.name = 'VeedeoError';
    this.code = code;
    this.statusCode = statusCode;
  }
}

Simple Image Slideshow

async function createImageSlideshow() {
  const client = new VeedeoClient('your_api_key');

  const renderRequest = {
    version: '3.0',
    request_id: 'slideshow_1735776000000',
    input: {
      timeline: {
        duration_ms: 15000,
        tracks: [
          {
            id: 'video_track_1',
            type: 'video',
            clips: [
              {
                id: 'clip_1',
                media_url: 'https://example.com/image1.jpg',
                start_time_ms: 0,
                end_time_ms: 5000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              },
              {
                id: 'clip_2',
                media_url: 'https://example.com/image2.jpg',
                start_time_ms: 5000,
                end_time_ms: 10000,
                properties: {
                  scale: { x: 1.2, y: 1.2 },
                  opacity: 1.0,
                  position: { x: -50, y: -50 }
                }
              },
              {
                id: 'clip_3',
                media_url: 'https://example.com/image3.jpg',
                start_time_ms: 10000,
                end_time_ms: 15000,
                properties: {
                  scale: { x: 0.8, y: 0.8 },
                  opacity: 0.9,
                  position: { x: 100, y: 0 }
                }
              }
            ]
          }
        ]
      },
      output: {
        resolution: { width: 1920, height: 1080 },
        framerate: 30,
        format: 'mp4',
        quality: 'high'
      }
    },
    webhook: {
      url: 'https://your-app.com/webhook',
      events: ['task.completed', 'task.failed']
    },
    metadata: {
      tags: ['slideshow', 'marketing'],
      priority: 'standard'
    }
  };

  try {
    const result = await client.createRender(renderRequest);
    console.log(`Render task created: ${result.task_id}`);
    return result;
  } catch (error) {
    console.error('Failed to create render:', error.message);
    throw error;
  }
}

Multi-Track Video with Audio and Subtitles

async function createFullVideo() {
  const client = new VeedeoClient('your_api_key');

  const renderRequest = {
    version: '3.0',
    request_id: 'full_video_1735776000000',
    input: {
      timeline: {
        duration_ms: 30000,
        tracks: [
          // Video Track
          {
            id: 'video_track_1',
            type: 'video',
            clips: [
              {
                id: 'intro_clip',
                media_url: 'https://storage.example.com/intro.jpg',
                start_time_ms: 0,
                end_time_ms: 5000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              },
              {
                id: 'main_clip',
                media_url: 'https://storage.example.com/main-content.jpg',
                start_time_ms: 5000,
                end_time_ms: 25000,
                properties: {
                  scale: { x: 1.1, y: 1.1 },
                  opacity: 1.0,
                  position: { x: -25, y: -25 }
                }
              },
              {
                id: 'outro_clip',
                media_url: 'https://storage.example.com/outro.jpg',
                start_time_ms: 25000,
                end_time_ms: 30000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              }
            ]
          },
          // Voiceover Audio Track
          {
            id: 'audio_track_voiceover',
            type: 'audio',
            clips: [
              {
                id: 'voiceover_clip',
                media_url: 'https://storage.example.com/narration.mp3',
                start_time_ms: 0,
                end_time_ms: 30000,
                properties: {
                  volume: 0.8,
                  fade_in_ms: 500,
                  fade_out_ms: 1000
                }
              }
            ]
          },
          // Background Music Track
          {
            id: 'audio_track_music',
            type: 'audio',
            clips: [
              {
                id: 'bgm_clip',
                media_url: 'https://storage.example.com/background-music.mp3',
                start_time_ms: 0,
                end_time_ms: 30000,
                properties: {
                  volume: 0.3,
                  fade_in_ms: 2000,
                  fade_out_ms: 3000
                }
              }
            ]
          },
          // Subtitle Track
          {
            id: 'subtitle_track_1',
            type: 'subtitle',
            clips: [
              {
                id: 'subtitle_clip',
                media_url: 'https://storage.example.com/subtitles.srt',
                start_time_ms: 0,
                end_time_ms: 30000,
                properties: {
                  preset: 'netflix',
                  font_size: 28,
                  font_family: 'dejavu-sans',
                  color: '#FFFFFF',
                  position: { x: 0, y: 85 },
                  text_wrapping: {
                    enabled: true,
                    max_chars_per_line: 42,
                    strategy: 'word',
                    language: 'auto'
                  },
                  outline: {
                    enabled: true,
                    width: 3,
                    color: '#000000',
                    blur: 1
                  },
                  shadow: {
                    enabled: true,
                    offset_x: 2,
                    offset_y: 2,
                    blur: 3,
                    color: '#000000',
                    opacity: 0.8
                  }
                }
              }
            ]
          }
        ]
      },
      output: {
        resolution: { width: 1920, height: 1080 },
        framerate: 30,
        format: 'mp4',
        quality: 'high',
        codec: 'h264'
      }
    },
    webhook: {
      url: 'https://your-app.com/webhook',
      events: ['task.started', 'task.progress', 'task.completed', 'task.failed'],
      secret: 'your_webhook_secret'
    },
    metadata: {
      user_reference: 'marketing_video_001',
      tags: ['marketing', 'product-demo'],
      priority: 'high',
      retention_hours: 48
    }
  };

  return client.createRender(renderRequest);
}

Webhook Handler

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

// Webhook endpoint
app.post('/webhook/veedeo', (req, res) => {
  const signature = req.headers['x-veedeo-signature'];
  const timestamp = req.headers['x-veedeo-timestamp'];
  const payload = req.body;

  // Verify webhook signature
  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Handle different event types
  const { event, task_id, data } = payload;

  switch (event) {
    case 'task.started':
      console.log(`Render ${task_id} started at ${data.started_at}`);
      // Update database status
      updateTaskStatus(task_id, 'processing');
      break;

    case 'task.progress':
      console.log(`Render ${task_id} progress: ${data.progress.percentage}%`);
      // Update progress in real-time UI
      broadcastProgress(task_id, data.progress);
      break;

    case 'task.completed':
      console.log(`Render ${task_id} completed!`);
      const { video_url, thumbnail_url, expires_at } = data.output;

      // Save output URLs and download files for permanent storage
      saveRenderOutput(task_id, {
        video_url,
        thumbnail_url,
        expires_at
      });

      // Download files before they expire
      downloadAndStore(task_id, video_url, thumbnail_url);
      break;

    case 'task.failed':
      console.error(`Render ${task_id} failed:`, data.error);

      // Log error details
      logError(task_id, data.error);

      // Retry logic for certain error types
      if (data.error.retry_possible) {
        scheduleRetry(task_id);
      }
      break;

    default:
      console.log(`Unknown event type: ${event}`);
  }

  res.status(200).json({ received: true });
});

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  const receivedSignature = signature.replace('sha256=', '');

  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature, 'hex'),
    Buffer.from(receivedSignature, 'hex')
  );
}

async function downloadAndStore(taskId, videoUrl, thumbnailUrl) {
  try {
    // Download video file
    const videoResponse = await fetch(videoUrl);
    const videoBuffer = await videoResponse.buffer();

    // Upload to your permanent storage (S3, GCS, etc.)
    await uploadToPermanentStorage(`renders/${taskId}.mp4`, videoBuffer);

    // Download thumbnail
    const thumbnailResponse = await fetch(thumbnailUrl);
    const thumbnailBuffer = await thumbnailResponse.buffer();

    await uploadToPermanentStorage(`thumbnails/${taskId}.jpg`, thumbnailBuffer);

    console.log(`Files saved for task ${taskId}`);
  } catch (error) {
    console.error(`Failed to download files for task ${taskId}:`, error);
  }
}

Python Examples

Basic Client Implementation

import requests
import hmac
import hashlib
import json
import time
from typing import Dict, List, Optional

class VeedeoClient:
    def __init__(self, api_key: str, base_url: str = 'https://api.veedeo.dev/v3'):
        self.api_key = api_key
        self.base_url = base_url
        self.version = '2025-01-17'

    def _request(self, endpoint: str, method: str = 'GET', data: Dict = None) -> Dict:
        url = f"{self.base_url}{endpoint}"
        headers = {
            'Authorization': f'Bearer {self.api_key}',
            'Content-Type': 'application/json',
            'X-Veedeo-Version': self.version
        }

        response = requests.request(
            method=method,
            url=url,
            headers=headers,
            json=data
        )

        if not response.ok:
            error_data = response.json()
            raise VeedeoError(
                error_data['error']['message'],
                error_data['error']['code'],
                response.status_code
            )

        return response.json()

    def create_render(self, render_request: Dict) -> Dict:
        return self._request('/renders', 'POST', render_request)

    def get_task(self, task_id: str) -> Dict:
        return self._request(f'/renders/{task_id}')

    def list_tasks(self, **params) -> Dict:
        query_string = '&'.join([f"{k}={v}" for k, v in params.items()])
        endpoint = f'/renders?{query_string}' if query_string else '/renders'
        return self._request(endpoint)

    def cancel_task(self, task_id: str) -> Dict:
        return self._request(f'/renders/{task_id}', 'DELETE')

    def retry_task(self, task_id: str) -> Dict:
        return self._request(f'/renders/{task_id}/retry', 'POST')

class VeedeoError(Exception):
    def __init__(self, message: str, code: str, status_code: int):
        super().__init__(message)
        self.code = code
        self.status_code = status_code

Social Media Video Generator

def create_social_media_video(client: VeedeoClient, images: List[str], music_url: str) -> Dict:
    """Create a social media video from images and background music."""

    # Calculate timing for equal duration per image
    total_duration = 15000  # 15 seconds
    clip_duration = total_duration // len(images)

    # Build video clips
    video_clips = []
    for i, image_url in enumerate(images):
        clip = {
            'id': f'clip_{i+1}',
            'media_url': image_url,
            'start_time_ms': i * clip_duration,
            'end_time_ms': (i + 1) * clip_duration,
            'properties': {
                'scale': {'x': 1.1, 'y': 1.1},  # Slight zoom effect
                'opacity': 1.0,
                'position': {'x': 0, 'y': 0}
            }
        }
        video_clips.append(clip)

    render_request = {
        'version': '3.0',
        'request_id': 'social_media_1735776000',
        'input': {
            'timeline': {
                'duration_ms': total_duration,
                'tracks': [
                    {
                        'id': 'video_track',
                        'type': 'video',
                        'clips': video_clips
                    },
                    {
                        'id': 'music_track',
                        'type': 'audio',
                        'clips': [
                            {
                                'id': 'background_music',
                                'media_url': music_url,
                                'start_time_ms': 0,
                                'end_time_ms': total_duration,
                                'properties': {
                                    'volume': 0.4,
                                    'fade_in_ms': 1000,
                                    'fade_out_ms': 2000
                                }
                            }
                        ]
                    }
                ]
            },
            'output': {
                'resolution': {'width': 1080, 'height': 1080},  # Square format
                'framerate': 30,
                'format': 'mp4',
                'quality': 'high'
            }
        },
        'webhook': {
            'url': 'https://your-app.com/webhook',
            'events': ['task.completed']
        },
        'metadata': {
            'tags': ['social-media', 'instagram', 'square'],
            'priority': 'standard'
        }
    }

    return client.create_render(render_request)

Flask Webhook Handler

from flask import Flask, request, jsonify
import hmac
import hashlib
import json

app = Flask(__name__)

@app.route('/webhook/veedeo', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Veedeo-Signature', '')
    payload = request.get_json()

    # Verify signature
    if not verify_webhook_signature(payload, signature, app.config['WEBHOOK_SECRET']):
        return jsonify({'error': 'Invalid signature'}), 401

    event_type = payload.get('event')
    task_id = payload.get('task_id')
    data = payload.get('data', {})

    if event_type == 'task.completed':
        handle_task_completed(task_id, data)
    elif event_type == 'task.failed':
        handle_task_failed(task_id, data)
    elif event_type == 'task.progress':
        handle_task_progress(task_id, data)

    return jsonify({'status': 'received'})

def verify_webhook_signature(payload: dict, signature: str, secret: str) -> bool:
    """Verify HMAC-SHA256 signature for webhook security."""
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        json.dumps(payload, separators=(',', ':')).encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    received_signature = signature.replace('sha256=', '')

    return hmac.compare_digest(expected_signature, received_signature)

def handle_task_completed(task_id: str, data: dict):
    """Handle completed render task."""
    output = data.get('output', {})
    video_url = output.get('video_url')
    thumbnail_url = output.get('thumbnail_url')
    expires_at = output.get('expires_at')

    # Download and store files before they expire
    download_and_store_files(task_id, video_url, thumbnail_url)

    # Update database
    update_task_status(task_id, 'completed', {
        'video_url': video_url,
        'thumbnail_url': thumbnail_url,
        'expires_at': expires_at
    })

def handle_task_failed(task_id: str, data: dict):
    """Handle failed render task."""
    error = data.get('error', {})
    error_code = error.get('code')
    error_message = error.get('message')
    retry_possible = error.get('retry_possible', False)

    # Log error
    log_error(task_id, error_code, error_message)

    # Schedule retry if possible
    if retry_possible:
        schedule_retry(task_id)
    else:
        update_task_status(task_id, 'failed', {'error': error})

PHP Examples

Basic Client Implementation

<?php

class VeedeoClient {
    private $apiKey;
    private $baseUrl;
    private $version;

    public function __construct($apiKey, $baseUrl = 'https://api.veedeo.dev/v3') {
        $this->apiKey = $apiKey;
        $this->baseUrl = $baseUrl;
        $this->version = '2025-01-17';
    }

    private function request($endpoint, $method = 'GET', $data = null) {
        $url = $this->baseUrl . $endpoint;

        $headers = [
            'Authorization: Bearer ' . $this->apiKey,
            'Content-Type: application/json',
            'X-Veedeo-Version: ' . $this->version
        ];

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

        if ($data) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        $decoded = json_decode($response, true);

        if ($httpCode >= 400) {
            throw new VeedeoException(
                $decoded['error']['message'],
                $decoded['error']['code'],
                $httpCode
            );
        }

        return $decoded;
    }

    public function createRender($renderRequest) {
        return $this->request('/renders', 'POST', $renderRequest);
    }

    public function getTask($taskId) {
        return $this->request('/renders/' . $taskId);
    }

    public function listTasks($params = []) {
        $queryString = http_build_query($params);
        $endpoint = '/renders' . ($queryString ? '?' . $queryString : '');
        return $this->request($endpoint);
    }

    public function cancelTask($taskId) {
        return $this->request('/renders/' . $taskId, 'DELETE');
    }

    public function retryTask($taskId) {
        return $this->request('/renders/' . $taskId . '/retry', 'POST');
    }
}

class VeedeoException extends Exception {
    public $code;
    public $statusCode;

    public function __construct($message, $code, $statusCode) {
        parent::__construct($message);
        $this->code = $code;
        $this->statusCode = $statusCode;
    }
}

Product Demo Video

function createProductDemo($client, $productImages, $voiceoverUrl) {
    $clipDuration = 4000; // 4 seconds per image
    $totalDuration = count($productImages) * $clipDuration;

    $videoClips = [];
    foreach ($productImages as $index => $imageUrl) {
        $videoClips[] = [
            'id' => 'product_clip_' . ($index + 1),
            'media_url' => $imageUrl,
            'start_time_ms' => $index * $clipDuration,
            'end_time_ms' => ($index + 1) * $clipDuration,
            'properties' => [
                'scale' => ['x' => 1.0, 'y' => 1.0],
                'opacity' => 1.0,
                'position' => ['x' => 0, 'y' => 0]
            ]
        ];
    }

    $renderRequest = [
        'version' => '3.0',
        'request_id' => 'product_demo_' . time(),
        'input' => [
            'timeline' => [
                'duration_ms' => $totalDuration,
                'tracks' => [
                    [
                        'id' => 'video_track',
                        'type' => 'video',
                        'clips' => $videoClips
                    ],
                    [
                        'id' => 'voiceover_track',
                        'type' => 'audio',
                        'clips' => [
                            [
                                'id' => 'voiceover',
                                'media_url' => $voiceoverUrl,
                                'start_time_ms' => 0,
                                'end_time_ms' => $totalDuration,
                                'properties' => [
                                    'volume' => 0.9,
                                    'fade_in_ms' => 300,
                                    'fade_out_ms' => 500
                                ]
                            ]
                        ]
                    ]
                ]
            ],
            'output' => [
                'resolution' => ['width' => 1920, 'height' => 1080],
                'framerate' => 30,
                'format' => 'mp4',
                'quality' => 'high'
            ]
        ],
        'webhook' => [
            'url' => 'https://your-app.com/webhook.php',
            'events' => ['task.completed', 'task.failed']
        ],
        'metadata' => [
            'tags' => ['product-demo', 'marketing'],
            'priority' => 'standard'
        ]
    ];

    return $client->createRender($renderRequest);
}

Webhook Handler

<?php

// webhook.php
$input = file_get_contents('php://input');
$payload = json_decode($input, true);

$signature = $_SERVER['HTTP_X_VEEDEO_SIGNATURE'] ?? '';
$webhookSecret = $_ENV['WEBHOOK_SECRET'];

// Verify signature
if (!verifyWebhookSignature($payload, $signature, $webhookSecret)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

$event = $payload['event'];
$taskId = $payload['task_id'];
$data = $payload['data'];

switch ($event) {
    case 'task.completed':
        handleTaskCompleted($taskId, $data);
        break;
    case 'task.failed':
        handleTaskFailed($taskId, $data);
        break;
    case 'task.progress':
        handleTaskProgress($taskId, $data);
        break;
}

http_response_code(200);
echo json_encode(['status' => 'received']);

function verifyWebhookSignature($payload, $signature, $secret) {
    $expectedSignature = hash_hmac('sha256', json_encode($payload), $secret);
    $receivedSignature = str_replace('sha256=', '', $signature);

    return hash_equals($expectedSignature, $receivedSignature);
}

function handleTaskCompleted($taskId, $data) {
    $videoUrl = $data['output']['video_url'];
    $thumbnailUrl = $data['output']['thumbnail_url'];
    $expiresAt = $data['output']['expires_at'];

    // Download files before expiry
    downloadAndStore($taskId, $videoUrl, $thumbnailUrl);

    // Update database
    updateTaskStatus($taskId, 'completed', [
        'video_url' => $videoUrl,
        'thumbnail_url' => $thumbnailUrl,
        'expires_at' => $expiresAt
    ]);
}

function downloadAndStore($taskId, $videoUrl, $thumbnailUrl) {
    // Download video
    $videoContent = file_get_contents($videoUrl);
    file_put_contents("renders/{$taskId}.mp4", $videoContent);

    // Download thumbnail
    $thumbnailContent = file_get_contents($thumbnailUrl);
    file_put_contents("thumbnails/{$taskId}.jpg", $thumbnailContent);
}

cURL Examples

Create Basic Render

curl -X POST https://api.veedeo.dev/v3/tasks \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -H "X-Veedeo-Version: 2025-01-17" \
  -d '{
    "version": "3.0",
    "request_id": "curl_test_001",
    "input": {
      "timeline": {
        "duration_ms": 10000,
        "tracks": [
          {
            "id": "video_track",
            "type": "video",
            "clips": [
              {
                "id": "clip_1",
                "media_url": "https://example.com/image.jpg",
                "start_time_ms": 0,
                "end_time_ms": 10000,
                "properties": {
                  "scale": {"x": 1.0, "y": 1.0},
                  "opacity": 1.0,
                  "position": {"x": 0, "y": 0}
                }
              }
            ]
          }
        ]
      },
      "output": {
        "resolution": {"width": 1920, "height": 1080},
        "framerate": 30,
        "format": "mp4",
        "quality": "high"
      }
    },
    "webhook": {
      "url": "https://webhook.site/your-unique-id",
      "events": ["task.completed"]
    }
  }'

Get Task Status

curl -X GET https://api.veedeo.dev/v3/tasks/tsk_1234567890abcdef \
  -H "Authorization: Bearer your_api_key" \
  -H "X-Veedeo-Version: 2025-01-17"

List Tasks with Filters

curl -X GET "https://api.veedeo.dev/v3/tasks?status=completed&limit=10&offset=0" \
  -H "Authorization: Bearer your_api_key" \
  -H "X-Veedeo-Version: 2025-01-17"

Cancel Task

curl -X DELETE https://api.veedeo.dev/v3/tasks/tsk_1234567890abcdef \
  -H "Authorization: Bearer your_api_key" \
  -H "X-Veedeo-Version: 2025-01-17"

Use Case Examples

Media Fit Modes - Handling Different Aspect Ratios

This section demonstrates how to use the fit parameter to handle different media aspect ratios effectively.

// Demonstration of all fit modes with your specific use case
async function demonstrateFitModes() {
  const veedeoClient = new VeedeoClient('your_api_key');

  // Example 1: Square image (1440×1440) on vertical canvas (1080×1920)
  // Using "cover" mode to eliminate black bars
  const coverModeExample = {
    version: '3.0',
    request_id: `fit_cover_${Date.now()}`,
    input: {
      timeline: {
        duration_ms: 5000,
        tracks: [{
          id: 'video_track_cover',
          type: 'video',
          clips: [{
            id: 'square_image_cover',
            media_url: 'https://storage.example.com/square-image-1440x1440.jpg',
            start_time_ms: 0,
            end_time_ms: 5000,
            properties: {
              fit: 'cover',  // Key: Fills entire canvas, crops excess
              scale: { x: 1.0, y: 1.0 },
              position: { x: 0, y: 0 },
              opacity: 1.0
            }
          }]
        }]
      },
      metadata: {
        resolution: { width: 1080, height: 1920 },
        framerate: 30
      }
    },
    output: { format: 'mp4', quality: 'high' },
    webhook: { url: 'https://your-app.com/webhooks/veedeo' }
  };

  // Example 2: Multi-layer composition with different fit modes
  const multiLayerExample = {
    version: '3.0',
    request_id: `fit_multi_${Date.now()}`,
    input: {
      timeline: {
        duration_ms: 10000,
        tracks: [{
          id: 'composite_track',
          type: 'video',
          clips: [
            // Background layer - always use cover for full coverage
            {
              id: 'background_layer',
              media_url: 'https://storage.example.com/background-landscape.jpg',
              start_time_ms: 0,
              end_time_ms: 10000,
              properties: {
                layer_type: 'background',
                fit: 'cover',  // Ensures no background gaps
                z_index: 0,
                background_blur: 15  // Subtle blur effect
              }
            },
            // Product image - preserve aspect ratio
            {
              id: 'product_foreground',
              media_url: 'https://storage.example.com/product-square.jpg',
              start_time_ms: 1000,
              end_time_ms: 9000,
              properties: {
                layer_type: 'foreground',
                fit: 'contain',  // Keep product undistorted
                z_index: 1,
                scale: { x: 0.7, y: 0.7 },  // 70% of canvas size
                position: { x: 162, y: 288 },  // Center with 15% margin
                opacity: 0.95
              }
            },
            // Logo overlay - exact placement
            {
              id: 'logo_overlay',
              media_url: 'https://storage.example.com/logo.png',
              start_time_ms: 0,
              end_time_ms: 10000,
              properties: {
                fit: 'contain',  // Preserve logo aspect ratio
                z_index: 2,
                scale: { x: 0.2, y: 0.2 },  // Small logo
                position: { x: 50, y: 50 },  // Top-left corner
                opacity: 0.8
              }
            }
          ]
        }]
      },
      metadata: {
        resolution: { width: 1080, height: 1920 },
        framerate: 30
      }
    },
    output: { format: 'mp4', quality: 'high' },
    webhook: { url: 'https://your-app.com/webhooks/veedeo' }
  };

  // Example 3: Social media content optimization
  const socialMediaExample = {
    version: '3.0',
    request_id: `social_${Date.now()}`,
    input: {
      timeline: {
        duration_ms: 15000,
        tracks: [{
          id: 'social_track',
          type: 'video',
          clips: [
            // Main content image
            {
              id: 'main_content',
              media_url: 'https://storage.example.com/content-image.jpg',
              start_time_ms: 0,
              end_time_ms: 15000,
              properties: {
                fit: 'cover',  // Social media best practice: no black bars
                scale: { x: 1.0, y: 1.0 },
                position: { x: 0, y: 0 }
              }
            }
          ]
        }]
      },
      metadata: {
        resolution: { width: 1080, height: 1920 }, // Instagram Story format
        framerate: 30
      }
    },
    output: { format: 'mp4', quality: 'high' },
    webhook: { url: 'https://your-app.com/webhooks/veedeo' }
  };

  try {
    console.log('🎬 Creating videos with optimized fit modes...');

    // Create all examples
    const [coverTask, multiTask, socialTask] = await Promise.all([
      veedeoClient.createRender(coverModeExample),
      veedeoClient.createRender(multiLayerExample),
      veedeoClient.createRender(socialMediaExample)
    ]);

    console.log('✅ All fit mode demonstrations started:');
    console.log(`Cover mode (no black bars): ${coverTask.task_id}`);
    console.log(`Multi-layer composition: ${multiTask.task_id}`);
    console.log(`Social media optimized: ${socialTask.task_id}`);

    return {
      cover: coverTask.task_id,
      multiLayer: multiTask.task_id,
      socialMedia: socialTask.task_id
    };

  } catch (error) {
    console.error('Error creating fit mode demonstrations:', error);
    throw error;
  }
}

// Usage with monitoring
demonstrateFitModes()
  .then(tasks => {
    console.log('All demonstrations started. Monitor progress:');
    Object.entries(tasks).forEach(([type, taskId]) => {
      console.log(`${type}: https://api.veedeo.dev/v3/tasks/${taskId}`);
    });
  })
  .catch(console.error);

Fit Mode Comparison for Your Use Case (1440×1440 → 1080×1920):

Fit ModeResultVisual EffectRecommended For
coverFills entire canvasCrops sides, no black barsSocial media, backgrounds
containShows complete imageBlack bars top/bottomPresentations, documentation
fillStretches to fitDistorts square to rectangleRarely recommended
stretchSame as fillDistorts aspect ratioLegacy compatibility only

Educational Content

async function createEducationalVideo() {
  const renderRequest = {
    version: '3.0',
    request_id: 'education_1735776000000',
    input: {
      timeline: {
        duration_ms: 45000,
        tracks: [
          // Lesson slides
          {
            id: 'slides_track',
            type: 'video',
            clips: [
              {
                id: 'intro_slide',
                media_url: 'https://example.com/lesson-intro.jpg',
                start_time_ms: 0,
                end_time_ms: 5000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              },
              {
                id: 'content_slide_1',
                media_url: 'https://example.com/lesson-content-1.jpg',
                start_time_ms: 5000,
                end_time_ms: 20000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              },
              {
                id: 'content_slide_2',
                media_url: 'https://example.com/lesson-content-2.jpg',
                start_time_ms: 20000,
                end_time_ms: 35000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              },
              {
                id: 'summary_slide',
                media_url: 'https://example.com/lesson-summary.jpg',
                start_time_ms: 35000,
                end_time_ms: 45000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              }
            ]
          },
          // Teacher narration
          {
            id: 'narration_track',
            type: 'audio',
            clips: [
              {
                id: 'teacher_voice',
                media_url: 'https://example.com/teacher-narration.mp3',
                start_time_ms: 0,
                end_time_ms: 45000,
                properties: {
                  volume: 0.9,
                  fade_in_ms: 200,
                  fade_out_ms: 500
                }
              }
            ]
          },
          // Educational subtitles
          {
            id: 'subtitle_track',
            type: 'subtitle',
            clips: [
              {
                id: 'lesson_subtitles',
                media_url: 'https://example.com/lesson-subtitles.srt',
                start_time_ms: 0,
                end_time_ms: 45000,
                properties: {
                  preset: 'professional',
                  font_size: 24,
                  font_family: 'liberation-sans',
                  color: '#FFFFFF',
                  position: { x: 0, y: 85 },
                  text_wrapping: {
                    enabled: true,
                    max_chars_per_line: 50,
                    strategy: 'word',
                    language: 'auto'
                  },
                  outline: {
                    enabled: true,
                    width: 2,
                    color: '#000000'
                  }
                }
              }
            ]
          }
        ]
      },
      output: {
        resolution: { width: 1920, height: 1080 },
        framerate: 30,
        format: 'mp4',
        quality: 'high'
      }
    },
    metadata: {
      tags: ['education', 'lesson', 'e-learning'],
      priority: 'standard',
      user_reference: 'lesson_chapter_3'
    }
  };

  return client.createRender(renderRequest);
}

Chinese Social Media Video (Douyin Style)

async function createDouyinVideo() {
  const client = new VeedeoClient('your_api_key');

  const renderRequest = {
    version: '3.0',
    request_id: 'douyin_1735776000000',
    input: {
      timeline: {
        duration_ms: 15000,
        tracks: [
          {
            id: 'video_track',
            type: 'video',
            clips: [
              {
                id: 'main_clip',
                media_url: 'https://storage.example.com/lifestyle.mp4',
                start_time_ms: 0,
                end_time_ms: 15000,
                properties: {
                  scale: { x: 1.1, y: 1.1 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              }
            ]
          },
          {
            id: 'audio_track',
            type: 'audio',
            clips: [
              {
                id: 'bgm_clip',
                media_url: 'https://storage.example.com/trending-music.mp3',
                start_time_ms: 0,
                end_time_ms: 15000,
                properties: {
                  volume: 0.4,
                  fade_in_ms: 1000,
                  fade_out_ms: 1000
                }
              }
            ]
          },
          {
            id: 'subtitle_track',
            type: 'subtitle',
            clips: [
              {
                id: 'subtitle_clip',
                media_url: 'https://storage.example.com/chinese-text.srt',
                start_time_ms: 0,
                end_time_ms: 15000,
                properties: {
                  preset: 'douyin',
                  font_family: 'sourcehan-sans-sc-bold',
                  font_size: 24,
                  color: '#FFFFFF',
                  position: { x: 0, y: 85 },
                  text_wrapping: {
                    enabled: true,
                    max_chars_per_line: 16,
                    strategy: 'character',
                    language: 'cjk'
                  },
                  outline: {
                    enabled: true,
                    width: 4,
                    color: '#000000',
                    blur: 1
                  },
                  shadow: {
                    enabled: true,
                    offset_x: 2,
                    offset_y: 2,
                    blur: 4,
                    color: '#FF6B6B',
                    opacity: 0.6
                  }
                }
              }
            ]
          }
        ]
      },
      output: {
        resolution: { width: 720, height: 1280 },
        framerate: 30,
        format: 'mp4',
        quality: 'high',
        codec: 'h264'
      }
    },
    webhook: {
      url: 'https://your-app.com/webhook',
      events: ['task.completed', 'task.failed']
    },
    metadata: {
      tags: ['douyin', 'chinese', 'social-media'],
      priority: 'standard',
      user_reference: 'douyin_lifestyle_001'
    }
  };

  return client.createRender(renderRequest);
}

Xiaohongshu (RedBook) Style Video

async function createXiaohongshuVideo() {
  const client = new VeedeoClient('your_api_key');

  const renderRequest = {
    version: '3.0',
    request_id: 'xhs_1735776000000',
    input: {
      timeline: {
        duration_ms: 30000,
        tracks: [
          {
            id: 'video_track',
            type: 'video',
            clips: [
              {
                id: 'beauty_clip',
                media_url: 'https://storage.example.com/beauty-tutorial.mp4',
                start_time_ms: 0,
                end_time_ms: 30000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              }
            ]
          },
          {
            id: 'subtitle_track',
            type: 'subtitle',
            clips: [
              {
                id: 'tutorial_subtitles',
                media_url: 'https://storage.example.com/beauty-tips.srt',
                start_time_ms: 0,
                end_time_ms: 30000,
                properties: {
                  preset: 'xiaohongshu',
                  font_family: 'sourcehan-sans-sc',
                  font_size: 22,
                  color: '#FFFFFF',
                  position: { x: 0, y: 80 },
                  background: {
                    enabled: true,
                    color: 'rgba(255,105,180,0.15)',
                    opacity: 0.8,
                    padding: 8
                  },
                  outline: {
                    enabled: true,
                    width: 3,
                    color: '#FF69B4',
                    blur: 1
                  },
                  shadow: {
                    enabled: true,
                    offset_x: 1,
                    offset_y: 1,
                    blur: 3,
                    color: '#FFB6C1',
                    opacity: 0.8
                  }
                }
              }
            ]
          }
        ]
      },
      output: {
        resolution: { width: 1080, height: 1920 },
        framerate: 30,
        format: 'mp4',
        quality: 'high'
      }
    },
    metadata: {
      tags: ['xiaohongshu', 'beauty', 'tutorial'],
      priority: 'standard',
      user_reference: 'xhs_beauty_tutorial_001'
    }
  };

  return client.createRender(renderRequest);
}

News Bulletin

async function createNewsBulletin() {
  const renderRequest = {
    version: '3.0',
    request_id: 'news_1735776000000',
    input: {
      timeline: {
        duration_ms: 60000,
        tracks: [
          // News graphics
          {
            id: 'graphics_track',
            type: 'video',
            clips: [
              {
                id: 'opening_graphic',
                media_url: 'https://example.com/news-intro.jpg',
                start_time_ms: 0,
                end_time_ms: 3000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              },
              {
                id: 'story_1_graphic',
                media_url: 'https://example.com/story-1-graphic.jpg',
                start_time_ms: 3000,
                end_time_ms: 18000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              },
              {
                id: 'story_2_graphic',
                media_url: 'https://example.com/story-2-graphic.jpg',
                start_time_ms: 18000,
                end_time_ms: 33000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              },
              {
                id: 'closing_graphic',
                media_url: 'https://example.com/news-outro.jpg',
                start_time_ms: 33000,
                end_time_ms: 60000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              }
            ]
          },
          // News anchor voice
          {
            id: 'anchor_voice_track',
            type: 'audio',
            clips: [
              {
                id: 'news_anchor',
                media_url: 'https://example.com/news-anchor-voice.mp3',
                start_time_ms: 0,
                end_time_ms: 60000,
                properties: {
                  volume: 0.95,
                  fade_in_ms: 100,
                  fade_out_ms: 200
                }
              }
            ]
          },
          // News subtitles
          {
            id: 'news_subtitles_track',
            type: 'subtitle',
            clips: [
              {
                id: 'news_captions',
                media_url: 'https://example.com/news-captions.srt',
                start_time_ms: 0,
                end_time_ms: 60000,
                properties: {
                  preset: 'professional',
                  font_size: 22,
                  font_family: 'liberation-sans',
                  color: '#FFFFFF',
                  position: { x: 0, y: 88 },
                  text_wrapping: {
                    enabled: true,
                    max_chars_per_line: 45,
                    strategy: 'word',
                    language: 'auto'
                  },
                  outline: {
                    enabled: true,
                    width: 2,
                    color: '#000000'
                  },
                  shadow: {
                    enabled: true,
                    offset_x: 1,
                    offset_y: 1,
                    blur: 2,
                    color: '#000000',
                    opacity: 0.6
                  }
                }
              }
            ]
          }
        ]
      },
      output: {
        resolution: { width: 1920, height: 1080 },
        framerate: 30,
        format: 'mp4',
        quality: 'high'
      }
    },
    metadata: {
      tags: ['news', 'bulletin', 'broadcast'],
      priority: 'high',
      user_reference: 'evening_news_20250117'
    }
  };

  return client.createRender(renderRequest);
}

Error Handling

Comprehensive Error Handling

async function robustRenderCreation(renderRequest) {
  const client = new VeedeoClient('your_api_key');
  const maxRetries = 3;
  let retryCount = 0;

  while (retryCount < maxRetries) {
    try {
      const result = await client.createRender(renderRequest);
      console.log(`Render created successfully: ${result.task_id}`);
      return result;

    } catch (error) {
      console.error(`Attempt ${retryCount + 1} failed:`, error.message);

      // Handle specific error codes
      switch (error.code) {
        case 'VALIDATION_ERROR':
          // Don't retry validation errors
          console.error('Request validation failed:', error.message);
          throw error;

        case 'MEDIA_DOWNLOAD_FAILED':
          // Retry download failures
          if (retryCount < maxRetries - 1) {
            console.log('Media download failed, retrying...');
            await sleep(2000 * Math.pow(2, retryCount)); // Exponential backoff
            retryCount++;
            continue;
          }
          break;

        case 'INSUFFICIENT_CREDITS':
          // Don't retry insufficient credits
          console.error('Insufficient credits:', error.message);
          throw error;

        case 'RATE_LIMIT_EXCEEDED':
          // Wait and retry for rate limits
          if (retryCount < maxRetries - 1) {
            const waitTime = 60000; // Wait 1 minute
            console.log(`Rate limited, waiting ${waitTime/1000} seconds...`);
            await sleep(waitTime);
            retryCount++;
            continue;
          }
          break;

        default:
          // Retry other errors
          if (retryCount < maxRetries - 1) {
            await sleep(1000 * Math.pow(2, retryCount));
            retryCount++;
            continue;
          }
      }

      // If we get here, all retries exhausted
      throw error;
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Testing Examples

Unit Tests (Jest)

// tests/veedeo-client.test.js
const VeedeoClient = require('../src/veedeo-client');

describe('VeedeoClient', () => {
  let client;

  beforeEach(() => {
    client = new VeedeoClient('test_api_key', 'https://api-sandbox.veedeo.dev/v3');
  });

  test('should create render with valid request', async () => {
    const renderRequest = {
      version: '3.0',
      request_id: 'test_request_001',
      input: {
        timeline: {
          duration_ms: 5000,
          tracks: [
            {
              id: 'test_track',
              type: 'video',
              clips: [
                {
                  id: 'test_clip',
                  media_url: 'https://storage.googleapis.com/veedeo-test-assets/sample.jpg',
                  start_time_ms: 0,
                  end_time_ms: 5000,
                  properties: {
                    scale: { x: 1.0, y: 1.0 },
                    opacity: 1.0,
                    position: { x: 0, y: 0 }
                  }
                }
              ]
            }
          ]
        },
        output: {
          resolution: { width: 1280, height: 720 },
          framerate: 30,
          format: 'mp4',
          quality: 'standard'
        }
      }
    };

    const result = await client.createRender(renderRequest);

    expect(result).toHaveProperty('task_id');
    expect(result).toHaveProperty('status');
    expect(result.status).toBe('queued');
  });

  test('should handle validation errors', async () => {
    const invalidRequest = {
      version: '3.0',
      // Missing required fields
    };

    await expect(client.createRender(invalidRequest))
      .rejects
      .toThrow(VeedeoError);
  });

  test('should get task status', async () => {
    const taskId = 'tsk_test_123';

    const status = await client.getTask(taskId);

    expect(status).toHaveProperty('task_id', taskId);
    expect(status).toHaveProperty('status');
  });
});

Integration Tests

// tests/integration.test.js
describe('Veedeo API Integration', () => {
  test('complete render workflow', async () => {
    const client = new VeedeoClient(process.env.VEEDEO_API_KEY);

    // Create render
    const renderRequest = createTestRenderRequest();
    const createResult = await client.createRender(renderRequest);

    expect(createResult.task_id).toBeTruthy();

    // Poll for completion
    const taskId = createResult.task_id;
    let status = 'queued';
    let attempts = 0;
    const maxAttempts = 30; // 5 minutes max

    while (status !== 'completed' && status !== 'failed' && attempts < maxAttempts) {
      await sleep(10000); // Wait 10 seconds
      const taskStatus = await client.getTask(taskId);
      status = taskStatus.status;
      attempts++;

      console.log(`Attempt ${attempts}: Status is ${status}`);

      if (taskStatus.progress) {
        console.log(`Progress: ${taskStatus.progress.percentage}%`);
      }
    }

    expect(status).toBe('completed');

    // Verify output
    const finalStatus = await client.getTask(taskId);
    expect(finalStatus.output).toHaveProperty('video_url');
    expect(finalStatus.output).toHaveProperty('thumbnail_url');

  }, 300000); // 5 minute timeout
});

function createTestRenderRequest() {
  return {
    version: '3.0',
    request_id: 'integration_test_1735776000000',
    input: {
      timeline: {
        duration_ms: 5000,
        tracks: [
          {
            id: 'test_video_track',
            type: 'video',
            clips: [
              {
                id: 'test_clip',
                media_url: 'https://storage.googleapis.com/veedeo-test-assets/test-image.jpg',
                start_time_ms: 0,
                end_time_ms: 5000,
                properties: {
                  scale: { x: 1.0, y: 1.0 },
                  opacity: 1.0,
                  position: { x: 0, y: 0 }
                }
              }
            ]
          }
        ]
      },
      output: {
        resolution: { width: 1280, height: 720 },
        framerate: 30,
        format: 'mp4',
        quality: 'standard'
      }
    },
    metadata: {
      tags: ['integration-test'],
      priority: 'standard'
    }
  };
}

For more examples and use cases, visit our GitHub repository or contact support at support@veedeo.dev.