API Reference
AI Clipping API
Create AI-selected or exact-segment clips from a video URL, render them through Subclip, and download the final MP4.
For current credit costs, see API credit costs.
OpenAPI-style reference
API endpoints
Create AI-selected or exact-segment clips and return final rendered clip URLs.
/api/v1are auto-cleaned and do not count toward the user's storage quota./api/v1/ai-clipping/uploadsCreate source or transcript upload URL
Creates a signed upload URL for source video or optional SRT transcript.
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
uploadType | source | transcript_srt | No | Upload target type |
fileName | string | Yes | File name body |
contentType | string | No | Video or SRT content type body |
fileSize | number | Yes | Declared size in bytes body |
Examples
Request
{
"uploadType": "source",
"fileName": "interview.mp4",
"contentType": "video/mp4",
"fileSize": 52428800
}Response
{
"uploadUrl": "https://...",
"sourceObjectKey": "user_.../ai-clipping-api/sources/abc123-interview.mp4",
"expiresIn": 900
}Responses
| Status | Description |
|---|---|
200 | Request succeeded |
400 | Invalid request body or unsupported parameter |
401 | Missing, invalid, or revoked API key |
429 | Rate limit exceeded |
500 | Unexpected processing error |
/api/v1/ai-clipping/jobsStart AI clipping
Starts AI analysis mode or exact segment mode and renders final clip URLs.
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
videoUrl | string | No | YouTube, public, signed S3/R2, or downloadable URL. Required unless sourceObjectKey is provided body |
sourceObjectKey | string | No | Uploaded source object key. Required unless videoUrl is provided |
language | string | No | Required when no transcript input is provided |
youtubeTranscript | boolean | No | For YouTube URLs, try YouTube transcript first. Adds 1 credit and falls back to ASR |
aiAnalysis | boolean | No | Let AI choose clips. Set false when sending exact segments |
preset | default | face_track | customised | aura | No | Render preset |
aspectRatio | portrait | square | No | Square is not supported for face_track |
dynamicCaptions | boolean | No | When false, no captions are rendered |
numberOfClips | number | No | AI analysis mode only, 1-12 |
preferredDurationSeconds | number | No | AI analysis hint, 10-120 seconds |
styleInstructions | string | No | AI analysis instructions, max 600 characters |
segments | array | No | Exact clipping segments when aiAnalysis is false bodyExact segments |
Examples
Request
{
"videoUrl": "https://www.youtube.com/watch?v=...",
"language": "en",
"youtubeTranscript": true,
"aiAnalysis": true,
"preset": "default",
"aspectRatio": "portrait",
"dynamicCaptions": true,
"numberOfClips": 6,
"preferredDurationSeconds": 45
}Response
{
"projectId": "rproj_...",
"status": "queued",
"runId": "run_...",
"estimatedCredits": 9,
"statusUrl": "/api/v1/.../jobs/rproj_...",
"downloadUrl": "/api/v1/.../jobs/rproj_.../download"
}Responses
| Status | Description |
|---|---|
200 | Request succeeded |
400 | Invalid request body or unsupported parameter |
401 | Missing, invalid, or revoked API key |
429 | Rate limit exceeded |
500 | Unexpected processing error |
/api/v1/ai-clipping/jobs/{projectId}Get AI clipping job status
Returns job progress and multiple rendered clip download URLs once complete.
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
projectId | string | Yes | AI clipping project ID path |
Examples
Response
{
"projectId": "rproj_...",
"status": "completed",
"outputReady": true,
"clips": [
{
"startTimeSeconds": 83.2,
"endTimeSeconds": 126.8,
"textHook": "This changed the launch",
"reason": "Clear reveal",
"downloadUrl": "https://..."
},
{
"startTimeSeconds": 242.5,
"endTimeSeconds": 289.1,
"textHook": "Most teams miss this",
"reason": "Practical payoff",
"downloadUrl": "https://..."
}
]
}Responses
| Status | Description |
|---|---|
200 | Request succeeded |
400 | Invalid request body or unsupported parameter |
401 | Missing, invalid, or revoked API key |
429 | Rate limit exceeded |
500 | Unexpected processing error |
/api/v1/ai-clipping/jobs/{projectId}/downloadCreate AI clipping download URLs
Returns refreshed signed URLs for all rendered clips.
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
projectId | string | Yes | AI clipping project ID path |
Examples
Response
{
"projectId": "rproj_...",
"clips": [
{
"downloadUrl": "https://...",
"expiresAt": "2026-06-19T15:00:00.000Z"
}
]
}Responses
| Status | Description |
|---|---|
200 | Request succeeded |
400 | Invalid request body or unsupported parameter |
401 | Missing, invalid, or revoked API key |
429 | Rate limit exceeded |
500 | Unexpected processing error |
Upload source video to Subclip
This step is optional. It is useful when you do not want to manage S3 storage infrastructure and want Subclip to handle direct client-side uploads.
1. Create an upload URL
curl -X POST https://www.subclip.app/api/v1/ai-clipping/uploads \
-H "Authorization: Bearer $SUBCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"uploadType": "source",
"fileName": "interview.mp4",
"contentType": "video/mp4",
"fileSize": 52428800
}'{
"uploadUrl": "https://...",
"method": "PUT",
"sourceObjectKey": "user_.../ai-clipping-api/sources/abc123-interview.mp4",
"sourceObjectName": "interview.mp4",
"sourceObjectMimeType": "video/mp4",
"expiresIn": 900
}2. Upload the file
curl -X PUT "$SOURCE_UPLOAD_URL" \ -H "Content-Type: video/mp4" \ -H "Content-Length: 52428800" \ --data-binary "@interview.mp4"
3. Start the job with sourceObjectKey
curl -X POST https://www.subclip.app/api/v1/ai-clipping/jobs \
-H "Authorization: Bearer $SUBCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sourceObjectKey": "user_.../ai-clipping-api/sources/abc123-interview.mp4",
"sourceObjectName": "interview.mp4",
"sourceObjectMimeType": "video/mp4",
"language": "en",
"preset": "default",
"aspectRatio": "portrait",
"numberOfClips": 6
}'Start a job
Use a public or signed source URL. YouTube URLs are imported by Subclip before processing. If you do not provide a transcript, pass language so Subclip can transcribe the source audio.
For YouTube URLs, set youtubeTranscript to true when you want Subclip to try YouTube captions first. If that fetch fails, the job falls back to Subclip transcription.
Use aiAnalysis true to let Subclip choose clips. Omit numberOfClips and preferredDurationSeconds for automatic selection, or pass numberOfClips from 1-12 and preferredDurationSeconds from 10-120 when you want a specific count and duration hint.
curl -X POST https://www.subclip.app/api/v1/ai-clipping/jobs \
-H "Authorization: Bearer $SUBCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"videoUrl": "https://www.youtube.com/watch?v=...",
"projectName": "Podcast clips",
"language": "en",
"youtubeTranscript": true,
"aiAnalysis": true,
"preset": "default",
"aspectRatio": "portrait",
"dynamicCaptions": true,
"numberOfClips": 6,
"preferredDurationSeconds": 45,
"styleInstructions": "Prioritize controversial takes, quick emotional hooks, and complete payoff endings.",
"addTextHook": true,
"addCtaText": true
}'{
"projectId": "rproj_...",
"status": "queued",
"runId": "run_...",
"statusUrl": "/api/v1/ai-clipping/jobs/rproj_...",
"downloadUrl": "/api/v1/ai-clipping/jobs/rproj_.../download"
}Input and output schema
The API returns the rendered clip montage and individual rendered MP4 URLs for each selected segment. It does not return the original source video URL.
Input
{
"videoUrl": "string optional, required unless sourceObjectKey",
"sourceObjectKey": "string optional, required unless videoUrl",
"sourceObjectName": "string optional",
"sourceObjectMimeType": "string optional",
"projectName": "string optional",
"language": "string required when no transcript input is provided",
"youtubeTranscript": "boolean optional, YouTube URLs only, default false",
"aiAnalysis": "boolean optional, inferred from segments when omitted",
"preset": "face_track | default | customised | aura",
"aspectRatio": "portrait | square",
"dynamicCaptions": "boolean optional, default true",
"numberOfClips": "number optional, 1-12, AI analysis mode only",
"maxClips": "number optional legacy alias for numberOfClips, 1-12",
"preferredDurationSeconds": "number optional, 10-120, AI analysis mode only",
"styleInstructions": "string optional, max 600 chars, AI analysis mode only",
"textHook": "string optional",
"ctaText": "string optional",
"addTextHook": "boolean optional",
"addCtaText": "boolean optional",
"transcript": {
"text": "string optional",
"segments": [
{
"start": "number",
"end": "number",
"text": "string",
"words": [
{ "word": "string", "start": "number", "end": "number" }
]
}
]
},
"transcriptSrtUrl": "string optional",
"transcriptSrtObjectKey": "string optional",
"segments": [
{
"startTimeSeconds": "number",
"endTimeSeconds": "number",
"textHook": "string optional",
"ctaText": "string optional",
"addTextHook": "boolean optional",
"addCtaText": "boolean optional"
}
]
}Status response
{
"projectId": "rproj_...",
"projectName": "Podcast clips",
"status": "queued | processing | completed | failed",
"progress": 100,
"label": "AI clipping video ready",
"outputReady": true,
"clips": [
{
"startTimeSeconds": 83.2,
"endTimeSeconds": 126.8,
"outputStartTimeSeconds": 0,
"outputEndTimeSeconds": 43.6,
"textHook": "This changed the whole launch",
"ctaText": "Follow for the full breakdown",
"reason": "Clear pain point with a strong reveal",
"score": 92,
"downloadUrl": "https://...",
"downloadUrlExpiresAt": "2026-06-19T15:00:00.000Z",
"fileSize": 18345678,
"contentType": "video/mp4"
},
{
"startTimeSeconds": 242.5,
"endTimeSeconds": 289.1,
"outputStartTimeSeconds": 44.4,
"outputEndTimeSeconds": 91,
"textHook": "Most teams miss this step",
"ctaText": "Save this before your next launch",
"reason": "Practical takeaway with a clear before and after",
"score": 88,
"downloadUrl": "https://...",
"downloadUrlExpiresAt": "2026-06-19T15:00:00.000Z",
"fileSize": 20123456,
"contentType": "video/mp4"
}
],
"creditsUsed": 6,
"aiAnalysis": true,
"transcriptSource": "youtube | asr | provided | srt_upload | srt_url | null",
"youtubeTranscript": true,
"youtubeTranscriptStatus": "queued | fetching | fetched | failed_fallback_asr | disabled | null",
"youtubeTranscriptCreditsUsed": 1,
"youtubeTranscriptError": null,
"errorMessage": null,
"runId": "run_...",
"renderId": "render_...",
"createdAt": "2026-06-19T14:00:00.000Z",
"updatedAt": "2026-06-19T14:20:00.000Z"
}Download response
{
"projectId": "rproj_...",
"downloadUrl": "https://...",
"expiresAt": "2026-06-19T15:00:00.000Z",
"expiresIn": 3600,
"mediaType": "video",
"contentType": "video/mp4",
"fileSize": 73456789,
"fileName": "Podcast clips.mp4",
"clips": [
{
"startTimeSeconds": 83.2,
"endTimeSeconds": 126.8,
"outputStartTimeSeconds": 0,
"outputEndTimeSeconds": 43.6,
"textHook": "This changed the whole launch",
"ctaText": "Follow for the full breakdown",
"reason": "Clear pain point with a strong reveal",
"score": 92,
"downloadUrl": "https://...",
"downloadUrlExpiresAt": "2026-06-19T15:00:00.000Z",
"fileSize": 18345678,
"contentType": "video/mp4"
},
{
"startTimeSeconds": 242.5,
"endTimeSeconds": 289.1,
"outputStartTimeSeconds": 44.4,
"outputEndTimeSeconds": 91,
"textHook": "Most teams miss this step",
"ctaText": "Save this before your next launch",
"reason": "Practical takeaway with a clear before and after",
"score": 88,
"downloadUrl": "https://...",
"downloadUrlExpiresAt": "2026-06-19T15:00:00.000Z",
"fileSize": 20123456,
"contentType": "video/mp4"
}
]
}Supported ASR languages
When no transcript input is provided, language is required. auto is accepted when you want language detection.
| Code | Language |
|---|---|
| auto | Auto-detect |
| af | Afrikaans |
| sq | Albanian |
| am | Amharic |
| ar | Arabic |
| hy | Armenian |
| as | Assamese |
| az | Azerbaijani |
| ba | Bashkir |
| eu | Basque |
| be | Belarusian |
| bn | Bengali |
| bs | Bosnian |
| br | Breton |
| bg | Bulgarian |
| ca | Catalan |
| zh | Chinese |
| hr | Croatian |
| cs | Czech |
| da | Danish |
| nl | Dutch |
| en | English |
| et | Estonian |
| fo | Faroese |
| fi | Finnish |
| fr | French |
| gl | Galician |
| ka | Georgian |
| de | German |
| el | Greek |
| gu | Gujarati |
| ht | Haitian Creole |
| ha | Hausa |
| haw | Hawaiian |
| he | Hebrew |
| hi | Hindi |
| hu | Hungarian |
| is | Icelandic |
| id | Indonesian |
| it | Italian |
| ja | Japanese |
| jw | Javanese |
| kn | Kannada |
| kk | Kazakh |
| km | Khmer |
| ko | Korean |
| lo | Lao |
| la | Latin |
| lv | Latvian |
| ln | Lingala |
| lt | Lithuanian |
| lb | Luxembourgish |
| mk | Macedonian |
| mg | Malagasy |
| ms | Malay |
| ml | Malayalam |
| mt | Maltese |
| mi | Maori |
| mr | Marathi |
| mn | Mongolian |
| my | Myanmar |
| ne | Nepali |
| no | Norwegian |
| nn | Nynorsk |
| oc | Occitan |
| ps | Pashto |
| fa | Persian |
| pl | Polish |
| pt | Portuguese |
| pa | Punjabi |
| ro | Romanian |
| ru | Russian |
| sa | Sanskrit |
| sr | Serbian |
| sn | Shona |
| sd | Sindhi |
| si | Sinhala |
| sk | Slovak |
| sl | Slovenian |
| so | Somali |
| es | Spanish |
| su | Sundanese |
| sw | Swahili |
| sv | Swedish |
| tl | Tagalog |
| tg | Tajik |
| ta | Tamil |
| tt | Tatar |
| te | Telugu |
| th | Thai |
| bo | Tibetan |
| tr | Turkish |
| tk | Turkmen |
| uk | Ukrainian |
| ur | Urdu |
| uz | Uzbek |
| vi | Vietnamese |
| cy | Welsh |
| yi | Yiddish |
| yo | Yoruba |
Upload an SRT transcript
If your transcript is a local SRT file, upload it to Subclip storage first, then pass the returned transcriptSrtObjectKey when starting the job.
curl -X POST https://www.subclip.app/api/v1/ai-clipping/uploads \
-H "Authorization: Bearer $SUBCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"uploadType": "transcript_srt",
"fileName": "podcast.srt",
"contentType": "text/plain",
"fileSize": 18432
}'{
"uploadUrl": "https://...",
"method": "PUT",
"transcriptSrtObjectKey": "user_.../ai-clipping-api/transcripts/abc123-podcast.srt",
"expiresIn": 900
}curl -X PUT "$TRANSCRIPT_UPLOAD_URL" \ -H "Content-Type: text/plain" \ --data-binary "@podcast.srt"
Exact segments
Pass aiAnalysis false and segments when you already know the clips. Omit AI-only selection fields such as numberOfClips, preferredDurationSeconds, and styleInstructions in this mode.
curl -X POST https://www.subclip.app/api/v1/ai-clipping/jobs \
-H "Authorization: Bearer $SUBCLIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"videoUrl": "https://your-bucket.s3.amazonaws.com/interview.mp4?X-Amz-Signature=...",
"aiAnalysis": false,
"preset": "customised",
"aspectRatio": "portrait",
"transcriptSrtObjectKey": "user_.../ai-clipping-api/transcripts/abc123-podcast.srt",
"ctaText": "Follow for the full breakdown",
"segments": [
{
"startTimeSeconds": 83.2,
"endTimeSeconds": 126.8,
"textHook": "This changed the whole launch"
}
]
}'Options
| Field | Required | Notes |
|---|---|---|
| videoUrl | Yes, unless sourceObjectKey | Public or signed HTTP(S) video URL, public S3/R2 URL, or YouTube URL. |
| sourceObjectKey | Yes, unless videoUrl | Object key returned by POST /api/v1/ai-clipping/uploads with uploadType source. |
| sourceObjectName | No | Original uploaded source filename. |
| sourceObjectMimeType | No | Source video MIME type. |
| language | Conditional | Required when no transcript, transcriptSrtUrl, or transcriptSrtObjectKey is provided. |
| youtubeTranscript | No | YouTube videoUrl only. When true, Subclip tries YouTube captions first. If fetched, the completed job includes 1 extra API credit. If fetch fails, Subclip falls back to ASR. |
| aiAnalysis | No | true lets Subclip choose clips. false requires segments. When omitted, Subclip infers the mode from whether segments are provided. |
| preset | No | face_track, default, customised, or aura. Defaults to default. |
| aspectRatio | No | portrait or square. Defaults to portrait. Square is rejected for face_track. |
| dynamicCaptions | No | Defaults to true. |
| numberOfClips | No | Desired AI-selected clip count, 1-12. Omit for automatic selection. |
| maxClips | No | Legacy alias for numberOfClips. Pass only one of them. |
| preferredDurationSeconds | No | Preferred duration per AI-selected clip, 10-120 seconds. This is a hint, not a hard trim. Omit for automatic duration. |
| styleInstructions | No | Custom instructions for how AI should select clips, max 600 characters. |
| transcript | No | Optional structured transcript segments to avoid ASR. |
| transcriptSrtUrl | No | Public or signed HTTP(S) URL for an SRT transcript. |
| transcriptSrtObjectKey | No | Object key returned by POST /api/v1/ai-clipping/uploads after uploading an SRT to Subclip storage. |
| segments | No | Exact clips with startTimeSeconds and endTimeSeconds. |
| textHook | No | Top-level hook for the first selected segment. |
| ctaText | No | CTA text used in CTA-capable presets. |
| addTextHook | No | Generate missing hooks for selected segments. |
| addCtaText | No | Generate missing CTA text for clips. |
Poll status
Poll every 10 to 15 seconds. The output is ready when outputReady is true. Completed responses include rendered MP4 URLs in clips[].downloadUrl.
curl https://www.subclip.app/api/v1/ai-clipping/jobs/rproj_... \ -H "Authorization: Bearer $SUBCLIP_API_KEY"
Download
The download endpoint returns JSON with a short-lived signed URL for the full rendered montage plus one signed URL per rendered clip segment.
DOWNLOAD_JSON=$(curl -s https://www.subclip.app/api/v1/ai-clipping/jobs/rproj_.../download \ -H "Authorization: Bearer $SUBCLIP_API_KEY") DOWNLOAD_URL=$(echo "$DOWNLOAD_JSON" | jq -r '.downloadUrl') curl -L "$DOWNLOAD_URL" -o ai-clips.mp4