On this page
API Overview
The JSON-to-video API offers a powerful set of features for video generation, letting you easily create professional videos from images, videos, audio and text — all driven by a single JSON payload.
- Mix Video Generation — combine images, videos, voice-overs and background music.
- Simple Video Generation — create a video from a single media file.
- Loop Video Generation — create looping videos with a background and media files.
- Video Progress Tracking — track the progress of your generation task.
- Video Credits Management — purchase and manage video credits.
Getting started
To start using the JSON-to-video API you need an API key:
- Log in to your account.
- Navigate to
Account Settings > API Keys. - Generate a new API key.
- Send this key with every API request in the
X-API-Keyheader.
Authentication
All API requests require an API key. Send it in the X-API-Key header. Query-parameter auth is still supported for legacy clients, but the header is recommended.
Adding an API key
X-API-Key: your_api_key_here
- Never share your API key publicly.
- Do not store it in public repositories or code.
- If you suspect your key is compromised, generate a new one immediately.
- Use environment variables to store your API key in your codebase.
Example API request
curl -X POST "https://samautomation.work/api/function/video-generation/mix-video" \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"data": {...}}'Rate Limiting
Understanding rate limits and quotas:
| Plan | Video limit | Reset period |
|---|---|---|
| Free Tier | 5 videos | Monthly (1st of month) |
| Basic | 50 videos | Monthly |
| Pro | 100 videos | Monthly |
Rate limit headers
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 45
Basic API Usage
Create your first video with this simple example. Media fields must
contain final public http or https URLs, not
local file paths or unresolved workflow expressions.
{
"type": "video",
"data": {
"settings": {
"aspect_ratio": "9:16",
"background_volume": 0.02,
"voice_volume": 1.0
},
"background_url": "https://cdn.example.com/background-music.mp3",
"voice_url": "https://cdn.example.com/voice-over.mp3",
"media_list": [
{
"type": "image",
"url": "https://cdn.example.com/intro-frame.jpg"
}
]
}
}n8n Media URLs
When you submit a render from n8n, media_list[].url
must contain the resolved media URL that the renderer can download.
The API rejects unresolved n8n expressions before a render task starts.
={{ $('Code in JavaScript2').item.json.videoUrl }}
as text, n8n did not resolve the expression. In the HTTP Request node,
use Expression mode for that field or build the final JSON body in a
Code node.
| Status | Response body | Fix |
|---|---|---|
| 400 | {"success": false, "message": "media_list[0].url must be a valid HTTP(S) URL"} | Send a final https://... media URL, not an unresolved expression or relative path. |
| 400 | {"success": false, "message": "Invalid payload shape"} | Send a JSON object such as {"media_list":[...]}, not a JSON string or array. |
Wrong n8n output
{
"media_list": [
{
"type": "video",
"url": "={{ $('Code in JavaScript2').item.json.videoUrl }}",
"duration": 60
}
]
}Correct resolved output
{
"media_list": [
{
"type": "video",
"url": "https://cdn.example.com/rendered-product-video.mp4",
"duration": 60
}
],
"settings": {
"aspect_ratio": "9:16"
}
}Mix Video Generation
Create a video by combining various media elements (images, videos, audio) with an optional voice-over and background media.
Endpoint
POST /api/function/video-generation/mix-video
| Parameter | Type | Required | Description |
|---|---|---|---|
X-API-Key (header) | string | Yes | Your API key |
| Parameter | Type | Required | Description |
|---|---|---|---|
media_list | array | Yes | Array of media objects used in the video |
settings | object | No | Video settings such as aspect_ratio, quality, etc. |
voice_url | string | No | URL to a voice-over audio file |
background_url | string | No | URL to a background image or video |
transcripts | array | No | Array of caption objects |
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Public http or https URL to the media file |
type | string | Yes | Media type ('image', 'video', 'audio') |
duration | number | Yes | Duration in seconds to display this element |
start_time | number | No | Start time within media file (for video/audio) |
end_time | number | No | End time within media file (for video/audio) |
text_overlay | string | No | Text to overlay on the media |
| Parameter | Type | Default | Description |
|---|---|---|---|
aspect_ratio | string | "16:9" | Aspect ratio of the video ("16:9", "1:1", "9:16") |
resolution | string | "720p" | Resolution of the video ("480p", "720p", "1080p") |
fps | number | 30 | Frames per second |
quality | string | "medium" | Quality of the video ("low", "medium", "high") |
Example request
{
"media_list": [
{
"url": "https://example.com/image1.jpg",
"type": "image",
"duration": 5,
"text_overlay": "Welcome to our presentation"
},
{
"url": "https://example.com/video1.mp4",
"type": "video",
"duration": 10,
"start_time": 2,
"end_time": 12
}
],
"voice_url": "https://example.com/voice.mp3",
"background_url": "https://example.com/background.jpg",
"settings": {
"aspect_ratio": "16:9",
"resolution": "720p"
}
}Response
{
"success": true,
"message": "Video generation started",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"progress_url": "/api/function/video-generation/progress/550e8400-e29b-41d4-a716-446655440000"
}
}| Status | Response body | Description |
|---|---|---|
| 400 | {"success": false, "message": "Invalid JSON data"} | Invalid JSON in the request |
| 400 | {"success": false, "message": "Invalid payload shape"} | Request body or nested data value is not a JSON object |
| 400 | {"success": false, "message": "No media items provided"} | media_list is empty or missing |
| 400 | {"success": false, "message": "media_list[0].url must be a valid HTTP(S) URL"} | A media URL is missing, relative, private, or an unresolved workflow expression |
| 401 | {"success": false, "message": "API key required"} | API key is missing |
| 403 | {"success": false, "message": "Invalid API key"} | Invalid API key |
| 403 | {"success": false, "message": "Free tier limit reached..."} | Quota limit reached |
| 500 | {"success": false, "message": "Internal server error"} | Server error |
Code example: Python
import requests
import json
api_key = "YOUR_API_KEY"
url = "https://samautomation.work/api/function/video-generation/mix-video"
# Video data
data = {
"media_list": [
{
"url": "https://example.com/image1.jpg",
"type": "image",
"duration": 5,
"text_overlay": "Welcome to our presentation"
},
{
"url": "https://example.com/video1.mp4",
"type": "video",
"duration": 10
}
],
"voice_url": "https://example.com/voice.mp3",
"background_url": "https://example.com/background.jpg",
"settings": {
"aspect_ratio": "16:9",
"resolution": "720p"
}
}
# API request
response = requests.post(
url,
headers={
"X-API-Key": api_key,
"Content-Type": "application/json",
},
data=json.dumps(data)
)
# Process the result
result = response.json()
if result["success"]:
video_id = result["data"]["id"]
progress_url = result["data"]["progress_url"]
print(f"Video generation started. ID: {video_id}")
print(f"Check progress at: {progress_url}")
else:
print(f"Error: {result['message']}")Code example: JavaScript
const apiKey = "YOUR_API_KEY";
const url = "https://samautomation.work/api/function/video-generation/mix-video";
// Video data
const data = {
media_list: [
{
url: "https://example.com/image1.jpg",
type: "image",
duration: 5,
text_overlay: "Welcome to our presentation"
},
{
url: "https://example.com/video1.mp4",
type: "video",
duration: 10
}
],
voice_url: "https://example.com/voice.mp3",
background_url: "https://example.com/background.jpg",
settings: {
aspect_ratio: "16:9",
resolution: "720p"
}
};
// API request
fetch(url, {
method: "POST",
headers: {
"X-API-Key": apiKey,
"Content-Type": "application/json"
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
const videoId = result.data.id;
const progressUrl = result.data.progress_url;
console.log(`Video generation started. ID: ${videoId}`);
console.log(`Check progress at: ${progressUrl}`);
} else {
console.error(`Error: ${result.message}`);
}
})
.catch(error => console.error("API call failed:", error));Simple Video Generation
Create a simple video with a single media file.
Endpoint
POST /api/function/video-generation/simple-video
| Parameter | Type | Required | Description |
|---|---|---|---|
X-API-Key (header) | string | Yes | Your API key |
| Parameter | Type | Required | Description |
|---|---|---|---|
media_list | array | Yes | Array with one or more media objects. Each item must include type and url. |
settings | object | No | Video settings such as aspect_ratio, quality, etc. |
Example request
{
"media_list": [
{
"type": "video",
"url": "https://example.com/video.mp4"
}
],
"settings": {
"aspect_ratio": "16:9",
"quality": "high"
}
}Response
{
"success": true,
"message": "Video generation started",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"progress_url": "/api/function/video-generation/progress/550e8400-e29b-41d4-a716-446655440000"
}
}| Status | Response body | Description |
|---|---|---|
| 400 | {"success": false, "message": "Invalid JSON data"} | Invalid JSON in the request |
| 400 | {"success": false, "message": "Invalid payload shape"} | Request body or nested data value is not a JSON object |
| 400 | {"success": false, "message": "No media items provided"} | media_list is empty or missing |
| 400 | {"success": false, "message": "media_list[0].url must be a valid HTTP(S) URL"} | A media URL is missing, relative, private, or an unresolved workflow expression |
| 401 | {"success": false, "message": "API key required"} | API key is missing |
| 403 | {"success": false, "message": "Invalid API key"} | Invalid API key |
| 403 | {"success": false, "message": "Free tier limit reached..."} | Quota limit reached |
| 500 | {"success": false, "message": "Internal server error"} | Server error |
Code example: Python
import requests
import json
api_key = "YOUR_API_KEY"
url = "https://samautomation.work/api/function/video-generation/simple-video"
# Video data
data = {
"media_list": [
{
"type": "video",
"url": "https://example.com/video.mp4",
}
],
"settings": {
"aspect_ratio": "16:9",
"quality": "high"
}
}
# API request
response = requests.post(
url,
headers={
"X-API-Key": api_key,
"Content-Type": "application/json",
},
data=json.dumps(data)
)
# Process the result
result = response.json()
if result["success"]:
video_id = result["data"]["id"]
progress_url = result["data"]["progress_url"]
print(f"Video generation started. ID: {video_id}")
print(f"Check progress at: {progress_url}")
else:
print(f"Error: {result['message']}")Code example: JavaScript
const apiKey = "YOUR_API_KEY";
const url = "https://samautomation.work/api/function/video-generation/simple-video";
// Video data
const data = {
media_list: [
{
type: "video",
url: "https://example.com/video.mp4"
}
],
settings: {
aspect_ratio: "16:9",
quality: "high"
}
};
// API request
fetch(url, {
method: "POST",
headers: {
"X-API-Key": apiKey,
"Content-Type": "application/json"
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
const videoId = result.data.id;
const progressUrl = result.data.progress_url;
console.log(`Video generation started. ID: ${videoId}`);
console.log(`Check progress at: ${progressUrl}`);
} else {
console.error(`Error: ${result.message}`);
}
})
.catch(error => console.error("API call failed:", error));Loop Video Generation
Create looping videos with background and media files.
Endpoint
POST /api/function/video-generation/loop-video
| Parameter | Type | Required | Description |
|---|---|---|---|
X-API-Key (header) | string | Yes | Your API key |
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Must be LoopVideo |
data | object | Yes | Container for loop-video payload fields |
data.media_list | array | Yes | Array of media items used in the loop video |
data.background_url | string | Yes | URL to a background image or video |
data.duration | number | No | Optional output duration in seconds (must be positive) |
data.settings | object | No | Video settings such as aspect_ratio and quality |
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | URL to the media file |
type | string | Yes | Media type ('image', 'video', 'audio') |
duration | number | Yes | Duration in seconds to display this element |
start_time | number | No | Start time within media file (for video/audio) |
end_time | number | No | End time within media file (for video/audio) |
text_overlay | string | No | Text to overlay on the media |
| Parameter | Type | Default | Description |
|---|---|---|---|
aspect_ratio | string | "16:9" | Aspect ratio of the video ("16:9", "1:1", "9:16") |
resolution | string | "720p" | Resolution of the video ("480p", "720p", "1080p") |
fps | number | 30 | Frames per second |
quality | string | "medium" | Quality of the video ("low", "medium", "high") |
Example request
{
"type": "LoopVideo",
"data": {
"media_list": [
{
"url": "https://example.com/image1.jpg",
"type": "image",
"duration": 5
}
],
"background_url": "https://example.com/background.jpg",
"duration": 15,
"settings": {
"aspect_ratio": "16:9",
"resolution": "720p"
}
}
}Response
{
"success": true,
"message": "Video generation started",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"celery_task_id": "c8a67f60-2e65-4fa1-9be2-1ffaf8b6f9c5"
}
}| Status | Response body | Description |
|---|---|---|
| 400 | {"success": false, "message": "Invalid JSON data"} | Invalid JSON in the request |
| 400 | {"success": false, "message": "Invalid video type. Only 'LoopVideo' is supported."} | type is missing or not LoopVideo |
| 400 | {"success": false, "message": "Background URL is required"} | data.background_url is missing |
| 401 | {"success": false, "message": "API key required"} | API key is missing |
| 403 | {"success": false, "message": "Invalid API key"} | Invalid API key |
| 403 | {"success": false, "message": "Free tier limit reached..."} | Quota limit reached |
| 500 | {"success": false, "message": "Internal server error"} | Server error |
Code example: Python
import requests
import json
api_key = "YOUR_API_KEY"
url = "https://samautomation.work/api/function/video-generation/loop-video"
# Video data
data = {
"type": "LoopVideo",
"data": {
"media_list": [
{
"url": "https://example.com/image1.jpg",
"type": "image",
"duration": 5
}
],
"background_url": "https://example.com/background.jpg",
"duration": 15,
"settings": {
"aspect_ratio": "16:9",
"resolution": "720p"
}
}
}
# API request
response = requests.post(
url,
headers={
"X-API-Key": api_key,
"Content-Type": "application/json",
},
data=json.dumps(data)
)
# Process the result
result = response.json()
if result["success"]:
video_id = result["data"]["id"]
progress_url = f"/api/function/video-generation/progress/{video_id}"
print(f"Video generation started. ID: {video_id}")
print(f"Check progress at: {progress_url}")
else:
print(f"Error: {result['message']}")Code example: JavaScript
const apiKey = "YOUR_API_KEY";
const url = "https://samautomation.work/api/function/video-generation/loop-video";
// Video data
const data = {
type: "LoopVideo",
data: {
media_list: [
{
url: "https://example.com/image1.jpg",
type: "image",
duration: 5
}
],
background_url: "https://example.com/background.jpg",
duration: 15,
settings: {
aspect_ratio: "16:9",
resolution: "720p"
}
}
};
// API request
fetch(url, {
method: "POST",
headers: {
"X-API-Key": apiKey,
"Content-Type": "application/json"
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
const videoId = result.data.id;
const progressUrl = `/api/function/video-generation/progress/${videoId}`;
console.log(`Video generation started. ID: ${videoId}`);
console.log(`Check progress at: ${progressUrl}`);
} else {
console.error(`Error: ${result.message}`);
}
})
.catch(error => console.error("API call failed:", error));Task Status Details
Understanding video task statuses:
| Status | Description |
|---|---|
pending | Task is queued for processing |
processing | Video is being generated |
completed | Video is ready for download |
failed | Task failed with error |
cancelled | Task was cancelled by user |
timeout | Task timed out after inactivity |
Advanced Settings
Customize your video generation with these advanced settings parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
aspect_ratio | string | "16:9" | Sets the aspect ratio of the video. Options: "16:9" (landscape), "9:16" (vertical/shorts), "1:1" (square) |
resolution | string | "720p" | Sets the resolution quality. Options: "480p", "720p", "1080p" |
fps | number | 24 | Frames per second for the video |
| Parameter | Type | Default | Description |
|---|---|---|---|
background_volume | number | 0.7 | Background music volume level (0.0-1.0). Lower values make background music quieter. |
voice_volume | number | 1.0 | Voice-over volume level (0.0-1.0). The API automatically boosts voice volume by 20%. |
| Parameter | Type | Default | Description |
|---|---|---|---|
disable_captions | boolean | false | When set to true, disables caption display while preserving timing information |
caption_style | string | null | Caption style. Set to "impact" to use Impact font with dynamic styling |
font | string | "bangers" | Font to use for captions. Options: "bangers", "mandarin", "montserrat", "montserrat-italic", or fallback to "Impact" |
font_size | number | 110 | Font size for captions (60-200). Automatically scales down if text is too wide. |
text_color | string | "white" | Color of caption text. Use color names ("white", "yellow", etc.) or hex codes. |
stroke_color | string | "black" | Color of the text outline/stroke. Use color names or hex codes. |
stroke_width | number | 10 | Width of the text outline/stroke (1-10). Higher values create thicker outlines. |
Example settings object
{
"settings": {
"aspect_ratio": "9:16",
"background_volume": 0.3,
"voice_volume": 1.0,
"font": "montserrat",
"font_size": 120,
"text_color": "yellow",
"stroke_color": "black",
"stroke_width": 5,
"caption_style": "impact",
"disable_captions": false
}
}Image animation
Images automatically receive a subtle zoom effect alternating between zoom-in and zoom-out based on their position in the media list:
- Even-indexed images: Zoom out (1.1x → 1.0x)
- Odd-indexed images: Zoom in (1.0x → 1.1x)
Video durations
The system handles video durations as follows:
- When not specified, videos use their actual duration.
- The last video automatically gets a 2-second freeze frame of its last frame added.
- Images get 6 seconds by default (8 seconds for the last image).
Caption chunking
Captions longer than 3 words are automatically split into chunks of 3 words with alternating colors (white-white-yellow pattern) for better readability.
Audio handling
When both voice-over and background audio are present:
- Background music is automatically ducked to 20% volume during voice sections.
- Fade-in and fade-out effects are applied to background music.
- If background music is shorter than the video, it loops automatically.
Video Support
You can use videos in your projects alongside images. Mix images and videos to make richer content.
Special behavior — last-clip freeze frame
When you place a video at the very end of your media list, the last frame of that video is held on screen for 2 extra seconds — a clean visual "end card" without extra text.
How to use videos
- Pick a video you want to include.
- Add it to your media list with
type: "video". - The video plays for exactly its own length (its "original duration").
- If it is the last video in your list, the last frame is shown for 2 extra seconds.
Example: mixing images and videos
{
"type": "video",
"data": {
"settings": {
"aspect_ratio": "9:16"
},
"media_list": [
{
"type": "image",
"url": "https://example.com/my_picture.jpg",
},
{
"type": "video",
"url": "https://example.com/my_video.mp4"
// No need for duration! We use the video's actual length
},
{
"type": "video",
"url": "https://example.com/my_last_video.mp4"
// This is the last video, so its last frame will stay for 2 extra seconds!
}
]
}
}- Use short videos (5-15 seconds) for the best results.
- Make sure your videos match your images in style and colors.
- Put your most important video at the end to get that special 2-second freeze frame.
- Videos with no sound work best (the API uses your voice-over and music).
Audio Options
Configure audio settings for your video generation task:
{
"voice_url": "https://example.com/voice.mp3",
"background_url": "https://example.com/music.mp3",
"settings": {
"background_volume": 0.3,
"voice_volume": 1.0
}
}Captions
Add captions to your video generation task:
{
"transcripts": [
{
"text": "Caption text here",
"start": 0,
"end": 5
}
]
}Example Requests
Example requests for video generation tasks:
Basic image to video
{
"data": {
"background_url": "https://cdn.example.com/background-music.mp3",
"voice_url": "https://cdn.example.com/voice-over.mp3",
"media_list": [
{
"type": "image",
"url": "https://cdn.example.com/product-frame.jpg",
"duration": 5
}
],
"transcripts": [
{"words": "Here are your words", "start": 0, "end": 0.88},
{"words": "Second caption line", "start": 0.88, "end": 1.76}
],
"settings": {
"aspect_ratio": "16:9",
"background_volume": 0.15,
"voice_volume": 1.0,
"font_size": 120,
"stroke_color": "green",
"stroke_width": 10
}
}
}Video generation process
Understanding the API workflow from request to finished file.
When you submit a video generation request, the API returns a task ID:
{
"success": true,
"message": "Video generation started",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"progress_url": "/api/function/video-generation/progress/550e8400-e29b-41d4-a716-446655440000"
}
}Use the task ID to check the progress:
curl -X GET "http://localhost:8000/api/function/video-generation/progress/550e8400-e29b-41d4-a716-446655440000" \ -H "X-API-Key: YOUR_API_KEY"
Response during processing:
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"progress": 0.45
}
}When processing is complete, the status endpoint returns:
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"progress": 1.0,
"output_url": "https://media.samautomation.work/videos/123/final.mp4"
}
}If an error occurs during processing:
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "failed",
"error": "Invalid media URL provided"
}
}- Poll the status endpoint every 5 seconds.
- Implement exponential backoff if needed.
- Cache the video URL once completed.
- Handle timeout scenarios (typical processing time: 30-60 seconds).
Subscription Management
Manage your subscription and video credits:
Available endpoints
# Buy video pack
POST /api/video-packs/{pack_id}/buy/
# Stripe webhook for payment processing
POST /api/video-packs/webhook/
# List recent tasks
GET /api/function/video-generation/tasks
# Summarize usage for the last 30 days
GET /api/function/video-generation/usage-summary?days=30Subscription response example
{
"subscription": {
"plan": "Pro",
"active": true,
"videos_remaining": 45,
"period_reset_date": "2024-04-01T00:00:00Z",
"extra_credits": 10
}
}