Skip to main content

Quickstart: Python

Render a video from Python using the requests library.


Prerequisites

pip install requests

Set your credentials:

export VIRALSYNC_API_KEY="vs_prod_YOUR_API_KEY"
export VIRALSYNC_PROJECT_ID="proj_abc123"

Basic example: submit and poll

# render.py
import os
import time
import requests

API_KEY = os.environ['VIRALSYNC_API_KEY']
PROJECT_ID = os.environ['VIRALSYNC_PROJECT_ID']
BASE_URL = 'https://api.viralsync.io'
HEADERS = {
'x-api-key': API_KEY,
'Content-Type': 'application/json',
}


def submit_render() -> str:
payload = {
'kind': 'movie',
'projectId': PROJECT_ID,
'movie': {
'width': 1920,
'height': 1080,
'fps': 30,
'scenes': [
{
'id': 'scene_1',
'duration': 5,
'bgColor': '#1a1a2e',
'layers': [
{
'id': 'title',
'type': 'text',
'text': 'Hello from Python',
'startTime': 0,
'duration': 5,
'zIndex': 1,
'position': {'x': 360, 'y': 460},
'size': {'width': 1200, 'height': 160},
'fontSize': 80,
'fontWeight': 'bold',
'color': '#00d4ff',
'textAlign': 'center',
}
],
}
],
},
}

response = requests.post(
f'{BASE_URL}/v1/render/jobs',
json=payload,
headers=HEADERS,
)
response.raise_for_status()
return response.json()['jobId']


def poll_until_done(job_id: str) -> str:
while True:
response = requests.get(
f'{BASE_URL}/v1/jobs/{job_id}',
headers=HEADERS,
)
response.raise_for_status()
job = response.json()

pct = int((job.get('progress') or 0) * 100)
phase = f" — {job['phase']}" if job.get('phase') else ''
print(f"[{job['status']}] {pct}%{phase}")

if job['status'] == 'done':
return job['outputUrl']
if job['status'] == 'failed':
raise RuntimeError(f"Render failed: {job.get('error')}")
if job['status'] == 'cancelled':
raise RuntimeError('Job was cancelled')

time.sleep(3)


def main():
print('Submitting render job...')
job_id = submit_render()
print(f'Job queued: {job_id}')

print('Waiting for completion...')
output_url = poll_until_done(job_id)
print(f'\nVideo ready: {output_url}')


if __name__ == '__main__':
main()

Download the output video

import urllib.request

def download_video(output_url: str, filename: str = 'output.mp4') -> None:
urllib.request.urlretrieve(output_url, filename)
print(f'Saved to {filename}')

output_url = poll_until_done(job_id)
download_video(output_url)

Real-time progress with SSE

For real-time progress updates, use httpx with streaming (or sseclient-py):

pip install httpx sseclient-py
import json
import httpx
import sseclient

def stream_progress(job_id: str) -> str:
url = f'{BASE_URL}/v1/jobs/{job_id}/progress'

with httpx.stream('GET', url, headers={'x-api-key': API_KEY}) as response:
client = sseclient.SSEClient(response.iter_bytes())
for event in client.events():
if not event.data:
continue
update = json.loads(event.data)
pct = int((update.get('progress') or 0) * 100)
print(f"\r[{update['status']}] {pct}%", end='', flush=True)

if update['status'] == 'done':
print()
return update['outputUrl']
if update['status'] in ('failed', 'cancelled'):
print()
raise RuntimeError(f"Job ended with status: {update['status']}")

Error handling with retry

import time
import requests
from requests.exceptions import HTTPError


def submit_render_with_retry(payload: dict, max_attempts: int = 3) -> str:
for attempt in range(1, max_attempts + 1):
response = requests.post(
f'{BASE_URL}/v1/render/jobs',
json=payload,
headers=HEADERS,
)

if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 5))
if attempt < max_attempts:
print(f'Rate limited. Retrying in {retry_after}s...')
time.sleep(retry_after)
continue

if response.status_code == 409:
error_data = response.json()
if error_data.get('code') == 'CONCURRENCY_LIMIT_EXCEEDED':
if attempt < max_attempts:
print('Concurrency limit reached. Retrying in 10s...')
time.sleep(10)
continue

if response.status_code >= 500 and attempt < max_attempts:
backoff = min(2 ** attempt, 30)
print(f'Server error. Retrying in {backoff}s...')
time.sleep(backoff)
continue

response.raise_for_status()
return response.json()['jobId']

raise RuntimeError(f'Failed after {max_attempts} attempts')

Webhook handler with Flask

pip install flask
# webhook_server.py
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route('/webhooks/viralsync', methods=['POST'])
def viralsync_webhook():
# Always respond 200 immediately
data = request.json
job_id = data['jobId']
status = data['status']

if status == 'done':
output_url = data['outputUrl']
print(f'Job {job_id} complete. URL: {output_url}')
# Download, store, notify user, etc.
elif status == 'failed':
error = data.get('error')
print(f'Job {job_id} failed: {error}')

return jsonify({'received': True}), 200


if __name__ == '__main__':
app.run(port=5000)

Submit the job with callbackUrl: "https://your-server.com/webhooks/viralsync" to receive push notifications instead of polling.


Multi-scene video from a data list

Generate a product showcase video with one scene per product:

def build_product_showcase(products: list[dict]) -> dict:
"""
products: [{'name': 'Widget Pro', 'price': '$49', 'image_url': '...'}]
"""
scenes = []
for i, product in enumerate(products):
scene = {
'id': f'scene_{i}',
'duration': 5,
'bgColor': '#0f172a',
'layers': [
{
'id': f'img_{i}',
'type': 'image',
'source': product['image_url'],
'startTime': 0,
'duration': 5,
'zIndex': 0,
'position': {'x': 0, 'y': 0},
'size': {'width': 960, 'height': 1080},
},
{
'id': f'name_{i}',
'type': 'text',
'text': product['name'],
'startTime': 0,
'duration': 5,
'zIndex': 1,
'position': {'x': 980, 'y': 380},
'size': {'width': 880, 'height': 120},
'fontSize': 56,
'fontWeight': 'bold',
'color': '#ffffff',
'textAlign': 'left',
},
{
'id': f'price_{i}',
'type': 'text',
'text': product['price'],
'startTime': 0,
'duration': 5,
'zIndex': 1,
'position': {'x': 980, 'y': 520},
'size': {'width': 400, 'height': 80 },
'fontSize': 44,
'color': '#00d4ff',
'textAlign': 'left',
},
],
}
scenes.append(scene)

return {
'kind': 'movie',
'projectId': PROJECT_ID,
'renderOptions': {'qualityPreset': 'fast'},
'movie': {
'width': 1920,
'height': 1080,
'fps': 30,
'scenes': scenes,
},
}


# Usage
products = [
{'name': 'Widget Pro', 'price': '$49', 'image_url': 'https://cdn.example.com/widget.jpg'},
{'name': 'Gadget Plus', 'price': '$89', 'image_url': 'https://cdn.example.com/gadget.jpg'},
]

payload = build_product_showcase(products)
job_id = submit_render_with_retry(payload)
output_url = poll_until_done(job_id)
print(f'Showcase video: {output_url}')

Next steps