Veedeo API V3 Examples
Code Examples and Use Cases Last Updated: September 17, 2025
Table of Contents
- JavaScript/Node.js Examples
- Python Examples
- PHP Examples
- cURL Examples
- Use Case Examples
- Error Handling
- 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 Mode | Result | Visual Effect | Recommended For |
---|---|---|---|
cover ✅ | Fills entire canvas | Crops sides, no black bars | Social media, backgrounds |
contain | Shows complete image | Black bars top/bottom | Presentations, documentation |
fill | Stretches to fit | Distorts square to rectangle | Rarely recommended |
stretch | Same as fill | Distorts aspect ratio | Legacy 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.