curl -X POST "https://api.submagic.co/v1/projects/upload" \
-H "x-api-key: sk-your-api-key-here" \
-F "title=My Uploaded Video" \
-F "language=en" \
-F "file=@./video.mp4" \
-F "templateName=Hormozi 2" \
-F 'items=[{"type":"user-media","startTime":5,"endTime":10,"userMediaId":"123e4567-e89b-12d3-a456-426614174000","layout":"pip-top-right"},{"type":"ai-broll","startTime":15,"endTime":21,"prompt":"drone shot of a modern city skyline at dusk","layout":"split-50-50"}]' \
-F "webhookUrl=https://yoursite.com/webhook/submagic" \
-F 'dictionary=["Submagic", "AI-powered", "captions"]' \
-F "magicZooms=true" \
-F "magicBrolls=true" \
-F "magicBrollsPercentage=75" \
-F "removeSilencePace=fast" \
-F "removeBadTakes=true" \
-F "cleanAudio=true" \
-F 'hookTitle={"text":"Stop scrolling—watch this in 30 seconds","template":"tiktok","top":45,"size":32}' \
-F 'music={"userMediaId":"88a08eec-712a-45d0-8d0b-3b631700cb3a","volume":30,"startFromTime":0,"fade":true}'
const uploadProject = async (file) => {
const formData = new FormData();
formData.append("title", "My Uploaded Video");
formData.append("language", "en");
formData.append("file", file);
formData.append("templateName", "Hormozi 2");
formData.append(
"items",
JSON.stringify([
{
type: "user-media",
startTime: 5,
endTime: 10,
userMediaId: "123e4567-e89b-12d3-a456-426614174000",
layout: "pip-top-right",
},
{
type: "ai-broll",
startTime: 15,
endTime: 21,
prompt: "drone shot of a modern city skyline at dusk",
layout: "split-50-50",
},
])
);
formData.append("webhookUrl", "https://yoursite.com/webhook/submagic");
formData.append(
"dictionary",
JSON.stringify(["Submagic", "AI-powered", "captions"])
);
formData.append("magicZooms", "true");
formData.append("magicBrolls", "true");
formData.append("magicBrollsPercentage", "75");
formData.append("removeSilencePace", "fast");
formData.append("removeBadTakes", "true");
formData.append("cleanAudio", "true");
formData.append(
"hookTitle",
JSON.stringify({
text: "Stop scrolling—watch this in 30 seconds",
template: "tiktok",
top: 45,
size: 32,
})
);
formData.append(
"music",
JSON.stringify({
userMediaId: "88a08eec-712a-45d0-8d0b-3b631700cb3a",
volume: 30,
startFromTime: 0,
fade: true,
})
);
const response = await fetch("https://api.submagic.co/v1/projects/upload", {
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
},
body: formData,
});
const project = await response.json();
console.log("Project uploaded:", project.id);
return project;
};
// Usage with file input
const fileInput = document.getElementById("video-file");
fileInput.addEventListener("change", async (event) => {
const file = event.target.files[0];
if (file) {
await uploadProject(file);
}
});
import json
import requests
def upload_project(file_path):
url = 'https://api.submagic.co/v1/projects/upload'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
with open(file_path, 'rb') as video_file:
files = {
'file': video_file
}
data = {
'title': 'My Uploaded Video',
'language': 'en',
'templateName': 'Hormozi 2',
'items': json.dumps([
{
'type': 'user-media',
'startTime': 5,
'endTime': 10,
'userMediaId': '123e4567-e89b-12d3-a456-426614174000',
'layout': 'pip-top-right'
},
{
'type': 'ai-broll',
'startTime': 15,
'endTime': 21,
'prompt': 'drone shot of a modern city skyline at dusk',
'layout': 'split-50-50'
}
]),
'webhookUrl': 'https://yoursite.com/webhook/submagic',
'dictionary': '["Submagic", "AI-powered", "captions"]',
'magicZooms': 'true',
'magicBrolls': 'true',
'magicBrollsPercentage': '75',
'removeSilencePace': 'fast',
'removeBadTakes': 'true',
'cleanAudio': 'true',
'hookTitle': json.dumps({
'text': 'Stop scrolling—watch this in 30 seconds',
'template': 'tiktok',
'top': 45,
'size': 32
}),
'music': json.dumps({
'userMediaId': '88a08eec-712a-45d0-8d0b-3b631700cb3a',
'volume': 30,
'startFromTime': 0,
'fade': True
})
}
response = requests.post(url, headers=headers, files=files, data=data)
project = response.json()
print(f"Project uploaded: {project['id']}")
return project
# Usage
project = upload_project('./video.mp4')
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Uploaded Video",
"language": "en",
"status": "processing",
"webhookUrl": "https://yoursite.com/webhook/submagic",
"templateName": "Hormozi 2",
"magicZooms": true,
"magicBrolls": true,
"magicBrollsPercentage": 75,
"removeSilencePace": "fast",
"removeBadTakes": true,
"cleanAudio": true,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
API Reference
Upload Project
Create a new video project by uploading a video file directly for AI-powered caption generation
POST
/
v1
/
projects
/
upload
curl -X POST "https://api.submagic.co/v1/projects/upload" \
-H "x-api-key: sk-your-api-key-here" \
-F "title=My Uploaded Video" \
-F "language=en" \
-F "file=@./video.mp4" \
-F "templateName=Hormozi 2" \
-F 'items=[{"type":"user-media","startTime":5,"endTime":10,"userMediaId":"123e4567-e89b-12d3-a456-426614174000","layout":"pip-top-right"},{"type":"ai-broll","startTime":15,"endTime":21,"prompt":"drone shot of a modern city skyline at dusk","layout":"split-50-50"}]' \
-F "webhookUrl=https://yoursite.com/webhook/submagic" \
-F 'dictionary=["Submagic", "AI-powered", "captions"]' \
-F "magicZooms=true" \
-F "magicBrolls=true" \
-F "magicBrollsPercentage=75" \
-F "removeSilencePace=fast" \
-F "removeBadTakes=true" \
-F "cleanAudio=true" \
-F 'hookTitle={"text":"Stop scrolling—watch this in 30 seconds","template":"tiktok","top":45,"size":32}' \
-F 'music={"userMediaId":"88a08eec-712a-45d0-8d0b-3b631700cb3a","volume":30,"startFromTime":0,"fade":true}'
const uploadProject = async (file) => {
const formData = new FormData();
formData.append("title", "My Uploaded Video");
formData.append("language", "en");
formData.append("file", file);
formData.append("templateName", "Hormozi 2");
formData.append(
"items",
JSON.stringify([
{
type: "user-media",
startTime: 5,
endTime: 10,
userMediaId: "123e4567-e89b-12d3-a456-426614174000",
layout: "pip-top-right",
},
{
type: "ai-broll",
startTime: 15,
endTime: 21,
prompt: "drone shot of a modern city skyline at dusk",
layout: "split-50-50",
},
])
);
formData.append("webhookUrl", "https://yoursite.com/webhook/submagic");
formData.append(
"dictionary",
JSON.stringify(["Submagic", "AI-powered", "captions"])
);
formData.append("magicZooms", "true");
formData.append("magicBrolls", "true");
formData.append("magicBrollsPercentage", "75");
formData.append("removeSilencePace", "fast");
formData.append("removeBadTakes", "true");
formData.append("cleanAudio", "true");
formData.append(
"hookTitle",
JSON.stringify({
text: "Stop scrolling—watch this in 30 seconds",
template: "tiktok",
top: 45,
size: 32,
})
);
formData.append(
"music",
JSON.stringify({
userMediaId: "88a08eec-712a-45d0-8d0b-3b631700cb3a",
volume: 30,
startFromTime: 0,
fade: true,
})
);
const response = await fetch("https://api.submagic.co/v1/projects/upload", {
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
},
body: formData,
});
const project = await response.json();
console.log("Project uploaded:", project.id);
return project;
};
// Usage with file input
const fileInput = document.getElementById("video-file");
fileInput.addEventListener("change", async (event) => {
const file = event.target.files[0];
if (file) {
await uploadProject(file);
}
});
import json
import requests
def upload_project(file_path):
url = 'https://api.submagic.co/v1/projects/upload'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
with open(file_path, 'rb') as video_file:
files = {
'file': video_file
}
data = {
'title': 'My Uploaded Video',
'language': 'en',
'templateName': 'Hormozi 2',
'items': json.dumps([
{
'type': 'user-media',
'startTime': 5,
'endTime': 10,
'userMediaId': '123e4567-e89b-12d3-a456-426614174000',
'layout': 'pip-top-right'
},
{
'type': 'ai-broll',
'startTime': 15,
'endTime': 21,
'prompt': 'drone shot of a modern city skyline at dusk',
'layout': 'split-50-50'
}
]),
'webhookUrl': 'https://yoursite.com/webhook/submagic',
'dictionary': '["Submagic", "AI-powered", "captions"]',
'magicZooms': 'true',
'magicBrolls': 'true',
'magicBrollsPercentage': '75',
'removeSilencePace': 'fast',
'removeBadTakes': 'true',
'cleanAudio': 'true',
'hookTitle': json.dumps({
'text': 'Stop scrolling—watch this in 30 seconds',
'template': 'tiktok',
'top': 45,
'size': 32
}),
'music': json.dumps({
'userMediaId': '88a08eec-712a-45d0-8d0b-3b631700cb3a',
'volume': 30,
'startFromTime': 0,
'fade': True
})
}
response = requests.post(url, headers=headers, files=files, data=data)
project = response.json()
print(f"Project uploaded: {project['id']}")
return project
# Usage
project = upload_project('./video.mp4')
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Uploaded Video",
"language": "en",
"status": "processing",
"webhookUrl": "https://yoursite.com/webhook/submagic",
"templateName": "Hormozi 2",
"magicZooms": true,
"magicBrolls": true,
"magicBrollsPercentage": 75,
"removeSilencePace": "fast",
"removeBadTakes": true,
"cleanAudio": true,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
Upload Project
Create a new video project by uploading a video file directly to Submagic. This endpoint accepts multipart/form-data uploads and is ideal for applications where you have video files stored locally or want to upload directly from user devices.This endpoint requires authentication and has a rate limit of 500 requests per
hour due to the resource-intensive nature of file uploads.
Authentication
string
required
Your Submagic API key starting with
sk-Request Body (multipart/form-data)
string
required
A descriptive title for your video project (1-100 characters)
string
required
Language code for transcription (e.g., “en”, “es”, “fr”). Use the languages
endpoint to get available options.
file
required
Video file to upload. Must be in a supported format and under 2GB.
string
Name of an AI edit template to apply. AI edit templates automatically apply
AI-powered scene splitting, B-roll, music, and styling to your video. Available templates:
"kelly" (minimal, design), "karl"
(effective, modern), "ella" (dynamic, bold).When aiEditTemplate is provided, the only other fields you can pass
alongside it are title, language, file, webhookUrl, and dictionary.
All other fields will be ignored or rejected.string
ID of a saved preset to apply to the project. A preset is a snapshot of your
project settings — including captions style, hook title, music, effects, and
more. When provided, all preset settings are automatically applied after
transcription completes. To get your preset ID, go to the Presets page in the
app, open the dropdown menu on any preset card, and click “Copy ID”.Cannot be combined with
templateName, userThemeId, hookTitle, music,
items, magicZooms, magicBrolls, magicBrollsPercentage,
removeSilencePace, removeBadTakes, captionPositionY, or
captionPositionX — the preset already controls these settings.string
Template to apply for styling. Use the templates
endpoint to get available options. Defaults to
“Sara” if not specified. Cannot be used together with
userThemeId.string
ID of a custom user theme to apply for styling. Must be a valid UUID of a
theme that belongs to you or your team. Cannot be used together with
templateName. You can find the id of your custom theme by opening a project,
selecting the theme, pressing the pen icon to edit it. You’ll see the id of
the theme under its name.string
Vertical caption position as a percentage from the top of the frame (0-80).
Pass as string, e.g.
"70". Overrides the position of the selected template
or theme. Cannot be combined with presetId or aiEditTemplate.string
Horizontal caption position as a percentage (0-100,
"50" = centered). Pass
as string. Overrides the position of the selected template or theme. Cannot
be combined with presetId or aiEditTemplate.string
Adds an animated hook caption. Pass
"true" to enable the default hook, or
pass a JSON string such as
{"text":"Stop scrolling—watch this in 30 seconds","template":"tiktok","top":45,"size":32}.text: Optional custom copy (1-100 characters)template: Optional template name (defaults to"tiktok"). Use the hook title templates endpoint to discover valid names.top: Optional vertical position between 0-80 (default50)size: Optional font size between 0-80 (default30)
VALIDATION_ERROR.string
URL to receive webhook notifications when processing is complete. Must be a
valid HTTPS URL.
string
JSON array string of custom words or phrases to improve transcription accuracy
(max 100 items, 50 characters each).
string
JSON string describing optional items to insert into the video. Pass an
array of objects. Each item must include a
Important: Each item must have a
type field to specify whether it’s
user media from your library or AI-generated content.Hide User Media Item JSON
Hide User Media Item JSON
string
required
Must equal
"user-media"number
required
Start time in seconds where the media should begin (≥ 0)
number
required
End time in seconds where the media should end (must be greater than
startTime)string
required
UUID of the user media from your library. You can find this ID in the
editor’s ‘B-roll’ tab → ‘My videos’ section under each video.
string
Layout mode for the inserted media. Available values depend on the media type:Video media:
"cover", "contain", "rounded", "square", "split-50-50", "split-35-65", "split-50-50-bordered", "split-35-65-bordered", "pip-top-right", "pip-bottom-right"Image media: "cover", "contain", "rounded", "square"Hide AI B-roll Item JSON
Hide AI B-roll Item JSON
string
required
Must equal
"ai-broll"number
required
Start timestamp in seconds (≥ 0)
number
required
End timestamp in seconds; must be greater than
startTime and within a
12-second windowstring
required
1-2500 character description of the B-roll to generate
string
Layout mode for the AI B-roll. Allowed values:
"cover", "contain", "rounded", "square", "split-50-50", "split-35-65", "split-50-50-bordered", "split-35-65-bordered", "pip-top-right", "pip-bottom-right"type field. Items entries cannot overlap
in time. When present, this metadata is parsed after the upload completes and
queued for rendering.string
Enable automatic zoom effects on the video to enhance visual engagement. Pass
“true” or “false” as string. Optional, defaults to “false”.
string
Enable automatic B-roll insertion to enhance video content with relevant
supplementary footage. Pass “true” or “false” as string. Optional, defaults to
“false”.
string
Percentage of automatic B-rolls to include in the video (0-100). Pass as
string. Only effective when magicBrolls is enabled. Optional, defaults to
“50”.
string
Automatically remove silence from the video at the specified pace. Pass as
string. Optional. Allowed values: “natural”, “fast”, “extra-fast”. -
“extra-fast”: 0.1-0.2 seconds of silence removal - “fast”: 0.2-0.6 seconds of
silence removal - “natural”: 0.6+ seconds of silence removal
string
Automatically detect and remove bad takes and silence from the video using AI
analysis. Pass “true” or “false” as string. Optional, defaults to “false”.
string
Enable AI-powered audio cleanup that removes background noises from the video.
Pass “true” or “false” as string. Optional, defaults to “false”.
string
Hide captions from the exported video. Pass
"true" or "false" as
string. Optional, defaults to "false".string
JSON string describing an optional background music track that spans the full
project duration.
Hide Music Object JSON
Hide Music Object JSON
string
required
UUID of the music track. Accepts either an
AUDIO file from your media
library (use List User Media) or a Trendy
Music track ID (copied from the editor’s Edit Audio → Trendy Music tab).number
required
Playback volume (1-100)
number
Start offset within the audio file in seconds. Defaults to
0.boolean
Apply a fade-in/fade-out effect to the music track. Defaults to
true.Supported Formats & Limits
Supported Formats
- MP4 (.mp4) - MOV (.mov)
File Limits
- Max size: 2GB - Max duration: 2 hours
curl -X POST "https://api.submagic.co/v1/projects/upload" \
-H "x-api-key: sk-your-api-key-here" \
-F "title=My Uploaded Video" \
-F "language=en" \
-F "file=@./video.mp4" \
-F "templateName=Hormozi 2" \
-F 'items=[{"type":"user-media","startTime":5,"endTime":10,"userMediaId":"123e4567-e89b-12d3-a456-426614174000","layout":"pip-top-right"},{"type":"ai-broll","startTime":15,"endTime":21,"prompt":"drone shot of a modern city skyline at dusk","layout":"split-50-50"}]' \
-F "webhookUrl=https://yoursite.com/webhook/submagic" \
-F 'dictionary=["Submagic", "AI-powered", "captions"]' \
-F "magicZooms=true" \
-F "magicBrolls=true" \
-F "magicBrollsPercentage=75" \
-F "removeSilencePace=fast" \
-F "removeBadTakes=true" \
-F "cleanAudio=true" \
-F 'hookTitle={"text":"Stop scrolling—watch this in 30 seconds","template":"tiktok","top":45,"size":32}' \
-F 'music={"userMediaId":"88a08eec-712a-45d0-8d0b-3b631700cb3a","volume":30,"startFromTime":0,"fade":true}'
const uploadProject = async (file) => {
const formData = new FormData();
formData.append("title", "My Uploaded Video");
formData.append("language", "en");
formData.append("file", file);
formData.append("templateName", "Hormozi 2");
formData.append(
"items",
JSON.stringify([
{
type: "user-media",
startTime: 5,
endTime: 10,
userMediaId: "123e4567-e89b-12d3-a456-426614174000",
layout: "pip-top-right",
},
{
type: "ai-broll",
startTime: 15,
endTime: 21,
prompt: "drone shot of a modern city skyline at dusk",
layout: "split-50-50",
},
])
);
formData.append("webhookUrl", "https://yoursite.com/webhook/submagic");
formData.append(
"dictionary",
JSON.stringify(["Submagic", "AI-powered", "captions"])
);
formData.append("magicZooms", "true");
formData.append("magicBrolls", "true");
formData.append("magicBrollsPercentage", "75");
formData.append("removeSilencePace", "fast");
formData.append("removeBadTakes", "true");
formData.append("cleanAudio", "true");
formData.append(
"hookTitle",
JSON.stringify({
text: "Stop scrolling—watch this in 30 seconds",
template: "tiktok",
top: 45,
size: 32,
})
);
formData.append(
"music",
JSON.stringify({
userMediaId: "88a08eec-712a-45d0-8d0b-3b631700cb3a",
volume: 30,
startFromTime: 0,
fade: true,
})
);
const response = await fetch("https://api.submagic.co/v1/projects/upload", {
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
},
body: formData,
});
const project = await response.json();
console.log("Project uploaded:", project.id);
return project;
};
// Usage with file input
const fileInput = document.getElementById("video-file");
fileInput.addEventListener("change", async (event) => {
const file = event.target.files[0];
if (file) {
await uploadProject(file);
}
});
import json
import requests
def upload_project(file_path):
url = 'https://api.submagic.co/v1/projects/upload'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
with open(file_path, 'rb') as video_file:
files = {
'file': video_file
}
data = {
'title': 'My Uploaded Video',
'language': 'en',
'templateName': 'Hormozi 2',
'items': json.dumps([
{
'type': 'user-media',
'startTime': 5,
'endTime': 10,
'userMediaId': '123e4567-e89b-12d3-a456-426614174000',
'layout': 'pip-top-right'
},
{
'type': 'ai-broll',
'startTime': 15,
'endTime': 21,
'prompt': 'drone shot of a modern city skyline at dusk',
'layout': 'split-50-50'
}
]),
'webhookUrl': 'https://yoursite.com/webhook/submagic',
'dictionary': '["Submagic", "AI-powered", "captions"]',
'magicZooms': 'true',
'magicBrolls': 'true',
'magicBrollsPercentage': '75',
'removeSilencePace': 'fast',
'removeBadTakes': 'true',
'cleanAudio': 'true',
'hookTitle': json.dumps({
'text': 'Stop scrolling—watch this in 30 seconds',
'template': 'tiktok',
'top': 45,
'size': 32
}),
'music': json.dumps({
'userMediaId': '88a08eec-712a-45d0-8d0b-3b631700cb3a',
'volume': 30,
'startFromTime': 0,
'fade': True
})
}
response = requests.post(url, headers=headers, files=files, data=data)
project = response.json()
print(f"Project uploaded: {project['id']}")
return project
# Usage
project = upload_project('./video.mp4')
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Uploaded Video",
"language": "en",
"status": "processing",
"webhookUrl": "https://yoursite.com/webhook/submagic",
"templateName": "Hormozi 2",
"magicZooms": true,
"magicBrolls": true,
"magicBrollsPercentage": 75,
"removeSilencePace": "fast",
"removeBadTakes": true,
"cleanAudio": true,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
Error Responses
object
{
"error": "VALIDATION_ERROR",
"message": "File validation failed",
"details": [
{
"field": "file",
"message": "File size exceeds 10GB limit",
"value": null
}
]
}
object
{
"error": "PAYLOAD_TOO_LARGE",
"message": "File size exceeds maximum allowed size"
}
object
{
"error": "UNSUPPORTED_MEDIA_TYPE",
"message": "Video format not supported"
}
object
{
"error": "VALIDATION_ERROR",
"message": "presetId cannot be combined with templateName, userThemeId, aiEditTemplate, magicZooms, magicBrolls, magicBrollsPercentage, removeBadTakes, removeSilencePace, items, hookTitle, music, captionPositionY, or captionPositionX. The preset controls these settings."
}
object
{
"error": "VALIDATION_ERROR",
"message": "Invalid template name"
}

