# Create Project
Source: https://docs.submagic.co/api-reference/create-project
POST https://api.submagic.co/v1/projects
Create a new video project using a video URL for AI-powered caption generation
# Create Project
Create a new video project by providing a video URL. This endpoint will download the video, transcribe the audio, and apply the specified template with AI-generated captions and effects.
This endpoint requires authentication and has a rate limit of 500 requests per
hour.
## Authentication
Your Submagic API key starting with `sk-`
## Request Body
A descriptive title for your video project (1-100 characters)
Language code for transcription (e.g., "en", "es", "fr"). Use the [languages
endpoint](/api-reference/languages) to get available options.
Public URL to your video file. Must be accessible without authentication and
in a supported format.
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`, `videoUrl`, `webhookUrl`, and
`dictionary`. All other fields will be ignored or rejected.
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.
Template to apply for styling. Use the [templates
endpoint](/api-reference/templates) to get available options. Defaults to
"Sara" if not specified. Cannot be used together with `userThemeId`.
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 theme's name.
Vertical caption position as a percentage from the top of the frame (0-80).
Overrides the position of the selected template or theme. Cannot be combined
with `presetId` or `aiEditTemplate`.
Horizontal caption position as a percentage (0-100, `50` = centered).
Overrides the position of the selected template or theme. Cannot be combined
with `presetId` or `aiEditTemplate`.
Adds an animated opening caption (hook) to the video. Set to `true` to let AI
generate using the default style or pass an object to customize the
appearance:
* `text` (optional): Custom hook, 1-100 characters
* `template` (optional): Hook title template name. Defaults to `"tiktok"`.
Use the [hook title templates
endpoint](/api-reference/hook-title-templates) to retrieve valid options.
* `top` (optional): Vertical position (0-80). Defaults to `50`.
* `size` (optional): Font size (0-80). Defaults to `30`.
URL to receive webhook notifications when processing is complete. Must be a
valid HTTPS URL.
Array of custom words or phrases to improve transcription accuracy (max 100
items, 50 characters each).
Optional array of items to insert into the video. Each item must include
a `type` field to specify whether it's user media from your library or
AI-generated content.
Must be set to `"user-media"`
Start time in seconds where the media should begin (must be ≥ 0)
End time in seconds where the media should end (must be greater than
`startTime`)
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.
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"`
Must be set to `"ai-broll"`
Start timestamp in seconds (must be ≥ 0)
End timestamp in seconds. Must be greater than `startTime` and no more than
12 seconds later.
Describes what the AI B-roll should depict (1-2500 characters)
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"`
**Important:** Each item must have a `type` field. Items cannot overlap with
each other in time. Requests that include invalid durations, overlapping
ranges, or prompts outside the allowed length will be rejected.
Enable automatic zoom effects on the video to enhance visual engagement.
Optional, defaults to false.
Enable automatic B-roll insertion to enhance video content with relevant
supplementary footage. Optional, defaults to false.
Percentage of automatic B-rolls to include in the video (0-100). Only
effective when magicBrolls is enabled. Optional, defaults to 50.
Automatically remove silence from the video at the specified pace. 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
Automatically detect and remove bad takes and silence from the video using AI
analysis. Optional, defaults to false.
Enable AI-powered audio cleanup that removes background noises from the video.
Optional, defaults to false.
Hide captions from the exported video. Optional, defaults to `false`.
Whether to automatically render the final video once transcription completes.
Optional, defaults to `true`.
Set to `false` to keep the project editable: after transcription the project
stays in `completed` status **without** a rendered video, and the `completed`
webhook fires so you know it's ready. You can then fetch the transcript with
[Get Project](/api-reference/get-project), edit the captions with
[Update Project](/api-reference/update-project) (the `words` field), and
trigger the render yourself with [Export Project](/api-reference/export-project).
Optional background music track that spans the full project duration.
UUID of the music track. Accepts either an `AUDIO` file from your media
library (use [List User Media](/api-reference/list-user-media)) or a Trendy
Music track ID (copied from the editor's **Edit Audio → Trendy Music** tab).
Playback volume (1-100)
Start offset within the audio file in seconds. Defaults to `0`.
Apply a fade-in/fade-out effect to the music track. Defaults to `true`.
## Supported Formats & Limits
* **MP4** (.mp4) - **MOV** (.mov)
* **Max size:** 2GB - **Max duration:** 2 hours
## Response
Unique identifier for the created project (UUID format)
The title you provided for the project
Language code used for transcription
Current processing status: `processing`, `transcribing`, `exporting`,
`completed`, or `failed`
Webhook URL if provided in the request
Template name applied to the project
User theme ID applied to the project
Preset ID applied to the project
AI edit template applied to the project (if any): `kelly`, `karl`, or `ella`
Whether automatic zoom effects are enabled for the video
Whether automatic B-roll insertion is enabled for the video
Percentage of automatic B-rolls to include in the video (0-100)
Pace setting for automatic silence removal: `natural`, `fast`, or `extra-fast`
Whether automatic bad takes and silence removal is enabled
Whether AI-powered audio cleanup is enabled
Vertical caption position override (0-80), echoed back when provided
Horizontal caption position override (0-100), echoed back when provided
ISO 8601 timestamp when the project was created
ISO 8601 timestamp when the project was last updated
```bash cURL theme={null}
curl -X POST "https://api.submagic.co/v1/projects" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"title": "My Awesome Video",
"language": "en",
"videoUrl": "https://example.com/videos/sample.mp4",
"templateName": "Hormozi 2",
"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": "person performing multiple back exercises on gym equipment",
"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": {
"text": "Stop scrolling—watch this in 30 seconds",
"template": "tiktok",
"top": 45,
"size": 32
},
"music": {
"userMediaId": "88a08eec-712a-45d0-8d0b-3b631700cb3a",
"volume": 30,
"startFromTime": 0,
"fade": true
}
}'
```
```javascript JavaScript theme={null}
const createProject = async () => {
const response = await fetch("https://api.submagic.co/v1/projects", {
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "My Awesome Video",
language: "en",
videoUrl: "https://example.com/videos/sample.mp4",
templateName: "Hormozi 2",
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:
"person performing multiple back exercises on gym equipment",
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: {
text: "Stop scrolling—watch this in 30 seconds",
template: "tiktok",
top: 45,
size: 32,
},
music: {
userMediaId: "88a08eec-712a-45d0-8d0b-3b631700cb3a",
volume: 30,
startFromTime: 0,
fade: true,
},
}),
});
const project = await response.json();
console.log("Project created:", project.id);
return project;
};
```
```python Python theme={null}
import requests
import json
def create_project():
url = 'https://api.submagic.co/v1/projects'
headers = {
'x-api-key': 'sk-your-api-key-here',
'Content-Type': 'application/json'
}
data = {
'title': 'My Awesome Video',
'language': 'en',
'videoUrl': 'https://example.com/videos/sample.mp4',
'templateName': 'Hormozi 2',
'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': 'person performing multiple back exercises on gym equipment',
'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': {
'text': 'Stop scrolling—watch this in 30 seconds',
'template': 'tiktok',
'top': 45,
'size': 32
},
'music': {
'userMediaId': '88a08eec-712a-45d0-8d0b-3b631700cb3a',
'volume': 30,
'startFromTime': 0,
'fade': True
}
}
response = requests.post(url, headers=headers, json=data)
project = response.json()
print(f"Project created: {project['id']}")
return project
```
```json 201 Created theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Awesome 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"
}
```
## Custom Dictionary
Improve transcription accuracy by providing custom terms:
```json theme={null}
{
"dictionary": [
"Submagic",
"API endpoint",
"captions",
"transcription",
"AI-powered",
"webhook notification"
]
}
```
**Best practices for dictionary terms:**
* Include brand names, product names, or technical terms
* Add words that are frequently mispronounced or misunderstood
* Keep terms under 50 characters each
* Limit to 100 terms per project
## Webhook Integration
Receive notifications when your project is complete:
```json theme={null}
{
"webhookUrl": "https://yoursite.com/webhook/submagic"
}
```
Your webhook endpoint will receive a POST request:
```json theme={null}
{
"projectId": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"downloadUrl": "https://app.submagic.co/api/file/download?path=3c6cd7cd-3f5e-4def-b662-69c48a7fc8ce/e568c322-7fa5-497a-8fb0-3ba32e9e67d2/364fe092-b68d-468a-9558-bfca7c4d130e-download.mp4&newFileName=ProMotion%20Display%20Breakthrough.mp4",
"directUrl": "https://dqu1p08d61fh.cloudfront.net/api/9cc52d00-43d7-442f-9a22-050312bkm24f/1ddee5b4-101f-4c1e-a74a-b6b3bcfe206c/video.mp4-download.mp4",
"timestamp": "2024-01-15T10:45:00.000Z"
}
```
## Error Responses
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "videoUrl",
"message": "Must be a valid URL",
"value": "invalid-url"
}
]
}
```
```json theme={null}
{
"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."
}
```
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Invalid template name"
}
```
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "URLs from social media platforms are not supported. Please use a direct download link."
}
```
Returned when `videoUrl` is a Google Drive folder or other Drive link that
doesn't point to a single file. Share a direct file link with access set to
"Anyone with the link".
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "This looks like a Google Drive folder or an unsupported Drive link. Please share a direct link to a single file (e.g. https://drive.google.com/file/d/FILE_ID/view) with access set to \"Anyone with the link\"."
}
```
Returned when `videoUrl` can't be reached or serves a web page instead of a
media file (e.g. a broken link, a login wall, or a share page rather than a
direct download).
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "The provided URL does not point to a downloadable media file. Please provide a direct download link to a video, audio, or image file."
}
```
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred"
}
```
# Export Project
Source: https://docs.submagic.co/api-reference/export-project
POST https://api.submagic.co/v1/projects/{id}/export
Trigger the rendering/export process for a completed project to generate the final video
# Export Project
Triggers the rendering/export process for a completed project. This starts the video generation process asynchronously with customizable output parameters.
This endpoint requires authentication and has enhanced rate limits for
API-generated projects. The export process is asynchronous - use webhooks or
polling to track completion.
## Authentication
Your Submagic API key starting with `sk-`
## Path Parameters
The unique identifier (UUID) of the project to export
## Request Body (Optional)
All parameters are optional. If not provided, the system uses optimal defaults based on the project's original video metadata.
Frames per second for the exported video (1-60). Defaults to project's
original fps or 30.
Video width in pixels (100-4000). Defaults to project's original width or
1080\.
Video height in pixels (100-4000). Defaults to project's original height or
1920\.
URL to receive notification when export is complete. Must be a valid URL
format.
## Prerequisites
Before exporting a project, ensure:
* **Project is transcribed**: Must have words data available
* **Project is not uploading**: Cannot be in "uploading" status
* **Project ownership**: Must belong to the authenticated user
* **API-generated project**: Must be created via API
## Response
Success message confirming the export has started
The unique identifier of the project being exported
Current status of the project after export trigger
## Error Responses
Error code: `NOT_FOUND`, `BAD_REQUEST`, or `INTERNAL_SERVER_ERROR`
Detailed error message explaining the issue
## Webhook Notifications
If you provide a `webhookUrl`, the system will send a POST request to your URL when export completes,
including export details and download URL in the notification.
## Export Status Tracking
After triggering an export:
1. **Monitor Progress**: Call `GET /v1/projects/{id}` to check export progress
2. **Check Download URL**: The `downloadUrl` and `directUrl` fields will be populated once rendering is complete
```bash cURL - Basic Export theme={null}
curl -X POST "https://api.submagic.co/v1/projects/550e8400-e29b-41d4-a716-446655440000/export" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json"
```
```bash cURL - Custom Parameters theme={null}
curl -X POST "https://api.submagic.co/v1/projects/550e8400-e29b-41d4-a716-446655440000/export" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"fps": 30,
"width": 1920,
"height": 1080,
"webhookUrl": "https://yoursite.com/webhook/export-complete"
}'
```
```javascript JavaScript - Basic Export theme={null}
const exportProject = async (projectId) => {
const response = await fetch(
`https://api.submagic.co/v1/projects/${projectId}/export`,
{
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
"Content-Type": "application/json",
},
}
);
const result = await response.json();
console.log("Export started:", result.message);
return result;
};
// Usage
const result = await exportProject("550e8400-e29b-41d4-a716-446655440000");
```
```javascript JavaScript - With Custom Parameters theme={null}
const exportProjectWithOptions = async (projectId, options = {}) => {
const response = await fetch(
`https://api.submagic.co/v1/projects/${projectId}/export`,
{
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify(options),
}
);
const result = await response.json();
console.log("Export started:", result.message);
return result;
};
// Usage with options
const exportOptions = {
fps: 30,
width: 1920,
height: 1080,
webhookUrl: "https://yoursite.com/webhook/export-complete",
};
const result = await exportProjectWithOptions(
"550e8400-e29b-41d4-a716-446655440000",
exportOptions
);
```
```python Python - Basic Export theme={null}
import requests
def export_project(project_id):
url = f'https://api.submagic.co/v1/projects/{project_id}/export'
headers = {
'x-api-key': 'sk-your-api-key-here',
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers)
result = response.json()
print(f"Export started: {result.get('message')}")
return result
# Usage
result = export_project('550e8400-e29b-41d4-a716-446655440000')
```
```python Python - With Custom Parameters theme={null}
import requests
import json
def export_project_with_options(project_id, options=None):
url = f'https://api.submagic.co/v1/projects/{project_id}/export'
headers = {
'x-api-key': 'sk-your-api-key-here',
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers, json=options)
result = response.json()
print(f"Export started: {result.get('message')}")
return result
# Usage with options
export_options = {
'fps': 30,
'width': 1920,
'height': 1080,
'webhookUrl': 'https://yoursite.com/webhook/export-complete'
}
result = export_project_with_options(
'550e8400-e29b-41d4-a716-446655440000',
export_options
)
```
```json 200 OK - Export Started theme={null}
{
"message": "Export started successfully",
"projectId": "550e8400-e29b-41d4-a716-446655440000",
"status": "exporting"
}
```
```json 404 NOT_FOUND - Project Not Found theme={null}
{
"error": "NOT_FOUND",
"message": "Project doesn't exist or doesn't belong to user"
}
```
```json 400 BAD_REQUEST - Project Not Ready theme={null}
{
"error": "BAD_REQUEST",
"message": "Project not ready for export"
}
```
```json 400 BAD_REQUEST - Invalid Parameters theme={null}
{
"error": "BAD_REQUEST",
"message": "Invalid fps value. Must be between 1 and 60"
}
```
```json 500 INTERNAL_SERVER_ERROR - Export Failed theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "Export failed to start"
}
```
**Tip**: After triggering an export, use the [Get
Project](/api-reference/get-project) endpoint to monitor the export progress.
The `downloadUrl` and `directUrl` fields will be populated once the rendering
is complete.
**Important**: The export process is asynchronous. The API will return
immediately after starting the export, but the actual video rendering happens
in the background. Use webhooks or polling to track completion status.
# Get Preset
Source: https://docs.submagic.co/api-reference/get-preset
GET https://api.submagic.co/v1/presets/{id}
Retrieve a single preset by its ID
# Get Preset
Retrieve a single preset from your library. Presets are created in the Submagic editor and bundle the settings and media (caption theme, hook, logo, music, and more) applied to new projects.
This endpoint requires authentication and has a rate limit of 1000 requests
per hour.
## Authentication
Your Submagic API key starting with `sk-`
## Path Parameters
The unique identifier (UUID) of the preset to retrieve
## Response
Unique identifier for the preset (UUID format)
Preset name
Preset description. Omitted when not set.
Whether projects created with this preset get its saved caption theme
Whether projects created with this preset get its saved hook title
Whether Magic Zooms are enabled
Whether AI Auto B-rolls are enabled
How much of the video AI Auto B-rolls cover, as a percentage (0-100). Omitted
when not set.
Whether AI Auto B-rolls use transition effects between clips. Omitted when not
set.
Whether projects created with this preset get its saved logo
Whether projects created with this preset get its saved color filter
Whether Clean Audio is enabled
Whether Eye Contact correction is enabled
Whether projects created with this preset get its saved background music
Background music volume, 1-100. Omitted when the preset has no saved music.
Volume applied to transition sounds, 0-100. Omitted when not set.
Which gated settings have saved config, i.e. which toggles can be enabled on
[Update Preset](/api-reference/update-preset).
Whether the preset has a saved caption theme
Whether the preset has a saved hook title
Whether the preset has a saved logo
Whether the preset has a saved color filter
Whether the preset has saved background music
ID of the project the preset was created from
ISO 8601 timestamp when the preset was created
ISO 8601 timestamp when the preset was last updated
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/presets/550e8400-e29b-41d4-a716-446655440000" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const getPreset = async (presetId) => {
const response = await fetch(
`https://api.submagic.co/v1/presets/${presetId}`,
{
headers: {
"x-api-key": "sk-your-api-key-here",
},
}
);
return response.json();
};
const preset = await getPreset("550e8400-e29b-41d4-a716-446655440000");
```
```python Python theme={null}
import requests
def get_preset(preset_id):
url = f'https://api.submagic.co/v1/presets/{preset_id}'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
response = requests.get(url, headers=headers)
return response.json()
preset = get_preset('550e8400-e29b-41d4-a716-446655440000')
```
```json 200 OK theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My TikTok Preset",
"description": "Punchy captions with background music",
"captions": true,
"hookTitle": true,
"magicZooms": true,
"magicBrolls": false,
"logo": false,
"colorFilter": false,
"cleanAudio": true,
"eyeContactCorrection": false,
"music": true,
"musicVolume": 25,
"configured": {
"captions": true,
"hookTitle": true,
"logo": false,
"colorFilter": false,
"music": true
},
"sourceProjectId": "88a08eec-712a-45d0-8d0b-3b631700cb3a",
"createdAt": "2024-01-10T08:00:00.000Z",
"updatedAt": "2024-01-12T09:30:00.000Z"
}
```
```json 404 NOT_FOUND theme={null}
{
"error": "NOT_FOUND",
"message": "Preset not found"
}
```
# Get Project
Source: https://docs.submagic.co/api-reference/get-project
GET https://api.submagic.co/v1/projects/{id}
Retrieve details of a specific video project including processing status and download links
# Get Project
Retrieve detailed information about a specific video project, including its current processing status, metadata, and download links when available.
This endpoint requires authentication and has a rate limit of 100 requests per
hour.
## Authentication
Your Submagic API key starting with `sk-`
## Path Parameters
The unique identifier (UUID) of the project to retrieve
## Response
Unique identifier of the project (UUID format)
The title of the project
AI description of the project
Language code used for transcription
Current processing status: `processing`, `transcribing`, `exporting`,
`completed`, or `failed`
Webhook URL if provided in the request
Template name applied to the project (if using a built-in template)
User theme ID applied to the project (if using a custom theme)
Preset ID applied to the project (if using a preset)
AI edit template applied to the project (if any): `kelly`, `karl`, or `ella`
Direct download URL for the processed video (available when status is
`completed`)
Direct URL that can be embedded on your website or used to play the video
directly (available when status is `completed`)
User-friendly preview page URL where the exported project can be previewed or
downloaded: `https://app.submagic.co/view/{projectId}` (available when status
is `completed`)
Current transcription status: `PROCESSING`, `COMPLETED`, or `FAILED`
Reason for failure if status is `failed`
Whether automatic zoom effects are enabled for the video
Whether automatic B-roll insertion is enabled for the video
Percentage of automatic B-rolls to include in the video (0-100)
Pace setting for automatic silence removal: `natural`, `fast`, or `extra-fast`
Whether automatic bad takes and silence removal is enabled
Whether automatic audio cleanup (noise removal) is enabled
Whether captions are disabled for the video
Video metadata extracted from the source
Video width in pixels (after any processing)
Video height in pixels (after any processing)
Original source video width in pixels
Original source video height in pixels
Video duration in seconds
Frames per second
Source file size in bytes
Video codec/format (e.g. `AVC`)
Container format(s) of the source file (e.g. `mov,mp4,m4a,3gp,3g2,mj2`)
Audio codec (e.g. `aac`)
Whether the source contains a video stream
Whether the source contains an audio stream
Whether the source video is HDR
Color space of the video (e.g. `bt709`)
Color transfer characteristics of the video (e.g. `bt709`)
ISO 8601 timestamp when the project was created
ISO 8601 timestamp when the project was last updated
Array of transcribed words and silence segments with timing information (available when transcription is completed)
Unique identifier for the word or silence segment
The transcribed text content (empty string for silence segments)
Type of segment: `word` for spoken words, `silence` for silent periods, or `punctuation` for punctuation marks
Start time of the word/silence in seconds
End time of the word/silence in seconds
Array of generated Magic Clips (only present for Magic Clips projects)
Unique identifier for the Magic Clip (UUID format)
AI-generated title for the clip
Brief AI explanation of why this clip has viral potential
Duration of the clip in seconds
Start time of the clip in the source video, in seconds
End time of the clip in the source video, in seconds
Object containing virality metrics for the clip (available when status is `completed`)
Overall virality score (0-100)
Shareability score (0-100)
Hook strength score (0-100)
Story quality score (0-100)
Emotional impact score (0-100)
Processing status of the clip: `processing`, `completed`, or `failed`
URL to preview the Magic Clip (available when status is `completed`)
Direct download URL for the Magic Clip (available when status is `completed`)
Direct URL that can be embedded on your website or used to play the Magic Clip directly (available when status is `completed`)
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/projects/550e8400-e29b-41d4-a716-446655440000" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const getProject = async (projectId) => {
const response = await fetch(
`https://api.submagic.co/v1/projects/${projectId}`,
{
method: "GET",
headers: {
"x-api-key": "sk-your-api-key-here",
},
}
);
const project = await response.json();
console.log("Project status:", project.status);
return project;
};
// Usage
const project = await getProject("550e8400-e29b-41d4-a716-446655440000");
```
```python Python theme={null}
import requests
def get_project(project_id):
url = f'https://api.submagic.co/v1/projects/{project_id}'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
response = requests.get(url, headers=headers)
project = response.json()
print(f"Project status: {project['status']}")
return project
# Usage
project = get_project('550e8400-e29b-41d4-a716-446655440000')
```
```json 200 OK - Transcribing theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Awesome Video",
"description": "AI description of the project",
"language": "en",
"status": "transcribing",
"webhookUrl": "https://yoursite.com/webhook/submagic",
"templateName": "Hormozi 2",
"transcriptionStatus": "PROCESSING",
"magicZooms": true,
"magicBrolls": true,
"magicBrollsPercentage": 75,
"removeSilencePace": "fast",
"removeBadTakes": true,
"videoMetaData": {
"width": 1920,
"height": 1080,
"duration": 185.2,
"fps": 30
},
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:32:15.000Z"
}
```
```json 200 OK - Processing with Transcription Complete theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Awesome Video",
"description": "AI description of the project",
"language": "en",
"status": "processing",
"webhookUrl": "https://yoursite.com/webhook/submagic",
"templateName": "Hormozi 2",
"transcriptionStatus": "COMPLETED",
"magicZooms": true,
"magicBrolls": true,
"magicBrollsPercentage": 75,
"removeSilencePace": "fast",
"removeBadTakes": true,
"videoMetaData": {
"width": 1920,
"height": 1080,
"duration": 185.2,
"fps": 30
},
"words": [
{
"id": "silence_f9164d70-ac39-41cc-ae31-5b9de3093a2f",
"text": "",
"type": "silence",
"startTime": 0,
"endTime": 0.08
},
{
"id": "w1EydrN5",
"text": "Do",
"type": "word",
"startTime": 0.08,
"endTime": 0.16
},
{
"id": "8VUdYfGN",
"text": "you",
"type": "word",
"startTime": 0.16,
"endTime": 0.24
},
{
"id": "eZBH24RB",
"text": "mind",
"type": "word",
"startTime": 0.24,
"endTime": 0.32
},
{
"id": "RFLElgda",
"text": "if",
"type": "word",
"startTime": 0.32,
"endTime": 0.4
},
{
"id": "iTWC5pc9",
"text": "we",
"type": "word",
"startTime": 0.4,
"endTime": 0.52
},
{
"id": "EpT23tSM",
"text": "do",
"type": "word",
"startTime": 0.52,
"endTime": 0.68
},
{
"id": "KtVZ8VDN",
"text": "something",
"type": "word",
"startTime": 0.68,
"endTime": 0.84
},
{
"id": "LtVZ8CM",
"text": "?",
"type": "punctuation",
"startTime": 0.84,
"endTime": 0.84
}
],
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:35:20.000Z"
}
```
```json 200 OK - Completed with Custom Theme theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Awesome Video",
"description": "AI description of the project",
"language": "en",
"status": "completed",
"webhookUrl": "https://yoursite.com/webhook/submagic",
"userThemeId": "730e9500-f29c-52e5-b826-556755550001",
"downloadUrl": "https://api.submagic.co/download/550e8400-e29b-41d4-a716-446655440000",
"directUrl": "https://dqu1p08d61fh.cloudfront.net/api/9cc52d00-43d7-442f-9a22-050312bkm24f/1ddee5b4-101f-4c1e-a74a-b6b3bcfe206c/video.mp4-download.mp4",
"previewUrl": "https://app.submagic.co/view/550e8400-e29b-41d4-a716-446655440000",
"transcriptionStatus": "COMPLETED",
"magicZooms": false,
"magicBrolls": true,
"magicBrollsPercentage": 30,
"removeSilencePace": "natural",
"removeBadTakes": false,
"cleanAudio": false,
"disableCaptions": false,
"videoMetaData": {
"fps": 30,
"isHDR": false,
"width": 1920,
"format": "AVC",
"height": 1080,
"duration": 185.2,
"fileSize": 3156305,
"hasAudio": true,
"hasVideo": true,
"audioCodec": "aac",
"colorSpace": "bt709",
"colorTransfer": "bt709",
"originalWidth": 1920,
"originalHeight": 1080,
"containerFormat": "mov,mp4,m4a,3gp,3g2,mj2"
},
"words": [
{
"id": "silence_f9164d70-ac39-41cc-ae31-5b9de3093a2f",
"text": "",
"type": "silence",
"startTime": 0,
"endTime": 0.08
},
{
"id": "w1EydrN5",
"text": "Do",
"type": "word",
"startTime": 0.08,
"endTime": 0.16
},
{
"id": "8VUdYfGN",
"text": "you",
"type": "word",
"startTime": 0.16,
"endTime": 0.24
},
{
"id": "eZBH24RB",
"text": "mind",
"type": "word",
"startTime": 0.24,
"endTime": 0.32
},
{
"id": "RFLElgda",
"text": "if",
"type": "word",
"startTime": 0.32,
"endTime": 0.4
},
{
"id": "iTWC5pc9",
"text": "we",
"type": "word",
"startTime": 0.4,
"endTime": 0.52
},
{
"id": "EpT23tSM",
"text": "do",
"type": "word",
"startTime": 0.52,
"endTime": 0.68
},
{
"id": "KtVZ8VDN",
"text": "something",
"type": "word",
"startTime": 0.68,
"endTime": 0.84
},
{
"id": "LtVZ8CM",
"text": "?",
"type": "punctuation",
"startTime": 0.84,
"endTime": 0.84
}
],
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:45:30.000Z"
}
```
```json 200 OK - Failed theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Awesome Video",
"description": "AI description of the project",
"language": "en",
"status": "failed",
"webhookUrl": "https://yoursite.com/webhook/submagic",
"templateName": "Hormozi 2",
"transcriptionStatus": "FAILED",
"failureReason": "Video format not supported or file corrupted",
"magicZooms": true,
"magicBrolls": false,
"magicBrollsPercentage": 50,
"removeSilencePace": null,
"removeBadTakes": false,
"videoMetaData": {
"width": 1920,
"height": 1080,
"duration": 185.2,
"fps": 30
},
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:33:45.000Z"
}
```
```json 200 OK - Magic Clips Project Completed theme={null}
{
"id": "e568c322-7fa5-497a-8fb0-3ba32e9e67d2",
"title": "YT magic-clips test1",
"description": "AI description of the project",
"language": "en",
"status": "completed",
"webhookUrl": "https://webhook-test.com/6704c8535a2a913c3d094aba685d6a19",
"previewUrl": "https://app.submagic.co/view/e568c322-7fa5-497a-8fb0-3ba32e9e67d3",
"transcriptionStatus": "COMPLETED",
"videoMetaData": {
"width": 1920,
"height": 1080,
"duration": 283,
"fps": 30
},
"magicClips": [
{
"id": "5c26f199-08dd-4fab-911f-5378c23cc103",
"title": "Fusion Camera Technology",
"viralAnalysis": "Opens on a bold claim about the camera system and pays it off with a concrete demo, a strong hook for tech audiences.",
"duration": 24.8,
"startTime": 132.4,
"endTime": 157.2,
"viralityScores": {
"total": 87,
"shareability": 22,
"hook_strength": 21,
"story_quality": 22,
"emotional_impact": 22
},
"status": "completed",
"previewUrl": "https://app.submagic.co/view/5c26f199-08dd-4fab-911f-5378c23cc104",
"downloadUrl": "https://api.submagic.co/api/file/download?path=3c6cd7cd-3f5e-4def-b662-69c48a7fc8ce/e568c322-7fa5-497a-8fb0-3ba32e9e67d2/5c26f199-08dd-4fac-911f-5378c23cc103-download.mp4&newFileName=Fusion%20Camera%20Technology.mp4",
"directUrl": "https://dqu1p08d61fh.cloudfront.net/api/9cc52d00-43d7-442f-9a22-050312bkm24f/1ddee5b4-101f-4c1e-a74a-b6b3bcfe206c/video.mp4-download.mp4"
},
{
"id": "364fe092-b68d-468a-9558-bfca7c4d190e",
"title": "ProMotion Display Breakthrough",
"viralAnalysis": "Surprising side-by-side comparison that invites debate in the comments.",
"duration": 21.04,
"startTime": 402.9,
"endTime": 423.94,
"viralityScores": {
"total": 89,
"shareability": 23,
"hook_strength": 22,
"story_quality": 21,
"emotional_impact": 23
},
"status": "completed",
"previewUrl": "https://app.submagic.co/view/364fe092-b68d-468a-9558-bfca7c4d190g",
"downloadUrl": "https://api.submagic.co/api/file/download?path=3c6cd7cd-3f5e-4def-b662-69c48a7fc8ce/e568c322-7fa5-497a-8fb0-3ba32e9e67d2/364fe092-b68d-468a-9558-bfca7c4d189c-download.mp4&newFileName=ProMotion%20Display%20Breakthrough.mp4",
"directUrl": "https://dqu1p08d61fh.cloudfront.net/api/9cc52d00-43d7-442f-9a22-050312bkm24f/1ddee5b4-101f-4c1e-a74a-b6b3bcfe206c/video.mp4-download.mp4"
}
],
"createdAt": "2025-09-24T12:55:56.989Z",
"updatedAt": "2025-09-24T12:59:24.880Z"
}
```
# Get Publication
Source: https://docs.submagic.co/api-reference/get-publication
GET https://api.submagic.co/v1/projects/published/{id}
Retrieve a single publication by its id, including its per-platform posts, analytics, and aggregated totals
# Get Publication
Retrieve one publication by its **publication id** — the `id` returned by
[List Published Projects](/api-reference/list-published-projects). A publication
represents one publish event for a project and contains a `posts` array with one
entry per platform. The response includes per-platform delivery status, post
URLs, analytics, and a `totals` rollup aggregated across those posts.
This endpoint requires authentication.
The `{id}` is the **publication id** (the `id` field from List Published
Projects), **not** a project id. A single project can be published multiple
times, so each publication has its own id. The `projectId` is still returned
for linking back to the underlying project.
## Authentication
Your Submagic API key starting with `sk-`
## Path Parameters
The unique identifier (UUID) of the publication to retrieve
## Response
Unique identifier of the publication
The unique identifier of the underlying project
ISO 8601 timestamp of when the publication was published or is scheduled to
publish
`true` if the publication was scheduled for a future time
Overall publication status: `published`, `scheduled`, `processing`, or
`failed`
Minimal project metadata
Project UUID
Project title
Metrics aggregated across the publication's platforms
Sum of views across all platforms
Sum of likes, comments, and shares across all platforms
Net followers gained around the publish time, summed across the
publication's accounts. `null` when no follower snapshots are available
to compute the delta.
Array of per-platform post objects
Platform identifier: `youtube`, `tiktok`, `instagram`, `twitter`,
`facebook`, or `linkedin`
The post ID assigned by the upstream platform
Public URL of the post on the platform
Per-platform delivery status: `published`, `scheduled`, `processing`, or
`failed`
Analytics sync status: `synced`, `pending`, or `failed`
Error message if the post failed to publish, otherwise `null`
Platform analytics (available once `syncStatus` is `synced`)
Total views
Total likes
Total comments
Total shares
Total impressions
Unique accounts reached
Total link clicks
Engagement rate as a decimal (e.g. `0.125` = 12.5%)
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/projects/published/pub_8f3b4c2e-1a9d-4e5f-9b7c-2a4d6e8f1c3b" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const getPublication = async (publicationId) => {
const response = await fetch(
`https://api.submagic.co/v1/projects/published/${publicationId}`,
{
headers: {
"x-api-key": "sk-your-api-key-here",
},
}
);
return response.json();
};
const publication = await getPublication(
"pub_8f3b4c2e-1a9d-4e5f-9b7c-2a4d6e8f1c3b"
);
console.log(
`${publication.totals.views} views across ${publication.posts.length} platforms`
);
```
```python Python theme={null}
import requests
def get_publication(publication_id):
url = f'https://api.submagic.co/v1/projects/published/{publication_id}'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
response = requests.get(url, headers=headers)
return response.json()
publication = get_publication('pub_8f3b4c2e-1a9d-4e5f-9b7c-2a4d6e8f1c3b')
print(f"{publication['totals']['views']} views across "
f"{len(publication['posts'])} platforms")
```
```json 200 OK theme={null}
{
"id": "pub_8f3b4c2e-1a9d-4e5f-9b7c-2a4d6e8f1c3b",
"projectId": "550e8400-e29b-41d4-a716-446655440000",
"publishedAt": "2026-04-28T12:34:56.000Z",
"scheduled": false,
"status": "published",
"project": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "How I Doubled My Revenue in 30 Days"
},
"totals": {
"views": 1234,
"engagement": 65,
"followersGained": 18
},
"posts": [
{
"platform": "youtube",
"platformPostId": "dQw4w9WgXcQ",
"platformPostUrl": "https://youtube.com/watch?v=dQw4w9WgXcQ",
"publishStatus": "published",
"syncStatus": "synced",
"error": null,
"analytics": {
"views": 1234,
"likes": 56,
"comments": 7,
"shares": 2,
"impressions": 5000,
"reach": 800,
"clicks": 120,
"engagementRate": 0.125
}
}
]
}
```
## Error Responses
```json theme={null}
{
"error": "NOT_FOUND",
"message": "Publication not found"
}
```
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred"
}
```
# Health Check
Source: https://docs.submagic.co/api-reference/health
GET https://api.submagic.co/health
Check the health status of the Submagic API
# Health Check
The health endpoint provides a simple way to check if the Submagic API is operational. This endpoint does not require authentication and can be used for monitoring and health checks.
This endpoint does not require authentication and is not subject to rate
limiting.
## Request
This endpoint does not accept any parameters.
## Response
The current status of the API service
The name of the service
ISO 8601 timestamp of when the response was generated
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/health"
```
```javascript JavaScript theme={null}
const response = await fetch("https://api.submagic.co/health");
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
response = requests.get('https://api.submagic.co/health')
data = response.json()
print(data)
```
```json Response theme={null}
{
"status": "healthy",
"service": "submagic-public-api",
"timestamp": "2024-01-15T10:30:00.000Z"
}
```
## Use Cases
Use this endpoint to monitor API availability in your monitoring systems
Configure your load balancer to use this endpoint for health checks
Verify API availability before running integration tests
Include this endpoint in your status page monitoring
## Status Values
| Status | Description |
| ----------- | ----------------------------------------------- |
| `healthy` | API is operational and ready to accept requests |
| `degraded` | API is operational but experiencing issues |
| `unhealthy` | API is not operational |
## Example Monitoring Script
Here's a simple monitoring script you can use:
```bash Bash theme={null}
#!/bin/bash
response=$(curl -s -o /dev/null -w "%{http_code}" https://api.submagic.co/health)
if [ $response -eq 200 ]; then
echo "API is healthy"
exit 0
else
echo "API is not responding (HTTP $response)"
exit 1
fi
```
```javascript Node.js theme={null}
const checkHealth = async () => {
try {
const response = await fetch("https://api.submagic.co/health");
const data = await response.json();
if (response.ok && data.status === "healthy") {
console.log("✅ API is healthy");
return true;
} else {
console.log("❌ API is not healthy:", data);
return false;
}
} catch (error) {
console.log("❌ Failed to check API health:", error.message);
return false;
}
};
// Run health check every 30 seconds
setInterval(checkHealth, 30000);
```
```python Python theme={null}
import requests
import time
import sys
def check_health():
try:
response = requests.get('https://api.submagic.co/health', timeout=10)
data = response.json()
if response.status_code == 200 and data.get('status') == 'healthy':
print('✅ API is healthy')
return True
else:
print(f'❌ API is not healthy: {data}')
return False
except Exception as e:
print(f'❌ Failed to check API health: {e}')
return False
if __name__ == '__main__':
is_healthy = check_health()
sys.exit(0 if is_healthy else 1)
```
The health endpoint is designed to respond quickly (typically under 100ms) to
provide fast health check results for monitoring systems.
# Get Hook Title Templates
Source: https://docs.submagic.co/api-reference/hook-title-templates
GET https://api.submagic.co/v1/hook-title/templates
Retrieve a list of all available hook title templates
# Get Hook Title Templates
Retrieve a list of all hookt title available templates that can be applied to your projects. Hook titles render an animated caption for the first seconds of your video to grab attention before the main captions begin.
This endpoint requires authentication and has a rate limit of 1000 requests
per hour.
## Authentication
Your Submagic API key starting with `sk-`
## Response
Array of available hook title template names
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/hook-title/templates" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.submagic.co/v1/hook-title/templates",
{
headers: { "x-api-key": "sk-your-api-key-here" },
}
);
const data = await response.json();
console.log(data.templates);
```
```python Python theme={null}
import requests
headers = {
'x-api-key': 'sk-your-api-key-here'
}
response = requests.get(
'https://api.submagic.co/v1/hook-title/templates',
headers=headers
)
data = response.json()
print("Available templates:", data['templates'])
```
```json Response theme={null}
{
"templates": [
"tiktok",
"laura",
"steph",
"kevin",
"kelly",
"mark",
"logan",
"enrico",
"mike",
"devin",
"hormozi",
"masi",
"ali"
]
}
```
## Using Hook Title Templates
When creating a project or updating a project, specify the template name in your request:
```json Example theme={null}
"hookTitle": {
"text": "Stop scrolling—watch this in 30 seconds",
"template": "tiktok",
"top": 45,
"size": 32
}
```
## Response
Array of available hook title template names
## Request Example
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/hook-title/templates" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.submagic.co/v1/hook-title/templates",
{
headers: { "x-api-key": "sk-your-api-key-here" },
}
);
const { templates } = await response.json();
console.log("Hook title templates:", templates);
```
```python Python theme={null}
import requests
headers = {'x-api-key': 'sk-your-api-key-here'}
response = requests.get(
'https://api.submagic.co/v1/hook-title/templates',
headers=headers
)
templates = response.json()['templates']
print("Hook title templates:", templates)
```
## Example Response
```json theme={null}
{
"templates": [
"tiktok",
"laura",
"steph",
"kevin",
"kelly",
"mark",
"logan",
"enrico",
"mike",
"devin",
"hormozi",
"masi",
"ali"
]
}
```
## Error Handling
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Invalid template name"
}
```
If you don't specify a `hookTitle` when creating a project, the system will
automatically apply the "tiktok" template.
Template names are case-sensitive. Make sure to use the exact template name as
returned by this endpoint.
# Get Languages
Source: https://docs.submagic.co/api-reference/languages
GET https://api.submagic.co/v1/languages
Retrieve a list of all supported languages for video transcription
# Get Languages
Retrieve a comprehensive list of all supported languages for video transcription and caption generation. This endpoint returns language codes and names that can be used when creating projects.
This endpoint requires authentication and has a rate limit of 100 requests per
minute.
## Authentication
Your Submagic API key starting with `sk-`
## Response
Array of supported language objects
Human-readable name of the language (e.g., "English", "Spanish")
ISO language code used for API requests (e.g., "en", "es", "fr")
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/languages" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const response = await fetch("https://api.submagic.co/v1/languages", {
headers: {
"x-api-key": "sk-your-api-key-here",
},
});
const data = await response.json();
console.log(data.languages);
```
```python Python theme={null}
import requests
headers = {
'x-api-key': 'sk-your-api-key-here'
}
response = requests.get('https://api.submagic.co/v1/languages', headers=headers)
data = response.json()
for language in data['languages']:
print(f"{language['name']}: {language['code']}")
```
```json Response theme={null}
{
"languages": [
{
"name": "English",
"code": "en"
},
{
"name": "Spanish",
"code": "es"
},
{
"name": "French",
"code": "fr"
},
{
"name": "German",
"code": "de"
},
{
"name": "Italian",
"code": "it"
},
{
"name": "Portuguese",
"code": "pt"
},
{
"name": "Dutch",
"code": "nl"
},
{
"name": "Russian",
"code": "ru"
},
{
"name": "Chinese (Simplified)",
"code": "zh"
},
{
"name": "Japanese",
"code": "ja"
},
{
"name": "Korean",
"code": "ko"
},
{
"name": "Arabic",
"code": "ar"
},
{
"name": "Hindi",
"code": "hi"
}
]
}
```
## Popular Languages
Here are some of the most commonly used language codes:
**Code:** `en` Most widely supported with highest accuracy
**Code:** `es` Excellent support for Latin American and European Spanish
**Code:** `fr` High accuracy for both European and Canadian French
**Code:** `de` Great support for German language nuances
**Code:** `it` Optimized for Italian pronunciation patterns
**Code:** `pt` Supports both Brazilian and European Portuguese
## Using Language Codes
Once you have the language codes, you can use them when creating projects:
```json theme={null}
{
"title": "My Spanish Video",
"language": "es",
"videoUrl": "https://example.com/spanish-video.mp4"
}
```
## Best Practices
### Caching Language Data
Since the list of supported languages doesn't change frequently, consider caching the response:
```javascript theme={null}
class LanguageCache {
constructor() {
this.cache = null;
this.lastFetch = null;
this.cacheDuration = 24 * 60 * 60 * 1000; // 24 hours
}
async getLanguages() {
const now = Date.now();
if (
this.cache &&
this.lastFetch &&
now - this.lastFetch < this.cacheDuration
) {
return this.cache;
}
const response = await fetch("https://api.submagic.co/v1/languages", {
headers: { "x-api-key": process.env.SUBMAGIC_API_KEY },
});
this.cache = await response.json();
this.lastFetch = now;
return this.cache;
}
}
```
### Building Language Selectors
Create user-friendly language selection interfaces:
```javascript theme={null}
const buildLanguageSelector = (languages) => {
return languages
.sort((a, b) => a.name.localeCompare(b.name))
.map((lang) => ({
value: lang.code,
label: lang.name,
flag: getFlagEmoji(lang.code), // Helper function for flag emojis
}));
};
```
## Error Responses
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred"
}
```
The language list is updated periodically as we add support for new languages.
We recommend fetching this list dynamically rather than hard-coding language
options in your application.
# List Presets
Source: https://docs.submagic.co/api-reference/list-presets
GET https://api.submagic.co/v1/presets
List the presets in your library
# List Presets
Retrieve a paginated list of your presets, newest first. Use cursor-based navigation to page through results.
This endpoint requires authentication and has a rate limit of 1000 requests
per hour.
## Authentication
Your Submagic API key starting with `sk-`
## Query Parameters
Number of presets to return per page (1-100). Defaults to `50`.
UUID cursor for pagination. Pass the `nextCursor` value from a previous
response to fetch the next page.
## Response
Array of preset objects. See [Get Preset](/api-reference/get-preset) for the
full object shape.
Whether there are more presets beyond this page
Cursor to pass as the `cursor` query parameter to fetch the next page.
Only present when `hasMore` is `true`.
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/presets?limit=10" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const listPresets = async ({ limit, cursor } = {}) => {
const params = new URLSearchParams();
if (limit) params.set("limit", String(limit));
if (cursor) params.set("cursor", cursor);
const response = await fetch(
`https://api.submagic.co/v1/presets?${params}`,
{
headers: {
"x-api-key": "sk-your-api-key-here",
},
}
);
return response.json();
};
const page = await listPresets({ limit: 10 });
if (page.pagination.hasMore) {
const nextPage = await listPresets({
limit: 10,
cursor: page.pagination.nextCursor,
});
}
```
```python Python theme={null}
import requests
def list_presets(limit=None, cursor=None):
url = 'https://api.submagic.co/v1/presets'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
params = {}
if limit:
params['limit'] = limit
if cursor:
params['cursor'] = cursor
response = requests.get(url, headers=headers, params=params)
return response.json()
page = list_presets(limit=10)
if page['pagination']['hasMore']:
next_page = list_presets(
limit=10,
cursor=page['pagination']['nextCursor']
)
```
```json 200 OK theme={null}
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My TikTok Preset",
"captions": true,
"hookTitle": true,
"magicZooms": true,
"magicBrolls": false,
"logo": false,
"colorFilter": false,
"cleanAudio": true,
"eyeContactCorrection": false,
"music": true,
"musicVolume": 25,
"configured": {
"captions": true,
"hookTitle": true,
"logo": false,
"colorFilter": false,
"music": true
},
"sourceProjectId": "88a08eec-712a-45d0-8d0b-3b631700cb3a",
"createdAt": "2024-01-10T08:00:00.000Z",
"updatedAt": "2024-01-12T09:30:00.000Z"
}
],
"pagination": {
"hasMore": true,
"nextCursor": "f5e4d3c2-b1a0-9876-fedc-ba0987654321"
}
}
```
```json 200 OK (empty) theme={null}
{
"data": [],
"pagination": {
"hasMore": false
}
}
```
# List Published Projects
Source: https://docs.submagic.co/api-reference/list-published-projects
GET https://api.submagic.co/v1/projects/published
List your published posts with optional platform, profile, and date-range filtering, sorting, and cursor-based pagination, including per-platform status, analytics, and aggregated totals
# List Published Projects
Retrieve a paginated list of your published posts across YouTube, TikTok,
Instagram, X (Twitter), Facebook, and LinkedIn. Each entry includes
per-platform delivery status, post URLs, and analytics where available, plus a
`totals` rollup aggregated across the post's platforms.
This endpoint requires authentication.
## Authentication
Your Submagic API key starting with `sk-`
## Query Parameters
Filter by platform. Allowed values: `youtube`, `tiktok`, `instagram`,
`twitter`, `facebook`, `linkedin`.
Filter to a single profile (UUID). Copy the id from the **Copy profile ID**
action in the publishing dashboard's profile selector.
Start of the date range (ISO 8601). Filters by `publishedAt`. When omitted,
results include everything published up to now.
End of the date range (ISO 8601). Filters by `publishedAt`. Defaults to the
current time.
Sort field. Allowed values: `publishedAt`, `views`, `engagement`. Defaults to
`publishedAt`. `views` and `engagement` sort by the aggregated `totals`.
Sort direction: `asc` or `desc`. Defaults to `desc`.
Number of items to return per page (1-100). Defaults to `25`.
Cursor for pagination. Pass the `nextCursor` value from a previous response
to fetch the next page of results.
## Response
Array of publication objects
Unique identifier of the publication
The unique identifier of the underlying project
ISO 8601 timestamp of when the publication was published or is scheduled
to publish
`true` if the publication was scheduled for a future time
Overall publication status: `published`, `scheduled`, `processing`, or
`failed`
Minimal project metadata
Project UUID
Project title
Metrics aggregated across the publication's platforms
Sum of views across all platforms
Sum of likes, comments, and shares across all platforms
Net followers gained around the publish time, summed across the
publication's accounts. `null` when no follower snapshots are
available to compute the delta.
Array of per-platform post objects
Platform identifier: `youtube`, `tiktok`, `instagram`, `twitter`,
`facebook`, or `linkedin`
The post ID assigned by the upstream platform
Public URL of the post on the platform
Per-platform delivery status: `published`, `scheduled`, `processing`,
or `failed`
Analytics sync status: `synced`, `pending`, or `failed`
Error message if the post failed to publish, otherwise `null`
Platform analytics (available once `syncStatus` is `synced`)
Total views
Total likes
Total comments
Total shares
Total impressions
Unique accounts reached
Total link clicks
Engagement rate as a decimal (e.g. `0.125` = 12.5%)
Cursor-based pagination metadata
Cursor to pass as the `cursor` query parameter to fetch the next page.
`null` or omitted when there are no more results.
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/projects/published?platform=youtube&from=2026-05-01T00:00:00Z&to=2026-05-31T23:59:59Z&sortBy=views&direction=desc&limit=25" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const listPublishedProjects = async ({
platform,
profileId,
from,
to,
sortBy,
direction,
limit,
cursor,
} = {}) => {
const params = new URLSearchParams();
if (platform) params.set("platform", platform);
if (profileId) params.set("profileId", profileId);
if (from) params.set("from", from);
if (to) params.set("to", to);
if (sortBy) params.set("sortBy", sortBy);
if (direction) params.set("direction", direction);
if (limit) params.set("limit", String(limit));
if (cursor) params.set("cursor", cursor);
const response = await fetch(
`https://api.submagic.co/v1/projects/published?${params}`,
{
headers: {
"x-api-key": "sk-your-api-key-here",
},
}
);
const data = await response.json();
console.log(`Fetched ${data.data.length} publications`);
return data;
};
// Fetch first page of YouTube publications
const page = await listPublishedProjects({ platform: "youtube", limit: 25 });
// Fetch next page
if (page.pagination.nextCursor) {
const nextPage = await listPublishedProjects({
platform: "youtube",
limit: 25,
cursor: page.pagination.nextCursor,
});
}
```
```python Python theme={null}
import requests
def list_published_projects(platform=None, profile_id=None, date_from=None,
date_to=None, sort_by=None, direction=None,
limit=None, cursor=None):
url = 'https://api.submagic.co/v1/projects/published'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
params = {}
if platform:
params['platform'] = platform
if profile_id:
params['profileId'] = profile_id
if date_from:
params['from'] = date_from
if date_to:
params['to'] = date_to
if sort_by:
params['sortBy'] = sort_by
if direction:
params['direction'] = direction
if limit:
params['limit'] = limit
if cursor:
params['cursor'] = cursor
response = requests.get(url, headers=headers, params=params)
data = response.json()
print(f"Fetched {len(data['data'])} publications")
return data
# Fetch first page of YouTube publications
page = list_published_projects(platform='youtube', limit=25)
# Fetch next page
next_cursor = page.get('pagination', {}).get('nextCursor')
if next_cursor:
next_page = list_published_projects(
platform='youtube',
limit=25,
cursor=next_cursor
)
```
```json 200 OK theme={null}
{
"data": [
{
"id": "pub_8f3b4c2e-1a9d-4e5f-9b7c-2a4d6e8f1c3b",
"projectId": "550e8400-e29b-41d4-a716-446655440000",
"publishedAt": "2026-04-28T12:34:56.000Z",
"scheduled": false,
"status": "published",
"project": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "How I Doubled My Revenue in 30 Days"
},
"totals": {
"views": 1234,
"engagement": 65,
"followersGained": 18
},
"posts": [
{
"platform": "youtube",
"platformPostId": "dQw4w9WgXcQ",
"platformPostUrl": "https://youtube.com/watch?v=dQw4w9WgXcQ",
"publishStatus": "published",
"syncStatus": "synced",
"error": null,
"analytics": {
"views": 1234,
"likes": 56,
"comments": 7,
"shares": 2,
"impressions": 5000,
"reach": 800,
"clicks": 120,
"engagementRate": 0.125
}
}
]
}
],
"pagination": {
"nextCursor": "eyJpZCI6InB1Yl83YjJjM2Q0ZSJ9"
}
}
```
```json 200 OK (empty) theme={null}
{
"data": [],
"pagination": {
"nextCursor": null
}
}
```
## Error Responses
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "platform",
"message": "Must be one of: youtube, tiktok, instagram, twitter, facebook, linkedin",
"value": "snapchat"
}
]
}
```
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred"
}
```
# List User Media
Source: https://docs.submagic.co/api-reference/list-user-media
GET https://api.submagic.co/v1/user-media
List the media files in your library
# List User Media
Retrieve a paginated list of media files from your library. You can filter by media type and control pagination using cursor-based navigation.
This endpoint requires authentication and has a rate limit of 500 requests per
hour.
## Authentication
Your Submagic API key starting with `sk-`
## Query Parameters
Filter by media type. Allowed values: `VIDEO`, `AUDIO`, `IMAGE`.
Number of items to return per page (1-100). Defaults to `50`.
UUID cursor for pagination. Pass the `nextCursor` value from a previous
response to fetch the next page of results.
## Response
Array of user media objects
Unique identifier for the media file (UUID format)
Media type: `VIDEO`, `AUDIO`, or `IMAGE`
Original file name of the uploaded media
URL to access the media file
File metadata that varies by media type
Width in pixels (VIDEO and IMAGE only)
Height in pixels (VIDEO and IMAGE only)
Duration in seconds (VIDEO and AUDIO only)
File size in bytes
ISO 8601 timestamp when the media was uploaded
Whether there are more items available beyond this page
Cursor to pass as the `cursor` query parameter to fetch the next page.
Only present when `hasMore` is `true`.
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/user-media?type=AUDIO&limit=10" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const listUserMedia = async ({ type, limit, cursor } = {}) => {
const params = new URLSearchParams();
if (type) params.set("type", type);
if (limit) params.set("limit", String(limit));
if (cursor) params.set("cursor", cursor);
const response = await fetch(
`https://api.submagic.co/v1/user-media?${params}`,
{
headers: {
"x-api-key": "sk-your-api-key-here",
},
}
);
const data = await response.json();
console.log(`Fetched ${data.items.length} items`);
return data;
};
// Fetch first page of audio files
const page = await listUserMedia({ type: "AUDIO", limit: 10 });
// Fetch next page
if (page.hasMore) {
const nextPage = await listUserMedia({
type: "AUDIO",
limit: 10,
cursor: page.nextCursor,
});
}
```
```python Python theme={null}
import requests
def list_user_media(media_type=None, limit=None, cursor=None):
url = 'https://api.submagic.co/v1/user-media'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
params = {}
if media_type:
params['type'] = media_type
if limit:
params['limit'] = limit
if cursor:
params['cursor'] = cursor
response = requests.get(url, headers=headers, params=params)
data = response.json()
print(f"Fetched {len(data['items'])} items")
return data
# Fetch first page of audio files
page = list_user_media(media_type='AUDIO', limit=10)
# Fetch next page
if page['hasMore']:
next_page = list_user_media(
media_type='AUDIO',
limit=10,
cursor=page['nextCursor']
)
```
```json 200 OK theme={null}
{
"items": [
{
"id": "88a08eec-712a-45d0-8d0b-3b631700cb3a",
"type": "AUDIO",
"fileName": "background-music.mp3",
"url": "https://cdn.submagic.co/media/88a08eec-712a-45d0-8d0b-3b631700cb3a.mp3",
"metadata": {
"duration": 180.5,
"fileSize": 4500000
},
"createdAt": "2024-01-10T08:00:00.000Z"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "VIDEO",
"fileName": "broll-clip.mp4",
"url": "https://cdn.submagic.co/media/a1b2c3d4-e5f6-7890-abcd-ef1234567890.mp4",
"metadata": {
"width": 1920,
"height": 1080,
"duration": 12.3,
"fileSize": 15000000
},
"createdAt": "2024-01-09T14:30:00.000Z"
}
],
"hasMore": true,
"nextCursor": "f5e4d3c2-b1a0-9876-fedc-ba0987654321"
}
```
```json 200 OK (empty) theme={null}
{
"items": [],
"hasMore": false
}
```
## Error Responses
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "type",
"message": "Must be one of: VIDEO, AUDIO, IMAGE",
"value": "INVALID"
}
]
}
```
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred"
}
```
# Create Magic Clips
Source: https://docs.submagic.co/api-reference/magic-clips
POST https://api.submagic.co/v1/projects/magic-clips
Automatically generate short-form video clips from YouTube videos
# Create Magic Clips
Generate multiple engaging short-form video clips from YouTube videos automatically. This endpoint processes YouTube videos and creates clips with automatic captions, animations, and effects optimized for social media platforms.
This endpoint requires authentication, Magic Clips subscription, and has a
rate limit of 500 requests per hour.
Magic Clips created via API use your regular Magic Clips credits, not API
credits. 1 credit per project.
## Authentication
Your Submagic API key starting with `sk-`
## Request Body
A descriptive title for your Magic Clips project (1-100 characters)
Language code for captions (2-10 characters, e.g., "en", "es", "fr"). Use the
[languages endpoint](/api-reference/languages) to get available options.
Valid YouTube video URL to process for clip generation
URL to receive webhook notifications when processing is complete. Must be a
valid HTTPS URL.
Template to apply for styling. Use the [templates
endpoint](/api-reference/templates) to get available options. Defaults to
"Sara" if not specified. Cannot be used together with `userThemeId`.
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`.
Minimum clip duration in seconds (15-300 seconds). Defaults to 15 seconds.
Maximum clip duration in seconds (15-300 seconds). Defaults to 60 seconds.
Enable face tracking for the generated clips. If not specified, defaults to `true`.
## Response
Unique identifier for the created Magic Clips project (UUID format)
The title you provided for the project
Language code used for captions
Current processing status: `processing`
Webhook URL if provided in the request
Template name applied to the project
User theme ID applied to the project
ISO 8601 timestamp when the project was created
```bash cURL theme={null}
curl -X POST "https://api.submagic.co/v1/projects/magic-clips" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"title": "YT magic-clips test1",
"language": "en",
"youtubeUrl": "https://www.youtube.com/watch?v=eURYVajKLVw",
"templateName": "Hormozi 2",
"webhookUrl": "https://webhook-test.com/6704c8535a2a913c3d094aba685d6a18",
"minClipLength": 15,
"maxClipLength": 60,
"faceTracking": true
}'
```
```javascript JavaScript theme={null}
const createMagicClips = async () => {
const response = await fetch(
"https://api.submagic.co/v1/projects/magic-clips",
{
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "YT magic-clips test1",
language: "en",
youtubeUrl: "https://www.youtube.com/watch?v=eURYVajKLVw",
templateName: "Hormozi 2",
webhookUrl: "https://webhook-test.com/6704c8535a2a913c3d094aba685d6a18",
minClipLength: 15,
maxClipLength: 60,
faceTracking: true,
}),
}
);
const project = await response.json();
console.log("Magic Clips project created:", project.id);
return project;
};
```
```python Python theme={null}
import requests
import json
def create_magic_clips():
url = 'https://api.submagic.co/v1/projects/magic-clips'
headers = {
'x-api-key': 'sk-your-api-key-here',
'Content-Type': 'application/json'
}
data = {
'title': 'YT magic-clips test1',
'language': 'en',
'youtubeUrl': 'https://www.youtube.com/watch?v=eURYVajKLVw',
'templateName': 'Hormozi 2',
'webhookUrl': 'https://webhook-test.com/6704c8535a2a913c3d094aba685d6a18',
'minClipLength': 15,
'maxClipLength': 60,
'faceTracking': True
}
response = requests.post(url, headers=headers, json=data)
project = response.json()
print(f"Magic Clips project created: {project['id']}")
return project
```
```json 202 Accepted theme={null}
{
"id": "e568c322-7fa5-497a-8fb0-3ba32e9e67d2",
"title": "YT magic-clips test1",
"language": "en",
"status": "processing",
"webhookUrl": "https://webhook-test.com/6704c8535a2a913c3d094aba685d6a18",
"templateName": "Hormozi 2",
"userThemeId": "749a19fs-4b45-4b14-bd31-a3970a1a5ff2",
"createdAt": "2025-09-24T12:55:56.989Z"
}
```
## Webhook Integration
When processing is complete, a webhook notification will be sent to the provided `webhookUrl` (if specified). The webhook payload includes information about the main project and all generated clips.
### Webhook Payload Structure
```json theme={null}
{
"projectId": "e568c322-7fa5-497a-8fb0-3ba32e9e67d2",
"status": "completed",
"title": "YT magic-clips test1",
"duration": 283,
"completedAt": "2025-09-24T12:59:24.880Z",
"magicClips": [
{
"id": "364fe092-b68d-468a-9558-bfca7c4d190e",
"title": "ProMotion Display Breakthrough",
"duration": 21.04,
"viralityScores": {
"total": 89,
"shareability": 23,
"hook_strength": 22,
"story_quality": 21,
"emotional_impact": 23
},
"status": "completed",
"previewUrl": "https://app.submagic.co/view/364fe092-b68d-468a-9558-bfca7c4d190g",
"downloadUrl": "https://app.submagic.co/api/file/download?path=3c6cd7cd-3f5e-4def-b662-69c48a7fc8ce/e568c322-7fa5-497a-8fb0-3ba32e9e67d2/364fe092-b68d-468a-9558-bfca7c4d130e-download.mp4&newFileName=ProMotion%20Display%20Breakthrough.mp4",
"directUrl": "https://dqu1p08d61fh.cloudfront.net/api/9cc52d00-43d7-442f-9a22-050312bkm24f/1ddee5b4-101f-4c1e-a74a-b6b3bcfe206c/video.mp4-download.mp4"
}
]
}
```
### Webhook Response Fields
Main project identifier
Processing status: `completed` or `failed`
Project title
Total duration of the original video in seconds
ISO 8601 timestamp when processing completed
Array of generated clips, each containing: - `id`: Unique clip identifier -
`title`: AI-generated clip title - `duration`: Clip duration in seconds -
`viralityScores`: Object containing virality metrics with `total`, `shareability`, `hook_strength`, `story_quality`, and `emotional_impact` scores -
`status`: Clip processing status - `previewUrl`: URL to preview the clip -
`downloadUrl`: Direct download URL for the clip - `directUrl`: Direct URL that
can be embedded on your website or used to play the clip directly
## Error Responses
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Invalid request parameters"
}
```
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "FORBIDDEN",
"message": "Magic Clips subscription required. Please upgrade your plan or purchase Magic Clips credits."
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 60
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred",
"details": {}
}
```
# Upload Magic Clips
Source: https://docs.submagic.co/api-reference/magic-clips-upload
POST https://api.submagic.co/v1/projects/magic-clips/upload
Generate short-form video clips by uploading a video file
# Upload Magic Clips
Generate multiple engaging short-form video clips by uploading a video file directly to Submagic. This endpoint accepts multipart/form-data uploads and creates clips with automatic captions, animations, and effects optimized for social media platforms.
This endpoint requires authentication, Magic Clips subscription, and has a
rate limit of 500 requests per hour.
Magic Clips created via API use your regular Magic Clips credits, not API
credits. 1 credit per project.
## Authentication
Your Submagic API key starting with `sk-`
## Request Body (multipart/form-data)
Video file to upload. Must be in a supported format.
A descriptive title for your Magic Clips project (1-100 characters)
Language code for captions (e.g., "en", "es", "fr"). Use the [languages
endpoint](/api-reference/languages) to get available options.
URL to receive webhook notifications when processing is complete. Must be a
valid HTTPS URL.
Template to apply for styling (max 50 characters). Use the [templates
endpoint](/api-reference/templates) to get available options. Defaults to
"Sara" if not specified. Cannot be used together with `userThemeId`.
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`.
Minimum clip duration in seconds (15-300 seconds). Defaults to 15 seconds.
Maximum clip duration in seconds (15-300 seconds). Defaults to 60 seconds.
Enable face tracking for the generated clips. If not specified, defaults to `true`.
## Supported Formats & Limits
* **MP4** (.mp4) - **MOV** (.mov)
* **Max size:** 10GB - **Max duration:** 120 minutes
## Response
Unique identifier for the created Magic Clips project (UUID format)
The title you provided for the project
Language code used for captions
Current processing status: `processing`
Webhook URL if provided in the request
Template name applied to the project
User theme ID applied to the project
ISO 8601 timestamp when the project was created
```bash cURL theme={null}
curl -X POST "https://api.submagic.co/v1/projects/magic-clips/upload" \
-H "x-api-key: sk-your-api-key-here" \
-F "file=@./video.mp4" \
-F "title=My Magic Clips Upload" \
-F "language=en" \
-F "templateName=Hormozi 2" \
-F "webhookUrl=https://yoursite.com/webhook/submagic" \
-F "minClipLength=15" \
-F "maxClipLength=60" \
-F "faceTracking=true"
```
```javascript JavaScript theme={null}
const uploadMagicClips = async (file) => {
const formData = new FormData();
formData.append("file", file);
formData.append("title", "My Magic Clips Upload");
formData.append("language", "en");
formData.append("templateName", "Hormozi 2");
formData.append("webhookUrl", "https://yoursite.com/webhook/submagic");
formData.append("minClipLength", "15");
formData.append("maxClipLength", "60");
formData.append("faceTracking", "true");
const response = await fetch(
"https://api.submagic.co/v1/projects/magic-clips/upload",
{
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
},
body: formData,
}
);
const project = await response.json();
console.log("Magic Clips project created:", 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 uploadMagicClips(file);
}
});
```
```python Python theme={null}
import requests
def upload_magic_clips(file_path):
url = 'https://api.submagic.co/v1/projects/magic-clips/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 Magic Clips Upload',
'language': 'en',
'templateName': 'Hormozi 2',
'webhookUrl': 'https://yoursite.com/webhook/submagic',
'minClipLength': '15',
'maxClipLength': '60',
'faceTracking': 'true'
}
response = requests.post(url, headers=headers, files=files, data=data)
project = response.json()
print(f"Magic Clips project created: {project['id']}")
return project
# Usage
project = upload_magic_clips('./video.mp4')
```
```json 202 Accepted theme={null}
{
"id": "e568c322-7fa5-497a-8fb0-3ba32e9e67d2",
"title": "My Magic Clips Upload",
"language": "en",
"status": "processing",
"webhookUrl": "https://yoursite.com/webhook/submagic",
"templateName": "Hormozi 2",
"userThemeId": null,
"createdAt": "2025-09-24T12:55:56.989Z"
}
```
## Webhook Integration
When processing is complete, a webhook notification will be sent to the provided `webhookUrl` (if specified). The webhook payload includes information about the main project and all generated clips.
### Webhook Payload Structure
```json theme={null}
{
"projectId": "e568c322-7fa5-497a-8fb0-3ba32e9e67d2",
"status": "completed",
"title": "My Magic Clips Upload",
"duration": 283,
"completedAt": "2025-09-24T12:59:24.880Z",
"magicClips": [
{
"id": "364fe092-b68d-468a-9558-bfca7c4d190e",
"title": "ProMotion Display Breakthrough",
"duration": 21.04,
"viralityScores": {
"total": 89,
"shareability": 23,
"hook_strength": 22,
"story_quality": 21,
"emotional_impact": 23
},
"status": "completed",
"previewUrl": "https://app.submagic.co/view/364fe092-b68d-468a-9558-bfca7c4d190g",
"downloadUrl": "https://app.submagic.co/api/file/download?path=3c6cd7cd-3f5e-4def-b662-69c48a7fc8ce/e568c322-7fa5-497a-8fb0-3ba32e9e67d2/364fe092-b68d-468a-9558-bfca7c4d130e-download.mp4&newFileName=ProMotion%20Display%20Breakthrough.mp4",
"directUrl": "https://dqu1p08d61fh.cloudfront.net/api/9cc52d00-43d7-442f-9a22-050312bkm24f/1ddee5b4-101f-4c1e-a74a-b6b3bcfe206c/video.mp4-download.mp4"
}
]
}
```
### Webhook Response Fields
Main project identifier
Processing status: `completed` or `failed`
Project title
Total duration of the original video in seconds
ISO 8601 timestamp when processing completed
Array of generated clips, each containing: - `id`: Unique clip identifier -
`title`: AI-generated clip title - `duration`: Clip duration in seconds -
`viralityScores`: Object containing virality metrics with `total`, `shareability`, `hook_strength`, `story_quality`, and `emotional_impact` scores -
`status`: Clip processing status - `previewUrl`: URL to preview the clip -
`downloadUrl`: Direct download URL for the clip - `directUrl`: Direct URL that
can be embedded on your website or used to play the clip directly
## Error Responses
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Content-Type must be multipart/form-data"
}
```
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Invalid request parameters"
}
```
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "FORBIDDEN",
"message": "Magic Clips subscription required. Please upgrade your plan or purchase Magic Clips credits."
}
```
```json theme={null}
{
"error": "PAYLOAD_TOO_LARGE",
"message": "File size exceeds maximum allowed size"
}
```
```json theme={null}
{
"error": "UNSUPPORTED_MEDIA_TYPE",
"message": "Video format not supported"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 60
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred",
"details": {}
}
```
# Profile Stats
Source: https://docs.submagic.co/api-reference/profile-stats
GET https://api.submagic.co/v1/publishing/stats/profiles/{profileId}
Publishing stats scoped to a single profile (brand): summary totals with prior-period comparison and a daily time-series for the chosen metric
# Profile Stats
The same data as [Publishing Stats](/api-reference/publishing-stats), but scoped
to a single **profile** (brand) — all connected accounts under that profile.
Returns the profile metadata plus summary totals (with prior-period comparison)
and a daily time-series for the chosen metric.
Both `from` and `to` are optional — omit them to get the **last 7 days** (the
dashboard's default period).
This endpoint requires authentication.
The `{profileId}` is the id shown by the **Copy profile ID** action in the
publishing dashboard's profile selector.
## Authentication
Your Submagic API key starting with `sk-`
## Path Parameters
The unique identifier (UUID) of the profile to scope the analytics to
## Query Parameters
Start of the date range (ISO 8601), e.g. `2026-05-01T00:00:00Z`. When both
`from` and `to` are omitted, the range defaults to the **last 7 days**.
End of the date range (ISO 8601), e.g. `2026-05-31T23:59:59Z`. Defaults to the
current time. The prior period used for comparison is the equally sized window
immediately before `from`.
Filter by platform. Allowed values: `youtube`, `tiktok`, `instagram`,
`twitter`, `facebook`, `linkedin`.
Which metric the `timeseries` returns: `views`, `engagement`, or `followers`.
Defaults to `views`. The `summary` block always includes all three.
## Response
Metadata for the scoped profile
Profile UUID
Profile display name
`true` if this is your primary profile
The requested window and the prior period used for comparison
Start of the requested range (ISO 8601)
End of the requested range (ISO 8601)
Start of the prior comparison period
End of the prior comparison period
Totals for the current period with prior-period comparison. Each metric is an
object with `current` and `prior` values.
`{ current, prior }` — total views
`{ current, prior }` — total likes + comments + shares
`{ current, prior }` — total followers across the profile's accounts
The metric the `timeseries` represents (echoes the `metric` query parameter)
Daily data points spanning the range (UTC days)
Day in `YYYY-MM-DD` format
Total value of the selected metric for that day
Per-platform split of the day's total
Platform identifier
Value for that platform on that day
```bash cURL (last 7 days) theme={null}
curl -X GET "https://api.submagic.co/v1/publishing/stats/profiles/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" \
-H "x-api-key: sk-your-api-key-here"
```
```bash cURL (custom range) theme={null}
curl -X GET "https://api.submagic.co/v1/publishing/stats/profiles/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d?from=2026-05-01T00:00:00Z&to=2026-05-31T23:59:59Z&metric=followers" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const getProfileStats = async (
profileId,
{ from, to, platform, metric } = {}
) => {
const params = new URLSearchParams();
if (from) params.set("from", from);
if (to) params.set("to", to);
if (platform) params.set("platform", platform);
if (metric) params.set("metric", metric);
const response = await fetch(
`https://api.submagic.co/v1/publishing/stats/profiles/${profileId}?${params}`,
{
headers: {
"x-api-key": "sk-your-api-key-here",
},
}
);
return response.json();
};
const stats = await getProfileStats(
"9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
{ from: "2026-05-01T00:00:00Z", to: "2026-05-31T23:59:59Z", metric: "followers" }
);
console.log(`${stats.profile.name}: ${stats.summary.followers.current} followers`);
```
```python Python theme={null}
import requests
def get_profile_stats(profile_id, date_from, date_to,
platform=None, metric=None):
url = f'https://api.submagic.co/v1/publishing/stats/profiles/{profile_id}'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
params = {'from': date_from, 'to': date_to}
if platform:
params['platform'] = platform
if metric:
params['metric'] = metric
response = requests.get(url, headers=headers, params=params)
return response.json()
stats = get_profile_stats(
'9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d',
'2026-05-01T00:00:00Z',
'2026-05-31T23:59:59Z',
metric='followers',
)
print(f"{stats['profile']['name']}: {stats['summary']['followers']['current']} followers")
```
```json 200 OK theme={null}
{
"profile": {
"id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"name": "My Brand",
"isPrimary": true
},
"period": {
"from": "2026-05-01T00:00:00.000Z",
"to": "2026-05-31T23:59:59.000Z",
"priorFrom": "2026-03-31T00:00:01.000Z",
"priorTo": "2026-05-01T00:00:00.000Z"
},
"summary": {
"views": { "current": 60200, "prior": 51000 },
"engagement": { "current": 4100, "prior": 3500 },
"followers": { "current": 15230, "prior": 14800 }
},
"metric": "followers",
"timeseries": [
{
"date": "2026-05-01",
"total": 14820,
"breakdown": [{ "platform": "tiktok", "value": 14820 }]
},
{
"date": "2026-05-31",
"total": 15230,
"breakdown": [{ "platform": "tiktok", "value": 15230 }]
}
]
}
```
## Error Responses
```json theme={null}
{
"error": "NOT_FOUND",
"message": "Profile not found"
}
```
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "from",
"message": "Invalid datetime",
"value": "2026-05-01"
}
]
}
```
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred"
}
```
# Publish Project
Source: https://docs.submagic.co/api-reference/publish-project
POST https://api.submagic.co/v1/projects/{id}/publish
Publish or schedule an exported project to one or more social platforms (YouTube, TikTok, Instagram, X/Twitter, Facebook, LinkedIn) in a single call
# Publish Project
Publish or schedule an exported project to one or more connected social platforms (YouTube, TikTok, Instagram, X/Twitter, Facebook, LinkedIn).
This endpoint requires authentication. The project must already be exported
(have a `downloadUrl`), and the social accounts you want to publish to must be
connected from the [Publishing](https://app.submagic.co/publishing) page in
the dashboard.
## Authentication
Your Submagic API key starting with `sk-`
## Path Parameters
The unique identifier (UUID) of the exported project to publish
## Request Body
ISO 8601 timestamp to schedule the publication for a future time. Must be in
the future. Omit to publish immediately. Scheduled publications return a
`202 Accepted` response; immediate publications return `200 OK`.
Per-platform publishing options. At least one platform must be provided.
Unknown keys are rejected.
YouTube publishing options
Video title (max 100 characters)
Video description
Array of tag strings (max 50 tags)
Pinned first comment to post under the video
TikTok publishing options
Caption / post content (max 2200 characters)
Who can see the post: `public` (everyone), `friends` (mutual follows),
`followers`, or `private` (only the creator). Defaults to `public`.
Note that a private TikTok account cannot post `public`.
Allow comments on the post. Defaults to `true`.
Allow Duets. Defaults to `true`.
Allow Stitches. Defaults to `true`.
Commercial-content disclosure: `brand_organic` (promoting your own
brand) or `brand_content` (paid partnership). Omit when the post is not
commercial. `brand_content` cannot be combined with a `private`
`privacyLevel` — TikTok forbids private branded content.
Flags the video as AI-generated content.
Instagram publishing options
Publishing format: `reel` or `story`
Caption text. Required when `format` is `reel`.
Pinned first comment to post under the reel
X (Twitter) publishing options
Post content (max 280 characters)
Facebook publishing options
Post content (max 5000 characters)
LinkedIn publishing options
Post content (max 3000 characters)
## Prerequisites
Before publishing a project, ensure:
* **Project is exported**: The project must have a `downloadUrl` (status `completed`)
* **Project ownership**: The project must belong to the authenticated user
* **Accounts connected**: Each requested platform must be connected from the [Publishing](https://app.submagic.co/publishing) page in the dashboard
* **Sufficient credits**: Your account must have enough API credits
## Response
Unique identifier of the publication
The unique identifier of the project being published
`true` if the publication was scheduled for a future time, `false` if
published immediately
ISO 8601 timestamp of when the publication was published or is scheduled to
publish
Current status of the publication: `published`, `scheduled`, `processing`, or
`failed`
Array of platforms the project is being published to
```bash cURL - Publish Now theme={null}
curl -X POST "https://api.submagic.co/v1/projects/550e8400-e29b-41d4-a716-446655440000/publish" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"platforms": {
"youtube": {
"title": "How I Doubled My Revenue in 30 Days",
"description": "In this video I break down the exact playbook I used.",
"tags": ["business", "marketing", "growth"],
"firstComment": "Drop a comment if this was helpful!"
},
"tiktok": {
"content": "The exact playbook I used to double my revenue 🚀 #business #marketing",
"privacyLevel": "public"
},
"twitter": {
"content": "I doubled my revenue in 30 days. Here is the exact playbook 🧵"
},
"facebook": {
"content": "I doubled my revenue in 30 days. Full breakdown of the playbook inside."
},
"linkedin": {
"content": "How I doubled my revenue in 30 days — the exact playbook, broken down step by step."
}
}
}'
```
```bash cURL - Schedule for Later theme={null}
curl -X POST "https://api.submagic.co/v1/projects/550e8400-e29b-41d4-a716-446655440000/publish" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"scheduledFor": "2026-05-01T15:00:00Z",
"platforms": {
"instagram": {
"format": "reel",
"content": "New video out now ✨",
"firstComment": "Tap the link in bio for the full episode!"
}
}
}'
```
```javascript JavaScript theme={null}
const publishProject = async (projectId, payload) => {
const response = await fetch(
`https://api.submagic.co/v1/projects/${projectId}/publish`,
{
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
}
);
const result = await response.json();
console.log("Publication:", result.id, result.status);
return result;
};
// Publish to multiple platforms now
const result = await publishProject("550e8400-e29b-41d4-a716-446655440000", {
platforms: {
youtube: {
title: "How I Doubled My Revenue in 30 Days",
description: "In this video I break down the exact playbook I used.",
tags: ["business", "marketing", "growth"],
},
tiktok: {
content: "The exact playbook I used to double my revenue 🚀",
privacyLevel: "public",
},
twitter: {
content: "I doubled my revenue in 30 days. Here is the exact playbook 🧵",
},
linkedin: {
content:
"How I doubled my revenue in 30 days — the exact playbook, broken down step by step.",
},
},
});
```
```python Python theme={null}
import requests
def publish_project(project_id, payload):
url = f'https://api.submagic.co/v1/projects/{project_id}/publish'
headers = {
'x-api-key': 'sk-your-api-key-here',
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
print(f"Publication: {result.get('id')} - {result.get('status')}")
return result
# Schedule a reel on Instagram
payload = {
'scheduledFor': '2026-05-01T15:00:00Z',
'platforms': {
'instagram': {
'format': 'reel',
'content': 'New video out now ✨',
'firstComment': 'Tap the link in bio for the full episode!'
}
}
}
result = publish_project(
'550e8400-e29b-41d4-a716-446655440000',
payload
)
```
```json 200 OK - Published Now theme={null}
{
"id": "pub_8f3b4c2e-1a9d-4e5f-9b7c-2a4d6e8f1c3b",
"projectId": "550e8400-e29b-41d4-a716-446655440000",
"scheduled": false,
"publishedAt": "2026-04-28T12:34:56.000Z",
"status": "published",
"platforms": ["youtube", "tiktok", "twitter", "facebook", "linkedin"]
}
```
```json 202 Accepted - Scheduled theme={null}
{
"id": "pub_8f3b4c2e-1a9d-4e5f-9b7c-2a4d6e8f1c3b",
"projectId": "550e8400-e29b-41d4-a716-446655440000",
"scheduled": true,
"publishedAt": "2026-05-01T15:00:00.000Z",
"status": "scheduled",
"platforms": ["instagram"]
}
```
```json 400 BAD_REQUEST - Project Not Exported theme={null}
{
"error": "BAD_REQUEST",
"message": "Project must be exported before it can be published"
}
```
```json 402 PAYMENT_REQUIRED - Insufficient Credits theme={null}
{
"error": "PAYMENT_REQUIRED",
"message": "Insufficient API credits to publish"
}
```
```json 403 FORBIDDEN - Plan Restriction theme={null}
{
"error": "FORBIDDEN",
"message": "Your plan does not allow publishing via the API"
}
```
```json 404 NOT_FOUND - Project Not Found theme={null}
{
"error": "NOT_FOUND",
"message": "Project doesn't exist or doesn't belong to user"
}
```
```json 412 PRECONDITION_FAILED - Account Not Connected theme={null}
{
"error": "PRECONDITION_FAILED",
"message": "Some platform accounts are not connected"
}
```
## Tracking Published Posts
After publishing, use the [List Published Projects](/api-reference/list-published-projects)
endpoint to retrieve the status, platform URLs, and analytics of your posts.
**Tip**: To connect a YouTube, TikTok, Instagram, X (Twitter), Facebook, or
LinkedIn account, visit the [Publishing](https://app.submagic.co/publishing)
page in the dashboard. Once connected, the account is available to all
publish requests for that user.
**Important**: Publication to upstream platforms is asynchronous. A `200`
response means the request was accepted and posts have been dispatched, but
individual platform delivery (especially for scheduled posts) is finalized in
the background. Poll the [List Published Projects](/api-reference/list-published-projects)
endpoint to track per-platform `publishStatus`.
# Publishing Stats
Source: https://docs.submagic.co/api-reference/publishing-stats
GET https://api.submagic.co/v1/publishing/stats
Aggregate publishing analytics across all connected accounts: summary totals with prior-period comparison and a daily time-series for the chosen metric
# Publishing Stats
Aggregate publishing analytics across all of your connected accounts for a date
range. Returns summary totals (views, engagement, followers) with a prior-period
comparison, plus a daily time-series with per-platform breakdown for the chosen
metric. This mirrors the summary cards and chart on the publishing analytics
dashboard.
Both `from` and `to` are optional — omit them to get the **last 7 days** (the
dashboard's default period).
This endpoint requires authentication.
## Authentication
Your Submagic API key starting with `sk-`
## Query Parameters
Start of the date range (ISO 8601), e.g. `2026-05-01T00:00:00Z`. When both
`from` and `to` are omitted, the range defaults to the **last 7 days**.
End of the date range (ISO 8601), e.g. `2026-05-31T23:59:59Z`. Defaults to the
current time. The prior period used for comparison is the equally sized window
immediately before `from`.
Filter by platform. Allowed values: `youtube`, `tiktok`, `instagram`,
`twitter`, `facebook`, `linkedin`.
Which metric the `timeseries` returns: `views`, `engagement`, or `followers`.
Defaults to `views`. The `summary` block always includes all three.
## Response
The requested window and the prior period used for comparison
Start of the requested range (ISO 8601)
End of the requested range (ISO 8601)
Start of the prior comparison period
End of the prior comparison period
Totals for the current period with prior-period comparison. Each metric is an
object with `current` and `prior` values.
`{ current, prior }` — total views
`{ current, prior }` — total likes + comments + shares
`{ current, prior }` — total followers across connected accounts
The metric the `timeseries` represents (echoes the `metric` query parameter)
Daily data points spanning the range (UTC days)
Day in `YYYY-MM-DD` format
Total value of the selected metric for that day
Per-platform split of the day's total
Platform identifier
Value for that platform on that day
```bash cURL (last 7 days) theme={null}
curl -X GET "https://api.submagic.co/v1/publishing/stats" \
-H "x-api-key: sk-your-api-key-here"
```
```bash cURL (custom range) theme={null}
curl -X GET "https://api.submagic.co/v1/publishing/stats?from=2026-05-01T00:00:00Z&to=2026-05-31T23:59:59Z&metric=views" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const getPublishingStats = async ({ from, to, platform, metric } = {}) => {
const params = new URLSearchParams();
if (from) params.set("from", from);
if (to) params.set("to", to);
if (platform) params.set("platform", platform);
if (metric) params.set("metric", metric);
const response = await fetch(
`https://api.submagic.co/v1/publishing/stats?${params}`,
{
headers: {
"x-api-key": "sk-your-api-key-here",
},
}
);
return response.json();
};
const stats = await getPublishingStats({
from: "2026-05-01T00:00:00Z",
to: "2026-05-31T23:59:59Z",
metric: "views",
});
console.log(`Views: ${stats.summary.views.current} (was ${stats.summary.views.prior})`);
```
```python Python theme={null}
import requests
def get_publishing_stats(date_from, date_to, platform=None, metric=None):
url = 'https://api.submagic.co/v1/publishing/stats'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
params = {'from': date_from, 'to': date_to}
if platform:
params['platform'] = platform
if metric:
params['metric'] = metric
response = requests.get(url, headers=headers, params=params)
return response.json()
stats = get_publishing_stats(
'2026-05-01T00:00:00Z',
'2026-05-31T23:59:59Z',
metric='views',
)
summary = stats['summary']['views']
print(f"Views: {summary['current']} (was {summary['prior']})")
```
```json 200 OK theme={null}
{
"period": {
"from": "2026-05-01T00:00:00.000Z",
"to": "2026-05-31T23:59:59.000Z",
"priorFrom": "2026-03-31T00:00:01.000Z",
"priorTo": "2026-05-01T00:00:00.000Z"
},
"summary": {
"views": { "current": 120400, "prior": 98300 },
"engagement": { "current": 8420, "prior": 7100 },
"followers": { "current": 15230, "prior": 14800 }
},
"metric": "views",
"timeseries": [
{
"date": "2026-05-01",
"total": 4200,
"breakdown": [
{ "platform": "tiktok", "value": 3000 },
{ "platform": "youtube", "value": 1200 }
]
},
{
"date": "2026-05-02",
"total": 5100,
"breakdown": [{ "platform": "tiktok", "value": 5100 }]
}
]
}
```
## Error Responses
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "from",
"message": "Invalid datetime",
"value": "2026-05-01"
}
]
}
```
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred"
}
```
# Get Templates
Source: https://docs.submagic.co/api-reference/templates
GET https://api.submagic.co/v1/templates
Retrieve a list of all available video templates for styling and effects
# Get Templates
Retrieve a list of all available video templates that can be applied to your projects. Templates define the visual styling, animations, and effects that will be applied to your captions and video content.
This endpoint requires authentication and has a rate limit of 1000 requests per
hour.
## Authentication
Your Submagic API key starting with `sk-`
## Response
Array of available template names that can be used in project creation
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/templates" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const response = await fetch("https://api.submagic.co/v1/templates", {
headers: {
"x-api-key": "sk-your-api-key-here",
},
});
const data = await response.json();
console.log(data.templates);
```
```python Python theme={null}
import requests
headers = {
'x-api-key': 'sk-your-api-key-here'
}
response = requests.get('https://api.submagic.co/v1/templates', headers=headers)
data = response.json()
print("Available templates:", data['templates'])
```
```json Response theme={null}
{
"templates": [
"Matt",
"Jess",
"Jack",
"Nick",
"Laura",
"Kelly 2",
"Caleb",
"Kendrick",
"Lewis",
"Doug",
"Carlos",
"Luke",
"Leila",
"Mark",
"Sara",
"Daniel",
"Dan 2",
"Hormozi 4",
"Dan",
"Devin",
"Tayo",
"Ella",
"Tracy",
"Hormozi 1",
"Hormozi 2",
"Hormozi 3",
"Hormozi 5",
"Jason",
"William",
"Leon",
"Ali",
"Beast",
"Maya",
"Karl",
"Iman",
"Umi",
"David",
"Noah",
"Gstaad",
"Malta",
"Nema",
"seth"
]
}
```
## Using Templates
When creating a project, specify the template name in your request:
```json URL-based Project theme={null}
{
"title": "My Trendy Video",
"language": "en",
"videoUrl": "https://example.com/video.mp4",
"templateName": "Hormozi 2"
}
```
```json File Upload Project theme={null}
{
"title": "Professional Presentation",
"language": "en",
"templateName": "Hormozi 2",
"file": "video.mp4"
}
```
## Template Features
Each template includes:
* **Caption Styling**: Font family, size, color, and positioning
* **Animation Effects**: How captions appear and disappear
* **Visual Elements**: Background shapes, highlights, and decorative elements
* **Color Schemes**: Coordinated color palettes optimized for the template theme
* **Emoji Integration**: How emojis are displayed and animated
* **Layout Options**: Caption positioning and text alignment
## Template Preview
While the API doesn't provide template previews directly, you can create small
test projects with different templates to see how they look with your content
style.
### Template Testing Strategy
```javascript theme={null}
const testTemplates = async (videoUrl, templates) => {
const results = [];
for (const template of templates.slice(0, 3)) {
// Test first 3 templates
try {
const response = await fetch("https://api.submagic.co/v1/projects", {
method: "POST",
headers: {
"x-api-key": process.env.SUBMAGIC_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: `Test - ${template}`,
language: "en",
videoUrl: videoUrl,
templateName: template,
}),
});
const project = await response.json();
results.push({ template, projectId: project.id });
// Wait between requests to respect rate limits
await new Promise((resolve) => setTimeout(resolve, 2000));
} catch (error) {
console.error(`Failed to test template ${template}:`, error);
}
}
return results;
};
```
## Best Practices
### Caching Templates
Since templates don't change frequently, cache the list:
```javascript theme={null}
class TemplateCache {
constructor() {
this.templates = null;
this.lastFetch = null;
this.cacheFor = 6 * 60 * 60 * 1000; // 6 hours
}
async getTemplates() {
if (
this.templates &&
this.lastFetch &&
Date.now() - this.lastFetch < this.cacheFor
) {
return this.templates;
}
const response = await fetch("https://api.submagic.co/v1/templates", {
headers: { "x-api-key": process.env.SUBMAGIC_API_KEY },
});
const data = await response.json();
this.templates = data.templates;
this.lastFetch = Date.now();
return this.templates;
}
}
```
## Error Responses
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred"
}
```
## Default Template
If you don't specify a `templateName` when creating a project, the system will
automatically apply the "Sara" template, which is optimized for general
social media content.
Template names are case-sensitive. Make sure to use the exact template name as
returned by this endpoint.
# Update Preset
Source: https://docs.submagic.co/api-reference/update-preset
PUT https://api.submagic.co/v1/presets/{id}
Update the settings of an existing preset
# Update Preset
Update the settings on an existing preset. Only the toggles and values below can be changed — the preset's saved media (caption theme, hook, logo, color filter, music) is created in the editor and cannot be replaced through the API.
This endpoint requires authentication and has a rate limit of 500 requests per
hour.
## Authentication
Your Submagic API key starting with `sk-`
## Path Parameters
The unique identifier (UUID) of the preset to update
## Request Body
All fields are optional, but you must provide at least one. A toggle that
depends on saved media can only be enabled when the preset has that media — check
the `configured` object from [Get Preset](/api-reference/get-preset) first,
otherwise the request fails with `VALIDATION_ERROR`.
Preset name (1-100 characters)
Preset description (up to 500 characters). Pass `null` to clear it.
Whether projects created with this preset get its saved caption theme. Can
only be enabled if `configured.captions` is `true`.
Whether projects created with this preset get its saved hook title. Can only
be enabled if `configured.hookTitle` is `true`.
Whether Magic Zooms are enabled.
AI Auto B-rolls: Submagic automatically picks the moments and inserts b-roll
across the whole video.
How much of the video AI Auto B-rolls should cover, as a percentage (0-100).
Only used together with `magicBrolls: true`.
Whether AI Auto B-rolls use transition effects between clips.
Whether projects created with this preset get its saved logo. Can only be
enabled if `configured.logo` is `true`.
Whether projects created with this preset get its saved color filter. Can only
be enabled if `configured.colorFilter` is `true`.
Whether Clean Audio is enabled.
Whether Eye Contact correction is enabled.
Whether projects created with this preset get its saved background music. Can
only be enabled if `configured.music` is `true`.
Background music volume, 1-100. Can only be set if `configured.music` is
`true`.
Volume applied to all transition sounds, 0-100. Pass `null` to turn transition
sounds off.
## Response
Returns the updated preset. See [Get Preset](/api-reference/get-preset) for the
full object shape.
```bash cURL theme={null}
curl -X PUT "https://api.submagic.co/v1/presets/550e8400-e29b-41d4-a716-446655440000" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"name": "My TikTok Preset",
"magicZooms": true,
"music": true,
"musicVolume": 25
}'
```
```javascript JavaScript theme={null}
const updatePreset = async (presetId, updateData) => {
const response = await fetch(
`https://api.submagic.co/v1/presets/${presetId}`,
{
method: "PUT",
headers: {
"x-api-key": "sk-your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify(updateData),
}
);
return response.json();
};
const preset = await updatePreset("550e8400-e29b-41d4-a716-446655440000", {
name: "My TikTok Preset",
magicZooms: true,
music: true,
musicVolume: 25,
});
```
```python Python theme={null}
import requests
def update_preset(preset_id, update_data):
url = f'https://api.submagic.co/v1/presets/{preset_id}'
headers = {
'x-api-key': 'sk-your-api-key-here',
'Content-Type': 'application/json'
}
response = requests.put(url, headers=headers, json=update_data)
return response.json()
preset = update_preset('550e8400-e29b-41d4-a716-446655440000', {
'name': 'My TikTok Preset',
'magicZooms': True,
'music': True,
'musicVolume': 25
})
```
```json 200 OK theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My TikTok Preset",
"captions": true,
"hookTitle": true,
"magicZooms": true,
"magicBrolls": false,
"logo": false,
"colorFilter": false,
"cleanAudio": true,
"eyeContactCorrection": false,
"music": true,
"musicVolume": 25,
"configured": {
"captions": true,
"hookTitle": true,
"logo": false,
"colorFilter": false,
"music": true
},
"sourceProjectId": "88a08eec-712a-45d0-8d0b-3b631700cb3a",
"createdAt": "2024-01-10T08:00:00.000Z",
"updatedAt": "2024-01-12T09:30:00.000Z"
}
```
```json 400 VALIDATION_ERROR - Toggle Not Configured theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Cannot enable music: this preset has no saved music"
}
```
```json 404 NOT_FOUND theme={null}
{
"error": "NOT_FOUND",
"message": "Preset not found"
}
```
# Update Project
Source: https://docs.submagic.co/api-reference/update-project
PUT https://api.submagic.co/v1/projects/{id}
Update an existing video project with new settings, features, or media insertions
# Update Project
Update an existing video project with new settings, AI features, or user media (B-roll) insertions. This endpoint allows you to modify project parameters and enhance your video with additional features or custom media content from your library.
This endpoint requires authentication and has a rate limit of 100 requests per
hour. After modifying a video, you'll need to re-export to see the changes
using the export endpoint. When using `removeBadTakes`, the response may take
1-2 minutes as our AI processes the video.
## Authentication
Your Submagic API key starting with `sk-`
## Path Parameters
The unique identifier (UUID) of the project to update
## Request Body
All fields are optional. Only provide the fields you want to update. If a field is provided with a different value from the current project settings, it will be updated.
Edit the project's captions. Fetch the current transcript from
[Get Project](/api-reference/get-project), change anything you want (text,
timing, add or remove words), and send the **full** array back — it replaces
the transcript, so include every word you want to keep. The caption layout is
rebuilt automatically.
Requirements (otherwise `VALIDATION_ERROR`): the project must already be
transcribed, and words must stay in chronological order — each `startTime`
greater than or equal to the previous word's.
Can be combined with other fields in the same request: the words are applied
first, then the rest. If the words edit fails, nothing else is applied.
The word's id from the Get Project response. Keep it to edit an existing
word; **omit it to add a new word.**
The word text. For `silence` segments it is ignored and stored as empty
(silence never renders caption text).
Segment type. Must be one of `"word"`, `"silence"`, or `"punctuation"` —
any other value is rejected with `VALIDATION_ERROR`.
Start time in seconds (must be ≥ 0).
End time in seconds (must be ≥ `startTime`).
Returns the standard update response (`Project updated successfully`).
Re-export the project to render the edited captions.
Automatically remove silence from the video at the specified pace. 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
Automatically detect and remove bad takes and silence from the video using AI
analysis. **Note: This process may take 1-2 minutes to complete.**
Hide captions from the exported video. Optional, defaults to `false`.
Vertical caption position as a percentage from the top of the frame (0-80).
Applies to all captions, replacing any per-line position adjustments.
Re-export the project to see the change.
Horizontal caption position as a percentage (0-100, `50` = centered).
Re-export the project to see the change.
Update the animated hook caption shown at the start of the video. Set to
`true` to enable the default AI-generated hook, `false` to remove it, or pass
an object with optional overrides:
* `text`: Custom copy (1-100 characters)
* `template`: Hook title template name (defaults to `"tiktok"`). Fetch valid
names using the [hook title templates
endpoint](/api-reference/hook-title-templates).
* `top`: Vertical position between 0-80 (default `50`)
* `size`: Font size between 0-80 (default `30`)
Template names are validated before processing resumes; invalid names return
`VALIDATION_ERROR` immediately.
Optional background music track. When provided, replaces any existing
background music on the project.
UUID of the music track. Accepts either an `AUDIO` file from your media
library (use [List User Media](/api-reference/list-user-media)) or a Trendy
Music track ID (copied from the editor's **Edit Audio → Trendy Music** tab).
Playback volume (1-100)
Start offset within the audio file in seconds. Defaults to `0`.
Apply a fade-in/fade-out effect to the music track. Defaults to `true`.
Array describing B-roll edits. Provide at least one entry when this field is
included. Each item must include a `type` field to specify whether it's user
media from your library or AI-generated content.
Must be set to `"user-media"`
Start time in seconds where the media should begin (must be ≥ 0)
End time in seconds where the media should end (must be greater than
`startTime`)
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.
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"`
Must be set to `"ai-broll"`
Start time in seconds (must be ≥ 0)
End time in seconds. Must be greater than `startTime` and no more than 12
seconds later.
1-2500 character description of what the AI should render. This field is
mandatory whenever `type: "ai-broll"` is present.
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"`
**Important:** Each item must have a `type` field. Items entries cannot overlap
each other in time.
## AI B-roll Workflow & Credits
* Every AI B-roll item consumes **3 AI credits**. Requests fail with
`VALIDATION_ERROR` if the authenticated user lacks enough credits (unless their
balance is unlimited).
* When AI B-roll work is accepted, the project is moved back to `processing` and
the generation jobs run asynchronously. Your webhook or polling logic should
watch for the project to return to `completed`.
* Stock/user-media updates continue to succeed immediately with the traditional
`Project updated successfully` message.
## Finding User Media ID
To find your `userMediaId`:
1. Go to the Submagic editor
2. Navigate to the **'B-roll'** tab
3. Add a B-roll to access your media library
4. Go to the **'My videos'** tab
5. Each video will display its unique media ID that you can use with this API
## Response
Success message confirming the project update. When AI B-roll work is accepted,
the message is `Project update accepted` to indicate asynchronous rendering.
The unique identifier of the updated project
Updated processing status of the project. If AI B-roll generation was
requested, the project is moved to `processing` until the renders finish.
## Error Responses
Error code: `NOT_FOUND` or `VALIDATION_ERROR`
Detailed error message explaining what went wrong
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Invalid template name"
}
```
```bash cURL theme={null}
curl -X PUT "https://api.submagic.co/v1/projects/550e8400-e29b-41d4-a716-446655440000" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"removeSilencePace": "fast",
"removeBadTakes": true,
"hookTitle": {
"text": "New intro in 15 seconds",
"template": "laura",
"top": 40,
"size": 28
},
"music": {
"userMediaId": "88a08eec-712a-45d0-8d0b-3b631700cb3a",
"volume": 25,
"startFromTime": 10,
"fade": true
},
"items": [
{
"type": "user-media",
"startTime": 10.5,
"endTime": 15.2,
"userMediaId": "123e4567-e89b-12d3-a456-426614174000",
"layout": "pip-top-right"
},
{
"type": "ai-broll",
"startTime": 25,
"endTime": 31,
"prompt": "smooth slider shot of a team collaborating in a modern studio",
"layout": "split-50-50"
}
]
}'
```
```javascript JavaScript theme={null}
const updateProject = async (projectId, updateData) => {
const response = await fetch(
`https://api.submagic.co/v1/projects/${projectId}`,
{
method: "PUT",
headers: {
"x-api-key": "sk-your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify(updateData),
}
);
const result = await response.json();
console.log("Project updated:", result.message);
return result;
};
// Usage
const updateData = {
removeSilencePace: "fast",
removeBadTakes: true,
hookTitle: {
text: "New intro in 15 seconds",
template: "laura",
top: 40,
size: 28,
},
music: {
userMediaId: "88a08eec-712a-45d0-8d0b-3b631700cb3a",
volume: 25,
startFromTime: 10,
fade: true,
},
items: [
{
type: "user-media",
startTime: 10.5,
endTime: 15.2,
userMediaId: "123e4567-e89b-12d3-a456-426614174000",
layout: "pip-top-right",
},
{
type: "ai-broll",
startTime: 25,
endTime: 31,
prompt:
"smooth slider shot of a team collaborating in a modern studio",
layout: "split-50-50",
},
],
};
const result = await updateProject(
"550e8400-e29b-41d4-a716-446655440000",
updateData
);
```
```python Python theme={null}
import json
import requests
def update_project(project_id, update_data):
url = f'https://api.submagic.co/v1/projects/{project_id}'
headers = {
'x-api-key': 'sk-your-api-key-here',
'Content-Type': 'application/json'
}
response = requests.put(url, headers=headers, json=update_data)
result = response.json()
print(f"Project updated: {result.get('message')}")
return result
# Usage
update_data = {
'removeSilencePace': 'fast',
'removeBadTakes': True,
'hookTitle': {
'text': 'New intro in 15 seconds',
'template': 'laura',
'top': 40,
'size': 28
},
'music': {
'userMediaId': '88a08eec-712a-45d0-8d0b-3b631700cb3a',
'volume': 25,
'startFromTime': 10,
'fade': True
},
'items': [
{
'type': 'user-media',
'startTime': 10.5,
'endTime': 15.2,
'userMediaId': '123e4567-e89b-12d3-a456-426614174000',
'layout': 'pip-top-right'
},
{
'type': 'ai-broll',
'startTime': 25,
'endTime': 31,
'prompt': 'smooth slider shot of a team collaborating in a modern studio',
'layout': 'split-50-50'
}
]
}
result = update_project('550e8400-e29b-41d4-a716-446655440000', update_data)
```
```json 200 OK - Stock/User Media Only theme={null}
{
"message": "Project updated successfully",
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing"
}
```
```json 200 OK - AI B-roll Accepted theme={null}
{
"message": "Project update accepted",
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing"
}
```
```json 200 OK - Words Updated theme={null}
{
"message": "Project updated successfully",
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed"
}
```
```json 400 VALIDATION_ERROR - Not Transcribed Yet theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Project is not transcribed yet; words can only be edited after transcription completes."
}
```
```json 404 NOT_FOUND theme={null}
{
"error": "NOT_FOUND",
"message": "Project not found"
}
```
```json 400 VALIDATION_ERROR - Invalid Time Range theme={null}
{
"error": "VALIDATION_ERROR",
"message": "endTime must be greater than startTime"
}
```
```json 400 VALIDATION_ERROR - Invalid UUID theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Invalid UUID format for userMediaId"
}
```
**Important:** After updating a project, you must re-export the project to see
the changes in the final video. Use the export endpoint to generate the
updated video with your modifications. **Processing Time:** When using
`removeBadTakes`, the API response may take 1-2 minutes as our AI analyzes and
processes the video to detect and remove bad takes and silence.
# Upload Project
Source: https://docs.submagic.co/api-reference/upload-project
POST https://api.submagic.co/v1/projects/upload
Create a new video project by uploading a video file directly for AI-powered caption generation
# 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
Your Submagic API key starting with `sk-`
## Request Body (multipart/form-data)
A descriptive title for your video project (1-100 characters)
Language code for transcription (e.g., "en", "es", "fr"). Use the [languages
endpoint](/api-reference/languages) to get available options.
Video file to upload. Must be in a supported format and under 2GB.
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.
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.
Template to apply for styling. Use the [templates
endpoint](/api-reference/templates) to get available options. Defaults to
"Sara" if not specified. Cannot be used together with `userThemeId`.
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.
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`.
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`.
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](/api-reference/hook-title-templates) to discover
valid names.
* `top`: Optional vertical position between 0-80 (default `50`)
* `size`: Optional font size between 0-80 (default `30`)
Template names are validated before the upload begins; invalid names return a
`VALIDATION_ERROR`.
URL to receive webhook notifications when processing is complete. Must be a
valid HTTPS URL.
JSON array string of custom words or phrases to improve transcription accuracy
(max 100 items, 50 characters each).
JSON string describing optional items to insert into the video. Pass an
array of objects. Each item must include a `type` field to specify whether it's
user media from your library or AI-generated content.
Must equal `"user-media"`
Start time in seconds where the media should begin (≥ 0)
End time in seconds where the media should end (must be greater than
`startTime`)
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.
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"`
Must equal `"ai-broll"`
Start timestamp in seconds (≥ 0)
End timestamp in seconds; must be greater than `startTime` and within a
12-second window
1-2500 character description of the B-roll to generate
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"`
**Important:** Each item must have a `type` field. Items entries cannot overlap
in time. When present, this metadata is parsed after the upload completes and
queued for rendering.
Enable automatic zoom effects on the video to enhance visual engagement. Pass
"true" or "false" as string. Optional, defaults to "false".
Enable automatic B-roll insertion to enhance video content with relevant
supplementary footage. Pass "true" or "false" as string. Optional, defaults to
"false".
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".
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
Automatically detect and remove bad takes and silence from the video using AI
analysis. Pass "true" or "false" as string. Optional, defaults to "false".
Enable AI-powered audio cleanup that removes background noises from the video.
Pass "true" or "false" as string. Optional, defaults to "false".
Hide captions from the exported video. Pass `"true"` or `"false"` as
string. Optional, defaults to `"false"`.
JSON string describing an optional background music track that spans the full
project duration.
UUID of the music track. Accepts either an `AUDIO` file from your media
library (use [List User Media](/api-reference/list-user-media)) or a Trendy
Music track ID (copied from the editor's **Edit Audio → Trendy Music** tab).
Playback volume (1-100)
Start offset within the audio file in seconds. Defaults to `0`.
Apply a fade-in/fade-out effect to the music track. Defaults to `true`.
## Supported Formats & Limits
* **MP4** (.mp4) - **MOV** (.mov)
* **Max size:** 2GB - **Max duration:** 2 hours
```bash cURL theme={null}
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}'
```
```javascript JavaScript theme={null}
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);
}
});
```
```python Python theme={null}
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')
```
```json 201 Created theme={null}
{
"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
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "File validation failed",
"details": [
{
"field": "file",
"message": "File size exceeds 10GB limit",
"value": null
}
]
}
```
```json theme={null}
{
"error": "PAYLOAD_TOO_LARGE",
"message": "File size exceeds maximum allowed size"
}
```
```json theme={null}
{
"error": "UNSUPPORTED_MEDIA_TYPE",
"message": "Video format not supported"
}
```
```json theme={null}
{
"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."
}
```
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Invalid template name"
}
```
# Create User Media
Source: https://docs.submagic.co/api-reference/user-media
POST https://api.submagic.co/v1/user-media
Upload media to your library from a URL for use in video projects as B-roll
# Create User Media
Upload video or image files to your user media library by providing a URL. This media can then be referenced in your video projects using the returned `userMediaId`.
This endpoint requires authentication and has a rate limit of 500 requests per
hour.
## Authentication
Your Submagic API key starting with `sk-`
## Request Body
Public URL to your video or image file. Must be a valid URL and accessible
without authentication.
## Response
Unique identifier for the uploaded media (UUID format). Use this ID to
reference the media in your video projects.
```bash cURL theme={null}
curl -X POST "https://api.submagic.co/v1/user-media" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/media/sample-video.mp4"
}'
```
```javascript JavaScript theme={null}
const createUserMedia = async (mediaUrl) => {
const response = await fetch("https://api.submagic.co/v1/user-media", {
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify({
url: mediaUrl,
}),
});
const data = await response.json();
console.log("User media created:", data.userMediaId);
return data;
};
// Usage
const media = await createUserMedia(
"https://example.com/media/sample-video.mp4"
);
```
```python Python theme={null}
import requests
def create_user_media(media_url):
url = 'https://api.submagic.co/v1/user-media'
headers = {
'x-api-key': 'sk-your-api-key-here',
'Content-Type': 'application/json'
}
data = {
'url': media_url
}
response = requests.post(url, headers=headers, json=data)
result = response.json()
print(f"User media created: {result['userMediaId']}")
return result
# Usage
media = create_user_media('https://example.com/media/sample-video.mp4')
```
```json 201 Created theme={null}
{
"userMediaId": "88a08eec-712a-45d0-8d0b-3b631700cb3a"
}
```
## Using User Media in Projects
Once you have a `userMediaId`, you can reference it in the `items` array when creating or uploading projects:
```json theme={null}
{
"items": [
{
"type": "user-media",
"startTime": 5,
"endTime": 10,
"userMediaId": "88a08eec-712a-45d0-8d0b-3b631700cb3a",
"layout": "pip-top-right"
}
]
}
```
See the [Create Project](/api-reference/create-project) or [Upload Project](/api-reference/upload-project) documentation for more details on using user media in your videos.
## Error Responses
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "url",
"message": "Must be a valid URL",
"value": "invalid-url"
}
]
}
```
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "The following media is not ready yet: df7d8cfd-eb82-4865-8b32-df3396b972b1. Please wait for the upload to complete."
}
```
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "URLs from social media platforms are not supported. Please use a direct download link."
}
```
Returned when `url` is a Google Drive folder or other Drive link that doesn't
point to a single file. Share a direct file link with access set to "Anyone
with the link".
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "This looks like a Google Drive folder or an unsupported Drive link. Please share a direct link to a single file (e.g. https://drive.google.com/file/d/FILE_ID/view) with access set to \"Anyone with the link\"."
}
```
Returned when `url` can't be reached or serves a web page instead of a media
file (e.g. a broken link, a login wall, or a share page rather than a direct
download).
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "The provided URL does not point to a downloadable media file. Please provide a direct download link to a video, audio, or image file."
}
```
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred"
}
```
# Upload User Media
Source: https://docs.submagic.co/api-reference/user-media-upload
POST https://api.submagic.co/v1/user-media/upload
Upload video or image files directly to your media library for use in projects
# Upload User Media
Upload video or image files directly to your user media library. This media can then be referenced in your video projects as custom B-roll using the returned `userMediaId`.
This endpoint requires authentication and has a rate limit of 500 requests per
hour.
## Authentication
Your Submagic API key starting with `sk-`
## Request Body (multipart/form-data)
Video or image file to upload. Must be in a supported format.
## Response
Unique identifier for the uploaded media (UUID format). Use this ID to
reference the media in your video projects.
```bash cURL theme={null}
curl -X POST "https://api.submagic.co/v1/user-media/upload" \
-H "x-api-key: sk-your-api-key-here" \
-F "file=@./my-video.mp4"
```
```javascript JavaScript theme={null}
const uploadUserMedia = async (file) => {
const formData = new FormData();
formData.append("file", file);
const response = await fetch(
"https://api.submagic.co/v1/user-media/upload",
{
method: "POST",
headers: {
"x-api-key": "sk-your-api-key-here",
},
body: formData,
}
);
const data = await response.json();
console.log("User media uploaded:", data.userMediaId);
return data;
};
// Usage with file input
const fileInput = document.getElementById("media-file");
fileInput.addEventListener("change", async (event) => {
const file = event.target.files[0];
if (file) {
await uploadUserMedia(file);
}
});
```
```python Python theme={null}
import requests
def upload_user_media(file_path):
url = 'https://api.submagic.co/v1/user-media/upload'
headers = {
'x-api-key': 'sk-your-api-key-here'
}
with open(file_path, 'rb') as media_file:
files = {
'file': media_file
}
response = requests.post(url, headers=headers, files=files)
result = response.json()
print(f"User media uploaded: {result['userMediaId']}")
return result
# Usage
media = upload_user_media('./my-video.mp4')
```
```json 201 Created theme={null}
{
"userMediaId": "88a08eec-712a-45d0-8d0b-3b631700cb7v"
}
```
## Using User Media in Projects
Once you have a `userMediaId`, you can reference it in the `items` array when creating or uploading projects:
```json theme={null}
{
"items": [
{
"type": "user-media",
"startTime": 5,
"endTime": 10,
"userMediaId": "88a08eec-712a-45d0-8d0b-3b631700cb3a"
}
]
}
```
See the [Create Project](/api-reference/create-project) or [Upload Project](/api-reference/upload-project) documentation for more details on using user media in your videos.
## Error Responses
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "File validation failed",
"details": [
{
"field": "file",
"message": "File is required",
"value": null
}
]
}
```
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "The following media is not ready yet: df7d8cfd-eb82-4865-8b32-df3396b972b1. Please wait for the upload to complete."
}
```
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
```json theme={null}
{
"error": "PAYLOAD_TOO_LARGE",
"message": "File size exceeds maximum allowed size"
}
```
```json theme={null}
{
"error": "UNSUPPORTED_MEDIA_TYPE",
"message": "Media format not supported"
}
```
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 30
}
```
```json theme={null}
{
"error": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred"
}
```
# Authentication
Source: https://docs.submagic.co/authentication
Learn how to authenticate with the Submagic API using API keys
# Authentication
The Submagic API uses API key authentication to secure access to all endpoints. Every request must include a valid API key in the request headers.
## Getting Your API Key
Sign up for a free account at [app.submagic.co](https://app.submagic.co)
Go to your account settings and click on the "API" tab
Click "Generate API Key" to create your unique API key
Copy your API key immediately - you won't be able to see it again!
**Important**: Your API key will only be shown once during generation. Make
sure to copy and store it securely. If you lose your API key, you'll need to
regenerate a new one.
## API Key Format
Submagic API keys follow this format:
```
sk-[64-character-hex-string]
```
Example:
```
sk-a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456
```
## Authentication Method
Include your API key in the `x-api-key` header with every request:
```http theme={null}
x-api-key: sk-your-api-key-here
```
## Example Requests
```bash cURL theme={null}
curl -X GET "https://api.submagic.co/v1/languages" \
-H "x-api-key: sk-your-api-key-here"
```
```javascript JavaScript theme={null}
const response = await fetch("https://api.submagic.co/v1/languages", {
headers: {
"x-api-key": "sk-your-api-key-here",
},
});
```
```python Python theme={null}
import requests
headers = {
'x-api-key': 'sk-your-api-key-here'
}
response = requests.get('https://api.submagic.co/v1/languages', headers=headers)
```
## API Key Security
Never expose your API key in client-side code, public repositories, or logs
Store your API key in environment variables, not in your source code
## Best Practices
### Environment Variables
Store your API key in environment variables:
```bash .env theme={null}
SUBMAGIC_API_KEY=sk-your-api-key-here
```
```javascript Node.js theme={null}
const apiKey = process.env.SUBMAGIC_API_KEY;
```
```python Python theme={null}
import os
api_key = os.getenv('SUBMAGIC_API_KEY')
```
### Server-Side Only
Never use your API key in client-side JavaScript, mobile apps, or any publicly
accessible code. API keys should only be used from your secure backend
servers.
### Key Rotation
To regenerate your API key:
1. Generate a new API key from your dashboard
2. Update your applications with the new key
3. Test that everything works with the new key
4. The old key will be automatically invalidated
## Authentication Errors
If authentication fails, you'll receive a `401 Unauthorized` response:
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
If you're having trouble with authentication contact our support team.
# Edit the Transcript Before Rendering
Source: https://docs.submagic.co/guides/edit-transcript-before-rendering
Create a project without auto-rendering, correct the captions, then export the final video
# Edit the Transcript Before Rendering
By default, a project created through the API is **rendered automatically** as
soon as transcription finishes. If you want to review or fix the captions first
(correct a misheard word, retime a caption, add or remove words), create the
project with `autoRender: false`. The project then waits for you to edit the
transcript and trigger the export yourself.
Pass `autoRender: false` to [Create Project](/api-reference/create-project).
The video is downloaded and transcribed, but **no render is started**.
```bash cURL theme={null}
curl -X POST "https://api.submagic.co/v1/projects" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"title": "My Awesome Video",
"language": "en",
"videoUrl": "https://example.com/videos/sample.mp4",
"templateName": "Hormozi 2",
"autoRender": false,
"webhookUrl": "https://yoursite.com/webhook/submagic"
}'
```
You have two options to learn when the transcript is ready:
* **Webhook (recommended):** if you passed a `webhookUrl`, you'll receive a
`completed` notification once transcription finishes. For an
`autoRender: false` project this fires **after transcription** (the video
is not rendered yet, so `downloadUrl` will be empty until you export).
* **Polling:** call [Get Project](/api-reference/get-project) until
`transcriptionStatus` is `"COMPLETED"`. At that point the `words` array is
available.
```bash cURL theme={null}
curl "https://api.submagic.co/v1/projects/{id}" \
-H "x-api-key: sk-your-api-key-here"
```
```json Response (transcription done) theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"transcriptionStatus": "COMPLETED",
"words": [
{ "id": "w1EydrN5", "text": "Do", "type": "word", "startTime": 0.08, "endTime": 0.16 },
{ "id": "8VUdYfGN", "text": "you", "type": "word", "startTime": 0.16, "endTime": 0.24 }
]
}
```
For an `autoRender: false` project, `status: "completed"` means
"transcribed and ready to edit" — the video has **not** been rendered yet.
A `downloadUrl` only appears after you export in step 4.
Take the `words` array from the Get Project response and change whatever you
need. You can:
* **Fix text** — change a word's `text`.
* **Retime** — adjust `startTime` / `endTime`.
* **Add a word** — insert a new entry and **omit its `id`** (a fresh id is
assigned for you).
* **Remove a word** — leave it out of the array.
Send the **full** array back — it replaces the current transcript.
Send the edited array to [Update Project](/api-reference/update-project) in
the `words` field. The caption layout is rebuilt automatically so everything
stays in sync at render time.
```bash cURL theme={null}
curl -X PUT "https://api.submagic.co/v1/projects/{id}" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"words": [
{ "id": "w1EydrN5", "text": "Did", "type": "word", "startTime": 0.08, "endTime": 0.16 },
{ "id": "8VUdYfGN", "text": "you", "type": "word", "startTime": 0.16, "endTime": 0.24 }
]
}'
```
```json 200 OK theme={null}
{
"message": "Project updated successfully",
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed"
}
```
Editing words is only allowed **after** transcription completes. Calling it
too early returns a `VALIDATION_ERROR`.
You can also combine `words` with other fields (e.g. `music`, `items`) in
the same request — the caption edit is applied first, then the rest.
When the captions look right, trigger the render with
[Export Project](/api-reference/export-project). You'll be notified at your
`webhookUrl` (or can poll Get Project) when the rendered video is ready and
`downloadUrl` is populated.
```bash cURL theme={null}
curl -X POST "https://api.submagic.co/v1/projects/{id}/export" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{}'
```
## Notes
* You can edit the transcript as many times as you like before exporting —
repeat steps 3–4.
* Default behavior is unchanged: omit `autoRender` (or set it to `true`) and the
project renders automatically once transcription completes, exactly as before.
* This flow does not apply to Magic Clips, which follow their own pipeline.
# Introduction
Source: https://docs.submagic.co/introduction
Welcome to the Submagic API - Transform your videos with AI-powered captions and effects
# Welcome to the Submagic API
The Submagic API allows you to programmatically generate AI-powered captions for your videos and create engaging short-form clips from YouTube content. Whether you're building a video editing platform, content management system, or automating your video workflow, our API provides enterprise-grade video processing capabilities.
## What can you do with Submagic API?
Generate accurate, styled captions in 100+ languages with automatic
transcription
Automatically generate engaging short-form clips from YouTube videos using
AI
Apply professional templates with animated text, emojis, and visual effects
Transcribe videos in over 100 languages with high accuracy
Receive real-time notifications when your video processing is complete
Drive Submagic from Claude Code, Claude Desktop, Cursor, or any
MCP-compatible AI assistant
## Quick Start
Get started with the Submagic API in 3 simple steps:
Sign up for a Submagic account at [https://app.submagic.co/signup](https://app.submagic.co/signup) and
generate your API key from the account settings
Upload a video or provide a video URL to start processing
Download your processed video with captions and effects applied
## Example Request
Here's a quick example of how to create a project with a video URL:
```bash theme={null}
curl -X POST "https://api.submagic.co/v1/projects" \
-H "x-api-key: sk-your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"title": "My First Video",
"language": "en",
"videoUrl": "https://drive.google.com/video.mp4",
"templateName": "Hormozi 2"
}'
```
## Rate Limits
The Submagic API implements different rate limits based on the type of operation:
* **Lightweight operations** (languages, templates): 1000 requests/hour
* **Standard operations** (project retrieval): 500 requests/hour
* **Upload operations** (project creation, file uploads): 500 requests/hour
* **Export operations** (project export): 50 requests/hour
## Support
Need help getting started? We're here to help:
* 📧 **Email Support**: [support@submagic.co](mailto:support@submagic.co)
* 💬 **Community**: Join our \[Discord] ([https://discord.gg/submagic](https://discord.gg/submagic)) community
* 📖 **Documentation**: Complete API reference and guides
[Generate your API key](https://app.submagic.co/account) and start
transforming your videos with AI
# MCP Server
Source: https://docs.submagic.co/mcp-server
Connect Submagic to Claude Code, Claude Desktop, Cursor, and other MCP clients
# MCP Server
Submagic exposes an [MCP](https://modelcontextprotocol.io)-compatible endpoint at `POST https://api.submagic.co/mcp`. Connect from Claude Code, Claude Desktop, Cursor, or any MCP client to drive Submagic from inside your AI assistant.
## Authentication
Pass your existing Submagic API key as a Bearer token:
```http theme={null}
Authorization: Bearer sk-your-api-key-here
```
The same key and credits that work for the REST API work for MCP. If you don't have a key yet, generate one from your [account settings](https://app.submagic.co/user?tab=api).
## Connecting Clients
### Claude Code
```bash theme={null}
claude mcp add --transport http submagic https://api.submagic.co/mcp \
--header "Authorization: Bearer $SUBMAGIC_API_KEY"
```
### Claude Desktop
Claude Desktop's config file doesn't speak HTTP transport directly, so we use the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge.
Requires Node.js **20.18+** installed locally so `npx` can run `mcp-remote`.
To open the config file, go to **Claude → Settings → Developer → Edit Config**. Add the following:
```json theme={null}
{
"mcpServers": {
"submagic": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.submagic.co/mcp",
"--header",
"Authorization: Bearer YOUR_API_KEY_HERE"
]
}
}
}
```
```json theme={null}
{
"mcpServers": {
"submagic": {
"command": "cmd",
"args": [
"/c",
"npx",
"-y",
"mcp-remote",
"https://api.submagic.co/mcp",
"--header",
"Authorization: Bearer YOUR_API_KEY_HERE"
]
}
}
}
```
Save, then fully quit and reopen Claude Desktop. Submagic tools will appear under the hammer icon at the bottom of the chat.
### Cursor / Windsurf
Configure an HTTP MCP server pointing at `https://api.submagic.co/mcp` with the header:
```http theme={null}
Authorization: Bearer YOUR_API_KEY_HERE
```
### claude.ai web (Custom Connectors)
Currently unsupported — claude.ai's Custom Connectors require OAuth, not Bearer
tokens. Use Claude Code, Claude Desktop, Cursor, or Windsurf for now.
## Example Prompts
Once connected, you can ask your assistant things like:
* "Create a Submagic project from this URL."
* "Turn this YouTube video into Submagic Magic Clips."
* "Show me the status of my recent Submagic projects."
* "Export Submagic project `[id]` and publish it to TikTok and Instagram."
* "List my published Submagic projects from the last week with their views."
* "Show my Submagic publishing stats for the last 30 days."
* "How did profile `[id]` perform this month — views, engagement, and follower growth?"
* "Add background music to Submagic project `[id]`."
* "What Submagic caption templates are available?"
## Rate Limits & Credits
MCP tool calls share the same rate limits and credit pool as the REST API — see [Rate Limits](/rate-limits) for details.
Reach out to [support@submagic.co](mailto:support@submagic.co) if you run into
trouble connecting your MCP client.
# Rate Limits
Source: https://docs.submagic.co/rate-limits
Understanding Submagic API rate limits and best practices for handling them
# Rate Limits
The Submagic API implements rate limiting to ensure fair usage and maintain service quality for all users. Different endpoints have different rate limits based on their resource intensity.
## Rate Limit Tiers
**1000 requests/hour** - Languages endpoint - Templates endpoint
**500 requests/hour** - Project retrieval
**500 requests/hour** - File upload endpoint, Project creation
## Rate Limit Headers
Every API response includes rate limit information in the headers:
```http theme={null}
HTTP/1.1 200 OK
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 29
X-RateLimit-Reset: 1640995200
```
| Header | Description |
| ----------------------- | ---------------------------------------------- |
| `X-RateLimit-Limit` | Maximum requests allowed in the current window |
| `X-RateLimit-Remaining` | Requests remaining in the current window |
| `X-RateLimit-Reset` | Unix timestamp when the rate limit resets |
## Rate Limit Exceeded
When you exceed the rate limit, you'll receive a `429 Too Many Requests` response:
```json theme={null}
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Please try again later.",
"retryAfter": 30
}
```
The response includes:
* `retryAfter`: Seconds to wait before making another request
* `Retry-After` header: Same information as `retryAfter` field
## Best Practices
### 1. Implement Exponential Backoff
When you receive a 429 response, implement exponential backoff:
```javascript JavaScript theme={null}
async function makeRequestWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After");
const delay = Math.min(Math.pow(2, attempt) * 1000, 30000);
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
return response;
} catch (error) {
if (attempt === maxRetries - 1) throw error;
}
}
}
```
```python Python theme={null}
import time
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def make_request_with_retry(url, headers, max_retries=3):
session = requests.Session()
retry_strategy = Retry(
total=max_retries,
status_forcelist=[429, 500, 502, 503, 504],
backoff_factor=1
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session.get(url, headers=headers)
```
### 2. Monitor Rate Limit Headers
Track your remaining requests to avoid hitting limits:
```javascript theme={null}
const response = await fetch(endpoint, { headers });
const remaining = parseInt(response.headers.get("X-RateLimit-Remaining"));
const resetTime = parseInt(response.headers.get("X-RateLimit-Reset"));
if (remaining < 5) {
console.log(`Warning: Only ${remaining} requests remaining`);
console.log(`Rate limit resets at: ${new Date(resetTime * 1000)}`);
}
```
### 3. Use Webhooks
Instead of polling for status updates, use webhooks to get notified when processing completes:
```json theme={null}
{
"title": "My Video",
"language": "en",
"videoUrl": "https://example.com/video.mp4",
"webhookUrl": "https://yoursite.com/webhook/submagic"
}
```
## Rate Limit by Endpoint
| Endpoint | Method | Rate Limit |
| --------------------- | ------ | ---------- |
| `/v1/languages` | GET | 1000/hour |
| `/v1/templates` | GET | 1000/hour |
| `/v1/projects/:id` | GET | 100/hour |
| `/v1/projects` | POST | 30/hour |
| `/v1/projects/upload` | POST | 30/hour |
## Increasing Rate Limits
If you need higher rate limits for your application:
Reach out to our support team with your use case details
Share information about your expected request volume and patterns
Our team will review your request and may offer custom rate limits
Consider our enterprise plans for guaranteed higher limits
Contact our team at [support@submagic.co](mailto:support@submagic.co) to
discuss enterprise plans with custom rate limits.