# 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 Example.png ## 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.