ClipMaster API
Automate ClipMaster from the command line, your own scripts, or an AI assistant. The same endpoints the app uses — metered by the same tokens, no separate billing.
Prefer plain text? Read this page as Markdown — built for LLMs and agents. Using Claude? Connect the MCP server instead of calling endpoints by hand.
Overview
Access ClipMaster programmatically with an API key. Every endpoint behaves exactly as it does in the app, scoped to your own projects and clips. API access is available on paid plans.
Base URL:
https://api.clipmaster.app/api/v1
Authentication
Create a key in the app under Settings → API access. The full key (cm_live_…) is shown once at creation — store it securely. Send it as a Bearer token in the Authorization header:
curl https://api.clipmaster.app/api/v1/projects \-H "Authorization: Bearer cm_live_xxxxxxxxxxxxxxxxxxxx"
- Header only. Keys are not accepted as a
?token=query parameter. - A key resolves to your account and acts as you, scoped to your own data.
- Revoke a key any time in Settings; a revoked key stops working immediately.
403 session_auth_required for login/OAuth, billing and checkout (reading your token balance and usage is allowed), admin, API-key management, and connecting a social channel. Connect your channel in the web app first — everything else is scriptable.Some endpoints are paid plan features, marked below with the feature name (for example compilation). On a plan without it they answer 403 subscription_required with that feature.
Rate limits
120 requests per minute per key. Exceeding it returns 429 with a Retry-After header.
The AI endpoints (suggestions, Auto-edit, audio measurement, arranging) also have their own per-user limits and answer 429 the same way.
Quickstart
Ingest a YouTube video, wait for processing, then list and download the clips. Set these once in your shell:
KEY="cm_live_xxxxxxxxxxxxxxxxxxxx"BASE="https://api.clipmaster.app/api/v1"
Request
curl -X POST "$BASE/upload/from-url" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"source_url": "https://youtube.com/watch?v=..."}
Response
{"project_id": 123,"task_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890","message": "Processing started","progress_url": "/api/v1/projects/123/status"}
Request
curl "$BASE/projects/123/status" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 123,"status": "completed","progress": 1.0,"progress_percent": 100,"total_clips": 6,"processing_complete": true}
Request
curl "$BASE/projects/123/clips" \-H "Authorization: Bearer $KEY"
Response
[{"clip_id": 456,"start_time": 0.0,"end_time": 42.3,"description": "Founder explains the pivot","status": "ready"}]
Request
# Render it (charges tokens) -> {"export_id": 37, "status": "rendering", ...}curl -X POST "$BASE/projects/123/clips/456/export" \-H "Authorization: Bearer $KEY"# Poll until "status" is "completed"curl "$BASE/exports/37/status" -H "Authorization: Bearer $KEY"# The clip's exports, newest firstcurl "$BASE/exports?clip_id=456" -H "Authorization: Bearer $KEY"# Download from the newest completed export's download_urlcurl -L "$DOWNLOAD_URL" -o clip.mp4
A download is always an export: the clip rendered with its saved captions, effects and trim. download_url is a presigned S3 link, valid until the next 00:00 UTC (at least 30 minutes) — no auth header needed. Call GET /exports again for a fresh one.
Upload
For files, use the two-step upload flow; for YouTube, ingest by URL. Both take the same optional fields: processing_mode (automatic, semantic or full_video), query, min_duration, orientation, the caption flags, and compilation_format with compilation_prompt to build a compilation once the clips are ready. Processing spends 1 token per source minute.
Ingest a YouTube URL. Processing starts immediately; poll the returned project's status. Paid: youtube_import.
Request
curl -X POST "$BASE/upload/from-url" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"source_url": "https://youtube.com/watch?v=...","orientation": "vertical","compilation_format": "auto"}
Response
{"project_id": 123,"task_id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890","message": "Processing started","metadata": { "duration_sec": 212.4, "resolution": "1920x1080" },"progress_url": "/api/v1/projects/123/status"}
Step 1 of a file upload. Returns a short-lived upload target: POST the file to upload.url with the returned upload.fields unchanged, then call complete.
Request
curl -X POST "$BASE/uploads/init" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"filename": "talk.mp4","size_bytes": 94371840,"sha256": "e3b0c44298fc1c14...","duration_sec": 693.5,"content_type": "video/mp4"}
Response
{"project_id": 124,"upload": {"url": "https://uploads.clipmaster.app","fields": {"...": "opaque form fields - send them back unchanged"}},"expires_at": "2026-08-14T21:00:00Z"}
Step 2: confirm the upload finished and start processing.
Request
curl -X POST "$BASE/uploads/124/complete" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 124,"task_id": "b2c3d4e5-...","message": "Processing started","progress_url": "/api/v1/projects/124/status"}
Upload a logo, a voiceover or a compilation scene file without a browser. purpose is logo, narration, scene_video or scene_image (the last two paid: compilation). POST the returned fields, then the file last, to upload.url within 15 minutes, then use the asset_key at /users/me/logo/from-upload, /projects/{id}/clips/{clip_id}/logo/from-upload, /compilations/{id}/narration/from-upload or /compilations/{id}/media-scenes (as upload_key).
Request
curl -X POST "$BASE/uploads/assets" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d '{"purpose": "logo", "content_type": "image/png", "size_bytes": 20480}'
Response
{"asset_key": "originals/staging/42/9f1c2b7e.png","upload": {"url": "https://s3.amazonaws.com/...","fields": { "...": "opaque form fields - send them back unchanged" }},"expires_at": "2026-09-11T21:15:00Z"}
Status & clips
Poll processing progress. status is one of uploading, uploaded, processing, completed, failed.
Request
curl "$BASE/projects/123/status" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 123,"status": "processing","progress": 0.45,"progress_percent": 45,"current_step": "Transcription","total_scenes": 8,"total_clips": 0,"processing_complete": false}
List generated clips with AI metadata (hook, clip type, confidence).
Request
curl "$BASE/projects/123/clips" \-H "Authorization: Bearer $KEY"
Response
[{"clip_id": 456,"start_time": 0.0,"end_time": 42.3,"description": "Founder explains the pivot","display_name": null,"status": "ready","source": "Clipmaster","streaming_url": "https://clipmaster-media.s3.amazonaws.com/...mp4?...","features": {"scene_id": 2265,"clip_duration": 42.3,"vision_analysis": {"hook": "The moment everything changed","clip_type": "story","confidence": 0.9}}}]
Full detail for one clip — a superset of the list item: every feature field, the scene's AI analysis, its padding, badge and virality. Every frame, the audio and the words are in GET …/analysis.
Request
curl "$BASE/projects/123/clips/456" \-H "Authorization: Bearer $KEY"
Same shape as the list entry above, with the complete features object.
List your projects.
Request
curl "$BASE/projects" -H "Authorization: Bearer $KEY"
Response
[{"project_id": 123,"filename": "keynote.mp4","status": "completed","created_at": "2026-08-14T20:11:38Z","video_metadata": {"duration_sec": 693.5,"resolution": "1920x1080","detected_orientation": "horizontal"},"is_demo": false}]
Delete a project and all its clips, scenes, and files.
Request
curl -X DELETE "$BASE/projects/123" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 123,"success": true,"message": "Project deleted"}
Transcribe the whole video again, in the background. Clips with their own edited words keep them. Poll the status for project_config.transcription_rerun_status. No tokens; rate-limited.
Request
curl -X POST "$BASE/projects/123/transcription" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 123,"status": "running","progress_url": "/api/v1/projects/123/status"}
Analysis
Read back the analysis, as JSON, to place an edit on what it found. A clip's documents use the viewer's time — the clock of overlays, suggestions and Auto-edit: seconds from the start of its scene. The clip plays from 0 to time.duration; times outside that are the padding around the scene, where overlays can't go. The moments list keeps only what plays, so each of its t can be placed as is; from the full clip analysis, place only times between 0 and time.duration. Video seconds = t + time.scene_start. The video's document uses seconds from the start of the upload. Boxes are {x, y, w, h} fractions of the frame. Reading is free and works on the demo project.
A clip's whole stored analysis. Sections: scene (description, hook, topic, type, score, badge, virality), layout (camera cuts and, per frame, the shot type, faces and screen/webcam panels), audio (loud, quiet, beat, singing; onsets=true adds every hit), transcript (the clip's words, with speakers when known), sound_events (laughter, applause, music), video_moments, talk_scenes and auto_edit (the stored take, with its speed ranges). include picks sections, comma-separated; all by default.
Request
curl "$BASE/projects/123/clips/456/analysis?include=scene,layout,audio" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 123,"clip_id": 456,"scene_id": 2265,"time": {"clock": "viewer","unit": "seconds","note": "0 = scene start, …","duration": 10.0,"scene_start": 100.0,"scene_end": 110.0,"clip_start": -2.0,"clip_end": 12.0},"analyses": {"layout": {"status": "completed","error": null,"step": null,"started_at": "2026-10-22T18:02:11+00:00","finished_at": "2026-10-22T18:02:40+00:00","what": "Faces, screen/webcam/gameplay panels, shot type per frame, camera cuts.","rest": "POST /api/v1/projects/123/clips/456/layout-analysis","mcp": "analyze_clip_layout","cost": "Paid plans (smart crop); no tokens."},"audio_cues": { "status": "completed", "...": "same fields" },"auto_edit": { "status": null, "...": "same fields" }},"scene": {"description": "He grabs the rim and the crowd erupts","hook": "The dunk nobody saw coming","topic": "basketball","clip_type": "reaction","score": 8,"...": "the rest of its stored AI fields","badge": { "label": "Highlight", "key": "highlight", "color": "#FF6B6B" },"virality": { "tier": "hot", "label": "Hot", "color": "#FF4444" }},"layout": {"current": true,"faces_placeable": true,"analyzed_at": "2026-10-22T18:02:40+00:00","model": "gemini-3.5-flash-lite","frame_interval": 2.0,"frame_size": { "width": 1920, "height": 1080 },"cuts": [4.0],"frames": [{ "t": 0.0, "shot": "solo_talking_head","faces": [{ "x": 0.4, "y": 0.1, "w": 0.2, "h": 0.4 }], "panels": [] },{ "t": 4.0, "shot": "screen_share_with_webcam","faces": [{ "x": 0.8, "y": 0.04, "w": 0.12, "h": 0.18 }],"panels": [{ "kind": "webcam_overlay", "x": 0.75, "y": 0.0, "w": 0.25, "h": 0.25,"highlighted": true }] }]},"audio": {"current": true,"measured_at": "2026-10-22T18:01:05+00:00","has_audio": true,"median_active_db": -20.0,"loud": [{ "t": 4.0, "end": 4.5, "over_db": 9.0 }],"quiet": [{ "start": -2.0, "end": -0.5, "level_db": -48.0 }],"beat": [{ "start": 0.0, "end": 10.0, "tempo_bpm": 120.0, "first_beat": 0.1, "period": 0.5 }],"singing": null}}
time and analyses are always there. analyses has one entry per on-demand run (layout, audio_cues, auto_edit): status is running, completed, failed, stale (marked running for over 30 minutes, so it probably died — check again before restarting it) or null (never run), with error, step (the last step reached; meaningful while running), timestamps and the endpoint that starts it. layout, audio and auto_edit are null until they have run; sound_events, video_moments and talk_scenes are [] when nothing was found. current: false means the result no longer matches the clip: its video file was cut again since (a trim alone doesn't change it), for layout also when the latest run didn't complete, for audio also when the measuring itself changed. A trim does make auto_edit current: false, since its times are on the scene's clock; an older take that didn't record its scene reads current: null (unknown). Audio times are converted from the file they were measured on. An unknown section is 400 unknown_section.
The clip as one time-sorted list of {t, kind, source, …}: cut, shot and faces (when the shot or face count changes), speaker turns, sound, loud, quiet, beat, singing and the vision pass's moments. Only what plays is listed — spans cut to 0–time.duration, a shot, face count or speaker set in the padding carried to 0, anything else in the padding left out — so every t goes to POST …/overlays as is. A layout or audio analysis that no longer matches the clip is left out and named in stale. With the scene's summary, which analyses are available, and analyses. Same clock as the clip analysis.
Request
curl "$BASE/projects/123/clips/456/moments" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 123,"clip_id": 456,"time": { "clock": "viewer", "duration": 10.0, "...": "as in …/analysis" },"summary": {"description": "He grabs the rim and the crowd erupts","hook": "The dunk nobody saw coming","topic": "basketball","clip_type": "reaction","score": 8},"available": { "layout": true, "audio": true, "transcript": true, "video_moments": true },"stale": [],"analyses": { "...": "as in …/analysis" },"moments": [{ "t": 0.0, "kind": "beat", "source": "audio", "end": 10.0, "tempo_bpm": 120.0 },{ "t": 0.0, "kind": "faces", "source": "layout", "count": 1 },{ "t": 0.0, "kind": "shot", "source": "layout", "detail": "solo_talking_head" },{ "t": 0.0, "kind": "speaker", "source": "transcript", "speaker": "speaker_0", "first_word": "Here" },{ "t": 1.0, "kind": "moment", "source": "video", "end": 3.0, "detail": "action_peak","score": 8, "description": "Rim grab" },{ "t": 3.0, "kind": "sound", "source": "transcript", "end": 4.0, "detail": "laughter" },{ "t": 4.0, "kind": "cut", "source": "layout" },{ "t": 4.0, "kind": "loud", "source": "audio", "end": 4.5, "over_db": 9.0 },{ "t": 4.0, "kind": "shot", "source": "layout", "detail": "screen_share_with_webcam" }]}
The whole video, on video time: metadata, the transcript by segment with speakers, sound_events, the vision pass's video_moments and the transcript pass's talk_scenes (each with a …_status and a …_pass block: the pass's status and model, plus the vision pass's error and time), and every scene with its hook, score and degraded flag (true: the AI scene step failed and it's an unscored fixed-length fallback). include picks sections; words (every word, with speaker and confidence) comes only when named. analyses.transcription reports a re-transcription.
Request
curl "$BASE/projects/123/analysis" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 123,"time": { "clock": "video", "unit": "seconds", "note": "Seconds from the start of the upload." },"analyses": {"transcription": {"status": null,"...": "same fields as a clip's runs","rest": "POST /api/v1/projects/123/transcription","mcp": "transcribe_video"}},"metadata": { "duration_sec": 693.5, "resolution": "1920x1080", "detected_orientation": "horizontal","...": "the rest of the upload's metadata" },"transcript": {"language": "english","provider": "openai_whisper","duration": 693.5,"no_audio": false,"speakers": [],"segments": [{ "start": 99.0, "end": 106.0, "text": "Here we go.", "speaker": null }]},"sound_events": [{ "t": 103.0, "end": 104.0, "kind": "laughter", "label": "[laughter]","speaker": null, "confidence": null }],"video_moments": [{ "start": 101.0, "end": 103.0, "type": "action_peak", "score": 8,"description": "Rim grab" }],"video_moments_status": "ok","video_moments_pass": { "status": "ok", "error": null, "model": "gemini-3.8-flash","analyzed_at": "2026-10-22T17:40:12+00:00" },"talk_scenes": [{ "start": 95.0, "end": 105.0, "clip_type": "story", "score": 7,"hook": "Why he almost quit", "topic": "basketball","description": "He tells how the season started", "confidence": 0.8 }],"talk_scenes_status": "ok","talk_scenes_pass": { "status": "ok", "model": "gpt-4o-mini" },"scenes": [{ "scene_id": 2265, "start": 100.0, "end": 110.0, "source": "merged","description": "He grabs the rim and the crowd erupts","hook": "The dunk nobody saw coming", "topic": "basketball","clip_type": "reaction", "score": 8, "degraded": false }]}
Measure the clip's audio — loud moments, quiet stretches, beat and tempo, singing — in the background. Poll GET …/analysis until analyses.audio_cues.status is completed or failed; the result is its audio section. Suggest and Auto-edit measure it themselves; call this to read the audio first. 409 audio_cues_running, audio_cues_current (the stored measurement still matches the clip's file) or clip_file_missing. Paid: ai_suggestions; no tokens; shares a rate limit with effects Suggest and Auto-edit.
Request
curl -X POST "$BASE/projects/123/clips/456/audio-cues" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 123,"clip_id": 456,"status": "running"}
Editing
Everything the in-app editor does is scriptable: trim, captions and their translation, layouts, zooms, screen effects, speed, emojis, GIFs, stickers, reaction clips, sound effects, logo, progress bar and orientation.
Two clocks. Overlays you add (POST …/overlays) and every suggestion use the viewer's time — seconds from the start of the clip as it plays. Saved settings keep times against the padded clip file, a few seconds longer at each end; the overlays endpoint converts for you.
Trim — move a clip's start/end. All clips on the scene update automatically.
Request
curl -X PUT "$BASE/projects/123/scenes/2265/boundaries" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"new_start_time": 12.0,"new_end_time": 27.5}
Response
{"scene_id": 2265,"project_id": 123,"old_start_time": 0.0,"old_end_time": 42.3,"new_start_time": 12.0,"new_end_time": 27.5,"message": "Scene boundaries updated successfully."}
Read or update the full editor state: captions (with secondary for a second line), emojis, GIFs, stickers and reaction clips (gifs, told apart by kind), sfx, zoom, screenEffects, framing, logo, progress bar, orientation, speed and speedRanges. PUT replaces the whole object — send back everything GET returned, with your changes; anything left out is cleared.
Request
curl -X PUT "$BASE/projects/123/clips/456/settings" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"orientation": "vertical","captions": {"enabled": true,"style": "hormozi","karaoke": true},"speed": 1.25}
Response
{"clip_id": 456,"settings": {"captions": { "enabled": true, "style": "hormozi", "karaoke": true },"zoom": null,"screenEffects": null,"logo": null,"progressBar": null,"smartCrop": null,"gifs": null,"orientation": "vertical","speed": 1.25,"speedRanges": null}}
A zoom is one shape: {type: "zoom", start, end, scale, glide, target, center} — in at start (glide seconds to get there, 0 snaps), held at scale (1.05–2.5), back at end, or held to the clip's end when end is null. Older punch and slow zooms are converted.
Read or replace caption words with word-level timing.
Request
curl -X PUT "$BASE/projects/123/clips/456/captions" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"words": [{ "text": "Use", "start": 28.06, "end": 28.14 },{ "text": "correct", "start": 28.14, "end": 28.22 }]}
Response
{"words": [{ "text": "Use", "start": 28.06, "end": 28.14, "highlight": null },{ "text": "correct", "start": 28.14, "end": 28.22, "highlight": null }]}
Rename a clip. An empty string clears it back to the generated name.
Request
curl -X PATCH "$BASE/projects/123/clips/456/name" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"name": "Best hook"}
Response
{"clip_id": 456,"display_name": "Best hook"}
Transcribe this clip's audio again and make the result its captions. Replaces edited words; answers with the new words. No tokens; rate-limited.
Request
curl -X POST "$BASE/projects/123/clips/456/transcribe" \-H "Authorization: Bearer $KEY"
Add overlays at the time the viewer sees: emojis, inline_emojis, gifs, stickers, reaction_clips, sfx, zooms, screen_effects and speed_ranges. The editor's time conversion and defaults are applied for you, and AI suggestions can be sent as they came. It only adds.
Request
curl -X POST "$BASE/projects/123/clips/456/overlays" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"emojis": [{ "emoji": "🔥", "timestamp": 12.0 }],"inline_emojis": [{ "emoji": "💰", "word_index": 5 }],"gifs": [{ "source_id": "k1", "slug": "mind-blown", "mp4_url": "https://…","width": 480, "height": 270, "timestamp": 8.0 }],"sfx": [{ "sfx_id": 3, "timestamp": 3.0, "volume": 0.4 }],"zooms": [{ "start": 20.0, "length": 1.0, "scale": 1.3 }],"screen_effects": [{ "look": "flash", "start": 20.0 }],"speed_ranges": [{ "start": 31.0, "length": 2.5, "speed": 2.0 }]}
Response
{"clip_id": 456,"settings": {"emojis": [{ "type": "floating", "emoji": "🔥", "timestamp": 14.0, "duration": 1.0,"position": { "x": 0.5, "y": 0.694 }, "scale": 1.0 }],"zoom": [{ "type": "zoom", "start": 22.0, "end": 23.0, "scale": 1.3, "glide": 0,"target": "frame", "center": { "x": 0.5, "y": 0.5 } }]}}
A clip holds up to 5 GIFs, 5 stickers and 5 reaction clips. spot on an emoji or sticker follows the main face, and target: "face" on a zoom zooms on it — paid (smart_crop), after the clip's layout analysis.
A second caption line in another language. GET lists the languages this clip already has and the supported ones; POST {"language": "es"} translates it — 1 token, free when the clip already has it. Paid: caption_translation. It shows once captions.secondary is set in the settings.
Request
curl -X POST "$BASE/projects/123/clips/456/translations" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d '{"language": "es"}'
Response
{"project_id": 123,"language": "es","lines": [{ "text": "Usa la palabra correcta", "start": 28.06, "end": 29.4 }],"created": true,"tokens_charged": 1}
Analyze what each moment of the clip shows — shot types, faces, screen and webcam panels — in the background. Needed before overlays can follow a face. Poll the clip for features.vision_analysis.layout_rerun_status, or GET …/analysis for analyses.layout.status; every frame is its layout section. Paid: smart_crop; no tokens.
Request
curl -X POST "$BASE/projects/123/clips/456/layout-analysis" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 123,"clip_id": 456,"status": "running"}
Upload (multipart, field file) or remove a per-clip logo. Without a browser, stage it with POST /uploads/assets and attach it at …/logo/from-upload.
Request
curl -X POST "$BASE/projects/123/clips/456/logo/from-upload" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d '{"asset_key": "originals/staging/42/9f1c2b7e.png"}'
AI suggestions & Auto-edit
Suggestions are read-only: they return placements on the viewer's time, ready to send to POST …/overlays as they came. Paid: ai_suggestions. No tokens; rate-limited.
Suggestions for one clip. kind is gifs, stickers, reaction-clips, emojis, sfx, effects (zoom and screen-effect recipes) or speed (dead air sped up, slow motion). Query param max_suggestions.
Request
curl -X POST "$BASE/projects/123/clips/456/gifs/suggest?max_suggestions=3" \-H "Authorization: Bearer $KEY"
Response
{"clip_id": 456,"suggestions": [{"source_id": "k1","slug": "mind-blown","title": "Mind Blown","mp4_url": "https://…","width": 480,"height": 270,"preview_url": "https://…","timestamp": 8.0,"duration": 2.0,"query": "mind blown","reason": "Reacts to the reveal"}]}
effects and speed answer with audio_cues_ready: false while the clip's audio is still being measured — ask again a few seconds later. A speed suggestion is {start, end, speed}: send it with length = end − start.
Plan a whole edit for the clip — zooms, screen effects, stickers, sounds and speed ranges — in the background. Poll the clip for features.vision_analysis.auto_edit_rerun_status; the edit is features.vision_analysis.auto_edit ({effects, stickers, sfx, speed}, also the auto_edit section of GET …/analysis). Nothing is applied: send its parts to POST …/overlays. Paid: ai_suggestions; no tokens.
Request
curl -X POST "$BASE/projects/123/clips/456/auto-edit" \-H "Authorization: Bearer $KEY"
Response
{"project_id": 123,"clip_id": 456,"status": "running"}
Export
Render a clip with its saved settings. Returns an export id to poll; charges tokens.
Request
curl -X POST "$BASE/projects/123/clips/456/export" \-H "Authorization: Bearer $KEY"
Response
{"export_id": 37,"status": "rendering","message": "Export render triggered"}
Poll an export. When it completes, download_url is a temporary link that expires — fetch the file promptly.
Request
curl "$BASE/exports/37/status" \-H "Authorization: Bearer $KEY"
Response
{"export_id": 37,"status": "completed","download_url": "https://clipmaster-media.s3.amazonaws.com/...mp4?...&Expires=...","created_at": "2026-08-14T21:47:35Z","completed_at": "2026-08-14T21:48:42Z"}
List your exports, newest first. Filter with clip_id or project_id. This is how you download a clip: take the newest export with status completed and fetch its download_url.
Request
curl "$BASE/exports?clip_id=456" \-H "Authorization: Bearer $KEY"
Response
{"exports": [{"export_id": 37,"clip_id": 456,"project_id": 123,"status": "completed","progress": null,"export_number": 2,"tokens_charged": 1,"clip_duration_sec": 42.3,"download_url": "https://clipmaster-media.s3.amazonaws.com/...mp4?...&Expires=...","created_at": "2026-08-14T21:47:35Z","completed_at": "2026-08-14T21:48:42Z"}],"total": 2,"limit": 20,"offset": 0}
Compilations
A compilation is one edit built from clips of any of your videos. Creating, arranging and exporting one is paid: compilation.
List your compilations (cross-project clip collections).
Request
curl "$BASE/compilations" -H "Authorization: Bearer $KEY"
Response
{"items": [{"id": 10,"name": "Funny Personal Anecdotes","clip_count": 5,"total_duration_sec": 149.9,"output": { "width": 1920, "height": 1080, "fps": 30 },"format": null,"latest_export": null,"created_at": "2026-08-10T19:05:41Z"}]}
Create an empty compilation. Paid: compilation.
Request
curl -X POST "$BASE/compilations" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"name": "Best of March","output": { "width": 1080, "height": 1920, "fps": 30 }}
Read one compilation — its clips, estimated_tokens for an export, the edit plan in arrangement_metadata.edit_plan and the files it plays in media — or change its name, output, logo_config or progress_bar_config.
Request
curl "$BASE/compilations/10" -H "Authorization: Bearer $KEY"
What deleting would take with it: uploads that exist only for this compilation, and pending posts.
Request
curl "$BASE/compilations/10/deletion-preview" -H "Authorization: Bearer $KEY"
Response
{"compilation_id": 10,"source_project_ids": [],"scheduled_posts_pending": 0}
Delete a compilation; its pending posts are cancelled. delete_sources=true also deletes the uploads from the preview; pass them as expected_project_ids and the delete is refused (409) if that set changed.
Request
curl -X DELETE "$BASE/compilations/10" \-H "Authorization: Bearer $KEY"
Response
{"compilation_id": 10,"deleted_project_ids": [],"scheduled_posts_cancelled": 0}
Your clips across all videos, to add. Query params q, limit, offset.
Request
curl "$BASE/compilations/available-clips?q=pivot&limit=50" \-H "Authorization: Bearer $KEY"
Add a clip (clip_id, optional position). DELETE /compilations/{id}/clips/{compilation_clip_id} removes one.
Request
curl -X POST "$BASE/compilations/10/clips" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d '{"clip_id": 456}'
More clips from your library that fit this compilation, best first.
Request
curl -X POST "$BASE/compilations/10/clips/suggest" \-H "Authorization: Bearer $KEY"
Compilation ideas from your library for a brief. Turn one into a compilation with POST /compilations/from-suggestion (name, clip_ids).
Request
curl -X POST "$BASE/compilations/suggest" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d '{"prompt": "funny moments", "max_suggestions": 3}'
Response
{"suggestions": [{"theme": "Funny moments","reason": "Five clips where the audience laughs","clip_ids": [456, 457, 461],"total_duration_sec": 94.2}]}
Render the compilation. Charges tokens for the source seconds it plays (estimated_tokens on the compilation).
Request
curl -X POST "$BASE/compilations/10/export" \-H "Authorization: Bearer $KEY"
Response
{"export_id": 5,"compilation_id": 10,"status": "pending","estimated_tokens": 3,"message": "Export started"}
Poll a compilation export; download_url works as for a clip export. GET /compilations/{id}/exports lists them.
Request
curl "$BASE/compilations/10/exports/5/status" \-H "Authorization: Bearer $KEY"
Response
{"export_id": 5,"compilation_id": 10,"status": "completed","progress": null,"download_url": "https://clipmaster-media.s3.amazonaws.com/...mp4?...&Expires=...","created_at": "2026-08-14T21:47:35Z","completed_at": "2026-08-14T21:50:02Z"}
Compilation editing
A compilation is an edit plan: scenes of one to three panes (a clip or a file) with transitions and text cards, plus overlays, music and a voiceover over the whole edit. Times are seconds into the compilation.
Re-cut the compilation with AI into a new plan of scenes. format: custom (a montage; needs prompt), best_of, countdown, trailer, hook_loop, tutorial, qa_highlights or auto. Replaces the scenes and the emojis and GIFs pinned to them; captions, music and narration stay.
Request
curl -X POST "$BASE/compilations/10/arrange" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"format": "hook_loop"}
Response
{"id": 10,"name": "Funny Personal Anecdotes","format": "hook_loop","total_duration_sec": 58.2,"estimated_tokens": 1,"clips": [{ "compilation_clip_id": 88, "position": 0, "clip_id": 456, "duration_sec": 12.4, "missing": false }],"arrangement_metadata": {"resolved_format": "hook_loop","degraded": false,"edit_plan": {"version": "2.0","scenes": [{"id": "1","layout": "full","sources": [{ "clip_id": 456, "slot": 0, "audio": "primary","start_offset_sec": 3.0, "end_offset_sec": 9.5, "speed": 1.0 }],"text": []}]}}}
degraded: true means the AI step failed and the clips were kept in order.
plan/undo goes back to before the last arrange, reset or place (again redoes); plan/reset makes a plain cut, every clip whole; plan/place-added-clips works clips added since the AI edit into it. No body.
Request
curl -X POST "$BASE/compilations/10/plan/undo" \-H "Authorization: Bearer $KEY"
Re-cut one scene with AI, leaving the rest. Optional prompt. Scene ids are text.
Request
curl -X POST "$BASE/compilations/10/scenes/3/regenerate" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d '{"prompt": "use the boat clip instead"}'
Give one scene a beat pattern: short muted repeats of its clip on the music's beats, right before it. pattern: stutter, quick_stutter, build_up, jump_cut, teaser, or null to take it out. Needs measured music (409 music_not_measured until then).
Request
curl -X PUT "$BASE/compilations/10/scenes/3/beat-pattern" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d '{"pattern": "stutter"}'
Replace the edit plan. Layouts: full (1 pane), stack2 / cols2 / pip (2), stack3 / cols3 (3). A source plays a clip_id or a media_key; its offsets are the window's start and end in seconds into the clip's scene; at most one source per scene is primary audio. Transitions: cut, fade, slide, wipe. Overlays left out are kept; music left out is removed.
Request
curl -X PUT "$BASE/compilations/10/plan" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"version": "2.0","scenes": [{"id": "1","layout": "full","sources": [{ "clip_id": 456, "audio": "primary","start_offset_sec": 2.0, "end_offset_sec": 14.5 }],"text": [{ "text": "Day one", "role": "title", "start_ms": 0, "duration_ms": 2000 }]},{"id": "2","layout": "stack2","transition_in": { "kind": "fade", "duration_ms": 400 },"sources": [{ "clip_id": 457, "slot": 0, "audio": "primary", "speed": 1.5 },{ "clip_id": 458, "slot": 1 }]}],"music": { "track_id": "a1b2c3", "volume": 0.3 }}
POST adds emojis, GIFs, stickers, zooms, screen effects and sound effects over the whole edit, in the clip overlays' shapes. PATCH replaces a stored set whole instead (captions, emojis, gifs, screenEffects, zoom, sfx); only what you send changes.
Request
curl -X POST "$BASE/compilations/10/plan/overlays" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"emojis": [{ "emoji": "🎉", "timestamp": 3.0 }],"screen_effects": [{ "look": "bw", "start": 10.0, "length": 2.0 }]}
Response
{"id": 10,"arrangement_metadata": {"edit_plan": {"emojis": [{ "type": "floating", "emoji": "🎉", "timestamp": 3.0, "duration": 1.0,"position": { "x": 0.5, "y": 0.694 }, "scale": 1.0 }]}}}
Edit the whole compilation in one go and apply it, filling only what is empty: how each scene arrives (any available transition), text cards (words, font, size, position, timing), a library track, speed ranges on clips at normal speed and the progress bar, plus zooms, screen effects, stickers, GIFs, emojis, sound effects and up to 2 reaction-clip scenes. direction (optional, up to 300 characters) steers all of it and is kept for the next run. Send the returned take to …/auto-edit/undo to take it out, or back here as replace_take to swap it for a new one. Paid: compilation; no tokens.
Request
curl -X POST "$BASE/compilations/10/auto-edit" \-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \-d '{"direction": "hype energy, big bold text"}'
Response
{"compilation": { "id": 10, "...": "as GET /compilations/{id}" },"take": {"scene_ids": ["r1"],"effect_ids": ["e1", "e2"],"sticker_ids": ["g1"],"emoji_ids": ["m1"],"zoom_ids": ["z1"],"sfx_ids": ["s1"],"transitions": [{ "scene_id": "2", "kind": "slide", "duration_ms": 400 }],"text_cards": [{ "scene_id": "1", "text": "Best of the year" }],"music_track_id": "lib-42","speed_ranges": [{ "scene_id": "2", "slot": 0, "start": 24.0, "end": 25.2, "speed": 0.5 }],"progress_bar": true},"counts": { "effects": 2, "stickers": 0, "gifs": 1, "emojis": 1,"reaction_clips": 1, "zooms": 1, "sounds": 1, "transitions": 1,"texts": 1, "music": 1, "speed_ranges": 1, "progress_bar": 1 }}
The edit's measured audio on its own clock: the music's beat (or an audible clip's), loud moments, quiet stretches and singing; onsets=true adds every onset. Each audio is measured once, in the background, the first time it is read or joins the edit — sources says which are measured and measuring stays true until all are. Party lights take beat_first, beat_period and beat_clock from it. Paid: compilation; no tokens.
Request
curl "$BASE/compilations/10/audio" \-H "Authorization: Bearer $KEY"
Response
{"compilation_id": 10,"duration_sec": 37.4,"measuring": false,"sources": [{ "kind": "music", "status": "measured", "track_id": "lib-42" },{ "kind": "clip", "status": "measured", "scene_id": "1", "clip_id": 1621 }],"beat": [{ "start": 12.0, "end": 37.4, "first_beat": 12.02, "period": 0.38,"tempo_bpm": 157.9, "clock": "edit" }],"loud": [{ "t": 33.0, "end": 33.4, "over_db": 11.0 }],"quiet": [{ "start": 3.4, "end": 9.0, "level_db": -38.0 }],"singing": [],"onsets": null}
Add a scene that plays a file instead of a clip: a Klipy reaction clip (klipy) or an uploaded video or image (upload_key). It goes after after_scene_id, or last; into_scene_id makes it one more pane beside that scene's clips. Uploaded video counts toward the export's tokens; reaction clips and images are free. Paid: compilation.
Request
curl -X POST "$BASE/compilations/10/media-scenes" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"klipy": { "source_id": "c3", "slug": "emotional-damage","title": "Emotional Damage", "mp4_url": "https://…" },"after_scene_id": "2"}
A voiceover over the whole edit, transcribed for captions. POST uploads it (multipart, field file) or attaches a staged one at …/narration/from-upload; PATCH sets volume and duck; DELETE removes it.
Request
curl -X POST "$BASE/compilations/10/narration/from-upload" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d '{"asset_key": "originals/staging/42/5d8e1a.mp3"}'
Response
{"url": "https://clipmaster-media.s3.amazonaws.com/...mp3?...","volume": 1.0,"duck": true,"duration_sec": 61.4,"word_count": 152}
Tracks that fit the edit, each with a reason. Take one with POST /music/tracks/{track_id}/select, then set music.track_id in the plan.
Request
curl -X POST "$BASE/compilations/10/music/suggest" \-H "Authorization: Bearer $KEY"
Suggestions for the whole edit, on its own seconds: kind is gifs, stickers or emojis. Send them to POST …/plan/overlays. Paid: compilation.
Request
curl -X POST "$BASE/compilations/10/emojis/suggest" \-H "Authorization: Bearer $KEY"
Response
{"compilation_id": 10,"suggestions": [{ "emoji": "😂", "timestamp": 14.2, "duration": 1.5, "reason": "The punchline lands" }]}
Title and label lines for one scene.
Request
curl -X POST "$BASE/compilations/10/scenes/1/text/suggest" \-H "Authorization: Bearer $KEY"
Response
{"suggestions": [{ "text": "Day one", "role": "title" }]}
Scheduling
Publish clips and compilations to a connected channel. Scheduling needs the social-scheduling entitlement.
Your connected channels. Connect them in the web app first.
Request
curl "$BASE/social/accounts" -H "Authorization: Bearer $KEY"
Response
{"platforms_available": ["youtube"],"accounts": [{"id": 2,"platform": "youtube","account_name": "MyChannel","status": "active"}]}
Schedule a clip or compilation export to publish. Idempotent — a retry with the same account, export, and time returns the existing post.
Request
curl -X POST "$BASE/scheduled-posts" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d @body.json
Request body
{"social_account_id": 2,"clip_id": 456,"export_id": 37,"scheduled_at": "2026-08-20T16:00:00Z","title": "The pivot that saved us","hashtags": ["startup", "founder"],"privacy": "public"}
Response
{"id": 12,"clip_id": 456,"export_id": 37,"social_account_id": 2,"platform": "youtube","account_name": "MyChannel","status": "scheduled","scheduled_at": "2026-08-20T16:00:00","title": "The pivot that saved us"}
Calendar range of scheduled and published posts.
Request
curl "$BASE/scheduled-posts?from_date=2026-08-01T00:00:00Z&to_date=2026-08-31T23:59:59Z" \-H "Authorization: Bearer $KEY"
Response
{"posts": [{"id": 12,"status": "scheduled","scheduled_at": "2026-08-20T16:00:00","title": "The pivot that saved us"}]}
Retime or edit a post before it publishes (scheduled_at, title, description, hashtags, privacy), or cancel it.
Request
curl -X PATCH "$BASE/scheduled-posts/12" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d '{"scheduled_at": "2026-08-21T16:00:00Z"}'
AI title, description and hashtags from a clip's transcript.
Request
curl -X POST "$BASE/scheduled-posts/metadata-suggest" \-H "Authorization: Bearer $KEY" \-H "Content-Type: application/json" \-d '{"clip_id": 456, "platform": "youtube"}'
Response
{"title": "The pivot that saved us","description": "How one decision turned the company around.","hashtags": ["startup", "founder"]}
Disconnect a channel. Its post history, pending and published, goes with it.
Request
curl -X DELETE "$BASE/social/accounts/2" \-H "Authorization: Bearer $KEY"
Library
List the 15 caption styles and their defaults.
Request
curl "$BASE/export/styles" \-H "Authorization: Bearer $KEY"
Response
[{"key": "clean","name": "Clean","category": "social","fontFamily": "Inter","fontSize": 56,"color": "#FFFFFF","highlightColor": "#9DC34F"}]
The sound-effect library; its ids go in sfx overlays. Optional category.
Request
curl "$BASE/sfx" -H "Authorization: Bearer $KEY"
Search Klipy, or list what's trending. kind is gif, sticker or clip (reaction clips). Place a result with id as source_id and mp4.url as mp4_url; a sticker also needs image.url as image_url.
Request
curl "$BASE/gifs/search?q=mind%20blown&kind=sticker&per_page=10" \-H "Authorization: Bearer $KEY"
Response
{"items": [{"id": "s9","slug": "mind-blown-sticker","title": "Mind Blown","kind": "sticker","mp4": { "url": "https://….webm", "width": 320, "height": 320 },"preview": { "url": "https://….webp", "width": 160, "height": 160 },"image": { "url": "https://….webp", "width": 320, "height": 320 }}],"page": 1,"per_page": 10,"has_next": true}
Search background music. Optional genre, duration_min, duration_max.
Request
curl "$BASE/music/search?q=upbeat" -H "Authorization: Bearer $KEY"
Take a track into your library so a compilation can use it.
Request
curl -X POST "$BASE/music/tracks/a1b2c3/select" \-H "Authorization: Bearer $KEY"
Account
Your token balance: total, reserved by running jobs, and available.
Request
curl "$BASE/billing/balance" -H "Authorization: Bearer $KEY"
Response
{"balance": 120,"reserved": 3,"available": 117,"total_purchased": 200,"total_spent": 80}
What you used over the last period days (1–365).
Request
curl "$BASE/billing/usage?period=30" -H "Authorization: Bearer $KEY"
Response
{"period_days": 30,"tokens_spent": 42,"tokens_purchased": 0,"uploads_count": 6,"exports_count": 11,"minutes_processed": 38.5,"minutes_exported": 7.9}
Errors
Errors carry a machine-readable code:
{"detail": {"error": "insufficient_tokens","message": "Not enough tokens for this operation"}}
Common codes: api_access_required / session_auth_required (403), subscription_required (403, names the plan feature), insufficient_tokens (402), export_in_progress / export_expired (409). A bad or revoked key returns 401; over a rate limit returns 429.
Token cost
Metered exactly like the app — 1 token = 1 source minute for processing an upload or a YouTube import; exports charge per the app's rules (a compilation by the source seconds it plays); a clip caption translation costs 1 token. Suggestions, Auto-edit, arranging, layout analysis, audio measurement and re-transcribing spend no tokens. There is no separate API billing.