curl -X POST "https://api.hooked.so/v1/project/create/clone-video" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"sourceVideoUrl": "https://www.tiktok.com/@hooked.so/video/7412345678901234567",
"voiceId": "1004",
"targetDurationSeconds": 30,
"mustInclude": "End with: try Hooked free",
"mediaType": "ai-images",
"aspectRatio": "ratio_9_16",
"caption": { "preset": "tiktok" },
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET",
"metadata": { "campaign": "spring" }
}'
const response = await fetch("https://api.hooked.so/v1/project/create/clone-video", {
method: "POST",
headers: {
"x-api-key": "your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
sourceVideoUrl: "https://www.tiktok.com/@hooked.so/video/7412345678901234567",
voiceId: "1004",
targetDurationSeconds: 30,
mustInclude: "End with: try Hooked free",
mediaType: "ai-images",
aspectRatio: "ratio_9_16",
caption: { preset: "tiktok" },
webhook: "https://example.com/hooked/webhook?token=YOUR_SECRET",
metadata: { campaign: "spring" },
}),
});
const { data } = await response.json();
console.log(data.projectId);
import requests
response = requests.post(
"https://api.hooked.so/v1/project/create/clone-video",
headers={"x-api-key": "your_api_key_here"},
json={
"sourceVideoUrl": "https://www.tiktok.com/@hooked.so/video/7412345678901234567",
"voiceId": "1004",
"targetDurationSeconds": 30,
"mustInclude": "End with: try Hooked free",
"mediaType": "ai-images",
"aspectRatio": "ratio_9_16",
"caption": {"preset": "tiktok"},
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET",
"metadata": {"campaign": "spring"},
},
)
print(response.json()["data"]["projectId"])
{
"success": true,
"data": {
"projectId": "cm4x9k2p10001ab12cd34ef56",
"status": "processing"
},
"message": "Clone Video successfully created"
}
{
"success": false,
"message": "sourceVideoUrl: Only TikTok, Instagram, and YouTube URLs are supported"
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"code": "missing_credentials",
"message": "Your team uses its own provider keys (BYOK) and these are missing or invalid: OpenRouter, ElevenLabs. Add them in Settings → AI keys.",
"errorCode": "MISSING_CREDENTIALS",
"missingProviders": ["openrouter", "elevenlabs"],
"details": {}
}
{
"success": false,
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Not enough credits for Clone Video. Needed: 90, Available: 45",
"creditsNeeded": 90,
"creditsAvailable": 45
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
Clone Video
Remake a TikTok, Instagram or YouTube video with your own voice and new AI visuals
curl -X POST "https://api.hooked.so/v1/project/create/clone-video" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"sourceVideoUrl": "https://www.tiktok.com/@hooked.so/video/7412345678901234567",
"voiceId": "1004",
"targetDurationSeconds": 30,
"mustInclude": "End with: try Hooked free",
"mediaType": "ai-images",
"aspectRatio": "ratio_9_16",
"caption": { "preset": "tiktok" },
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET",
"metadata": { "campaign": "spring" }
}'
const response = await fetch("https://api.hooked.so/v1/project/create/clone-video", {
method: "POST",
headers: {
"x-api-key": "your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
sourceVideoUrl: "https://www.tiktok.com/@hooked.so/video/7412345678901234567",
voiceId: "1004",
targetDurationSeconds: 30,
mustInclude: "End with: try Hooked free",
mediaType: "ai-images",
aspectRatio: "ratio_9_16",
caption: { preset: "tiktok" },
webhook: "https://example.com/hooked/webhook?token=YOUR_SECRET",
metadata: { campaign: "spring" },
}),
});
const { data } = await response.json();
console.log(data.projectId);
import requests
response = requests.post(
"https://api.hooked.so/v1/project/create/clone-video",
headers={"x-api-key": "your_api_key_here"},
json={
"sourceVideoUrl": "https://www.tiktok.com/@hooked.so/video/7412345678901234567",
"voiceId": "1004",
"targetDurationSeconds": 30,
"mustInclude": "End with: try Hooked free",
"mediaType": "ai-images",
"aspectRatio": "ratio_9_16",
"caption": {"preset": "tiktok"},
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET",
"metadata": {"campaign": "spring"},
},
)
print(response.json()["data"]["projectId"])
{
"success": true,
"data": {
"projectId": "cm4x9k2p10001ab12cd34ef56",
"status": "processing"
},
"message": "Clone Video successfully created"
}
{
"success": false,
"message": "sourceVideoUrl: Only TikTok, Instagram, and YouTube URLs are supported"
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"code": "missing_credentials",
"message": "Your team uses its own provider keys (BYOK) and these are missing or invalid: OpenRouter, ElevenLabs. Add them in Settings → AI keys.",
"errorCode": "MISSING_CREDENTIALS",
"missingProviders": ["openrouter", "elevenlabs"],
"details": {}
}
{
"success": false,
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Not enough credits for Clone Video. Needed: 90, Available: 45",
"creditsNeeded": 90,
"creditsAvailable": 45
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
Overview
Clone Video takes a short video that works (a TikTok, an Instagram Reel, a YouTube Short or video) and makes a new one in its image: Hooked analyses its story, structure and pacing, writes a new narration from it, reads it with your voice and generates a new visual for each scene. The look is taken from the source video, so there is no style preset to pick: you choose AI images or AI video clips, and optionally the model. What it does not do: it does not copy the original’s voice, music or footage, and it cannot clone a video that is private, deleted or behind a login.sourceVideoUrlis a TikTok, Instagram or YouTube link. Any other host answers400.targetDurationSecondssets the length of the new video (default 30 seconds) and its price.mustIncludeis something the new video has to mention, such as your product or a call to action.
projectId right away. The video is analysed, generated and rendered in the background.
POST https://api.hooked.so/v1/project/create/clone-video
curl -X POST "https://api.hooked.so/v1/project/create/clone-video" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"sourceVideoUrl": "https://www.tiktok.com/@hooked.so/video/7412345678901234567",
"voiceId": "1004",
"targetDurationSeconds": 30,
"mustInclude": "End with: try Hooked free",
"mediaType": "ai-images",
"aspectRatio": "ratio_9_16",
"caption": { "preset": "tiktok" },
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET",
"metadata": { "campaign": "spring" }
}'
const response = await fetch("https://api.hooked.so/v1/project/create/clone-video", {
method: "POST",
headers: {
"x-api-key": "your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
sourceVideoUrl: "https://www.tiktok.com/@hooked.so/video/7412345678901234567",
voiceId: "1004",
targetDurationSeconds: 30,
mustInclude: "End with: try Hooked free",
mediaType: "ai-images",
aspectRatio: "ratio_9_16",
caption: { preset: "tiktok" },
webhook: "https://example.com/hooked/webhook?token=YOUR_SECRET",
metadata: { campaign: "spring" },
}),
});
const { data } = await response.json();
console.log(data.projectId);
import requests
response = requests.post(
"https://api.hooked.so/v1/project/create/clone-video",
headers={"x-api-key": "your_api_key_here"},
json={
"sourceVideoUrl": "https://www.tiktok.com/@hooked.so/video/7412345678901234567",
"voiceId": "1004",
"targetDurationSeconds": 30,
"mustInclude": "End with: try Hooked free",
"mediaType": "ai-images",
"aspectRatio": "ratio_9_16",
"caption": {"preset": "tiktok"},
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET",
"metadata": {"campaign": "spring"},
},
)
print(response.json()["data"]["projectId"])
More request bodies
{
"sourceVideoUrl": "https://www.youtube.com/shorts/aBcD1234EfG",
"voiceId": "1004",
"targetDurationSeconds": 45,
"mediaType": "ai-videos",
"presetSettings": { "quality": "pro" }
}
{
"sourceVideoUrl": "https://www.instagram.com/reel/C9xYz123AbC/",
"voiceId": "1004",
"mediaType": "ai-images",
"presetSettings": { "aiModel": "nano_banana_pro" },
"aspectRatio": "ratio_16_9"
}
{
"success": true,
"data": {
"projectId": "cm4x9k2p10001ab12cd34ef56",
"status": "processing"
},
"message": "Clone Video successfully created"
}
{
"success": false,
"message": "sourceVideoUrl: Only TikTok, Instagram, and YouTube URLs are supported"
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"code": "missing_credentials",
"message": "Your team uses its own provider keys (BYOK) and these are missing or invalid: OpenRouter, ElevenLabs. Add them in Settings → AI keys.",
"errorCode": "MISSING_CREDENTIALS",
"missingProviders": ["openrouter", "elevenlabs"],
"details": {}
}
{
"success": false,
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Not enough credits for Clone Video. Needed: 90, Available: 45",
"creditsNeeded": 90,
"creditsAvailable": 45
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
What happens next
- Keep the
projectId. - Either poll
GET /v1/project/{projectId}untilvideo.statusisCOMPLETED(or the projectstatusisfailed), or pass awebhookand wait for the call. - Download the file from
video.url(ordata.urlin the webhook).
"status": "FAILED" and the credits are refunded. See Webhooks for every payload.
Credits and your own keys
Teams on Hooked’s keys (managed): the project is priced fromtargetDurationSeconds, the media type and the quality or model, and the credits are taken up front. If the balance is short, the request answers 402 INSUFFICIENT_CREDITS with creditsNeeded and creditsAvailable and nothing is created. If the project fails later, the credits are refunded automatically.
Teams on their own keys (BYOK): no credits are charged. The team needs these keys in Settings → AI keys:
- OpenRouter: always (analysis, script, images, video clips, captions).
- ElevenLabs: for the voiceover.
- Gemini: when
presetSettings.aiModelisgemini_omni_flash.
402 missing_credentials with the list in missingProviders, before anything is created.
Errors
| Status | Body | When |
|---|---|---|
400 | { "success": false, "message": "<field>: <reason>" } | Validation failed. Examples: sourceVideoUrl: Required (a TikTok, Instagram or YouTube video URL), sourceVideoUrl: Only TikTok, Instagram, and YouTube URLs are supported, voiceId: Voice "…" not found., musicId: Music "…" not found., mediaType: Invalid media type "…". Allowed values are: ai-images, ai-videos, presetSettings.aiModel: "…" does not generate ai-videos, caption.preset: Invalid caption preset "…", cloneVideoSettings.targetDurationSeconds: … (outside 1-600), webhook: Must be a valid HTTPS URL, Invalid JSON body. |
401 | not_authenticated | Missing or invalid x-api-key. |
402 | missing_credentials | BYOK team without the keys listed above. |
402 | subscription_required | Managed team without an active plan. |
402 | INSUFFICIENT_CREDITS | Managed team without enough credits. |
403 | entitlement_required | The team does not have the product this endpoint needs. |
500 | { "success": false, "message": "..." } | Unexpected error; credits taken for this request are refunded. |
Idempotency-Key header to retry safely: if a request times out or answers 5xx, sending it again with the same key and body returns the first answer instead of creating (and charging) the project twice.Next Steps
Get Project
Clone Video example
List Voices
Script to Video
Authorizations
Headers
Makes the request safe to retry. 1-255 printable ASCII characters, one per operation (your job id, or a UUID you store), reused on every retry. Within 24 hours the same key with the same body answers with the first response and the header Idempotent-Replayed: true, without running or charging again. Keys are scoped to your team and the endpoint. 5xx answers and refusals before anything ran (401, 402, 403, 409, 429) are not kept. See Idempotency.
1 - 255^[\x20-\x7E]+$Body
The video to clone: a TikTok, Instagram or YouTube link. Any other host answers 400 (sourceVideoUrl: Only TikTok, Instagram, and YouTube URLs are supported).
2000The narrator: a library voice from /v1/voice/list or one of your team's custom voices. An ID that is neither answers 400 (voiceId: Voice "<id>" not found.).
1 - 30Length of the new video in seconds. It also sets the price.
1 <= x <= 600Something the new video has to mention or show, e.g. your product or a call to action (max 500 characters).
500ai-images: one AI image per scene. ai-videos: one AI video clip per scene, chained frame to frame. Any other value answers 400.
ai-images, ai-videos The model of the visuals. The style is taken from the source video, so there is no preset.
Show child attributes
Show child attributes
Project name (max 100 characters). Generated from the source when omitted.
100Background music: a track from /v1/music/list or your team's own uploaded music. An ID that is neither answers 400 (musicId: Music "<id>" not found.).
30Vertical, horizontal or square.
ratio_9_16, ratio_16_9, ratio_1_1 Two-letter ISO 639-1 code of the narration, e.g. en, es.
Burned-in captions of the narration. Not used with motion-graphics (it typesets its own copy) or a speaking cast.
Show child attributes
Show child attributes
Add emoji/GIF stickers anchored to the captions. Not used with motion-graphics or a speaking cast.
Branding.
Show child attributes
Show child attributes
HTTPS URL on a public host, called when the video is ready or the project fails (max 500 characters). Requests are signed: verify the Hooked-Signature header (HMAC-SHA256 of ".") with the signing secret from Settings → Webhooks; Hooked-Event and Hooked-Delivery headers name the event and the delivery. No redirects are followed; 10 s timeout; 3 immediate attempts. See the Webhooks guide for the payload.
500Any JSON object of your own (max 5 KB). Stored on the project and sent back in the webhook payload. The key automationId is reserved for dashboard automations and is removed.
Narrator voice settings (ElevenLabs). Any field you leave out keeps its default. Not used by a speaking cast.
Show child attributes
Show child attributes