curl -X POST "https://api.hooked.so/v1/project/create/extend-video" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"media": "cm4x9k2p10001ab12cd34ef56",
"prompt": "The camera slowly pulls back to reveal the whole kitchen as she keeps cooking",
"targetDuration": 8,
"resolution": "1080p",
"webhook": "https://example.com/hooks/hooked?token=YOUR_SECRET"
}'
const response = await fetch("https://api.hooked.so/v1/project/create/extend-video", {
method: "POST",
headers: {
"x-api-key": "your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
media: "cm4x9k2p10001ab12cd34ef56",
prompt: "The camera slowly pulls back to reveal the whole kitchen as she keeps cooking",
targetDuration: 8,
resolution: "1080p",
webhook: "https://example.com/hooks/hooked?token=YOUR_SECRET",
}),
});
const result = await response.json();
console.log(result.data.projectId);
import requests
response = requests.post(
"https://api.hooked.so/v1/project/create/extend-video",
headers={"x-api-key": "your_api_key_here"},
json={
"media": "cm4x9k2p10001ab12cd34ef56",
"prompt": "The camera slowly pulls back to reveal the whole kitchen as she keeps cooking",
"targetDuration": 8,
"resolution": "1080p",
"webhook": "https://example.com/hooks/hooked?token=YOUR_SECRET",
},
)
print(response.json()["data"]["projectId"])
{
"success": true,
"data": {
"projectId": "cm4xa1b2c0002ab12cd34ef56",
"status": "processing"
},
"message": "Extend Video successfully created"
}
{
"success": false,
"message": "extendVideoSettings.targetDuration: Target duration cannot exceed 60 seconds"
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"success": false,
"code": "missing_credentials",
"errorCode": "MISSING_CREDENTIALS",
"missingProviders": ["openrouter"],
"message": "Your team uses its own provider keys (BYOK) and these are missing or invalid: OpenRouter. Add them in Settings → AI keys."
}
{
"success": false,
"code": "subscription_required",
"errorCode": "SUBSCRIPTION_REQUIRED",
"message": "Your team generates with Hooked's keys, which are paid by your plan. Subscribe to keep generating."
}
{
"success": false,
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Not enough credits for Extend Video creation. Estimated: 95, Available: 40",
"creditsNeeded": 95,
"creditsAvailable": 40
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
Extend Video
Continue a video from your media library with an AI video model
curl -X POST "https://api.hooked.so/v1/project/create/extend-video" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"media": "cm4x9k2p10001ab12cd34ef56",
"prompt": "The camera slowly pulls back to reveal the whole kitchen as she keeps cooking",
"targetDuration": 8,
"resolution": "1080p",
"webhook": "https://example.com/hooks/hooked?token=YOUR_SECRET"
}'
const response = await fetch("https://api.hooked.so/v1/project/create/extend-video", {
method: "POST",
headers: {
"x-api-key": "your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
media: "cm4x9k2p10001ab12cd34ef56",
prompt: "The camera slowly pulls back to reveal the whole kitchen as she keeps cooking",
targetDuration: 8,
resolution: "1080p",
webhook: "https://example.com/hooks/hooked?token=YOUR_SECRET",
}),
});
const result = await response.json();
console.log(result.data.projectId);
import requests
response = requests.post(
"https://api.hooked.so/v1/project/create/extend-video",
headers={"x-api-key": "your_api_key_here"},
json={
"media": "cm4x9k2p10001ab12cd34ef56",
"prompt": "The camera slowly pulls back to reveal the whole kitchen as she keeps cooking",
"targetDuration": 8,
"resolution": "1080p",
"webhook": "https://example.com/hooks/hooked?token=YOUR_SECRET",
},
)
print(response.json()["data"]["projectId"])
{
"success": true,
"data": {
"projectId": "cm4xa1b2c0002ab12cd34ef56",
"status": "processing"
},
"message": "Extend Video successfully created"
}
{
"success": false,
"message": "extendVideoSettings.targetDuration: Target duration cannot exceed 60 seconds"
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"success": false,
"code": "missing_credentials",
"errorCode": "MISSING_CREDENTIALS",
"missingProviders": ["openrouter"],
"message": "Your team uses its own provider keys (BYOK) and these are missing or invalid: OpenRouter. Add them in Settings → AI keys."
}
{
"success": false,
"code": "subscription_required",
"errorCode": "SUBSCRIPTION_REQUIRED",
"message": "Your team generates with Hooked's keys, which are paid by your plan. Subscribe to keep generating."
}
{
"success": false,
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Not enough credits for Extend Video creation. Estimated: 95, Available: 40",
"creditsNeeded": 95,
"creditsAvailable": 40
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
Overview
Extend Video takes a video from your team’s media library and continues it: the model starts from the last frame and generates new footage that follows your prompt. Longer extensions are made of several clips, each one starting from the last frame of the previous one. The output keeps the source video’s aspect ratio.POST https://api.hooked.so/v1/project/create/extend-video
media is the id of a video in your team’s media library. Add one with Upload Media or Import Media from URL, or find it with List Media; it must be COMPLETED.Models
model picks the video model that continues the clip (default veo_3_fast). Any model that can start from a frame:
| Value | Model | Resolutions | Clip lengths | Audio |
|---|---|---|---|---|
veo_3_fast | Veo 3.1 Fast (default) | 720p, 1080p | 4, 6, 8 s | yes |
veo_3 | Veo 3.1 Standard | 720p, 1080p | 4, 6, 8 s | yes |
veo_3_1_lite | Veo 3.1 Lite | 720p, 1080p | 4, 6, 8 s | yes |
kling_3_0 | Kling 3.0 Standard | 1080p (720p is taken and rendered at 1080p) | 5, 10 s | yes |
kling_3_0_pro | Kling 3.0 Pro | 1080p (720p is taken and rendered at 1080p) | 5, 10 s | yes |
seedance_2_5 | Seedance 2.5 | 480p, 720p | 5, 10 s | yes |
seedance_2_0 | Seedance 2.0 | 720p, 1080p | 5, 10 s | yes |
seedance_2_0_fast | Seedance 2.0 Fast | 720p | 5, 10 s | yes |
seedance_2_0_mini | Seedance 2.0 Mini | 720p | 5, 10 s | yes |
grok_imagine_video | Grok Imagine | 720p, 1080p | 4, 6, 8, 10, 15 s | yes |
grok_imagine_video_lite | Grok Imagine Lite | 480p, 720p, 1080p | 4, 6, 8, 10, 15 s | yes |
wan_2_7_i2v | Wan | 720p, 1080p | 5, 10, 15 s | no |
targetDuration is rounded to the nearest multiple of 4 and split into clips of these lengths; a remainder shorter than the model’s shortest clip takes that clip, so the extension (and the charge) can be a few seconds longer than asked.
prompt, targetDuration, model, resolution and generateAudio go at the top level of the body. Validation errors name them under extendVideoSettings (for example extendVideoSettings.prompt: Required).curl -X POST "https://api.hooked.so/v1/project/create/extend-video" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"media": "cm4x9k2p10001ab12cd34ef56",
"prompt": "The camera slowly pulls back to reveal the whole kitchen as she keeps cooking",
"targetDuration": 8,
"resolution": "1080p",
"webhook": "https://example.com/hooks/hooked?token=YOUR_SECRET"
}'
const response = await fetch("https://api.hooked.so/v1/project/create/extend-video", {
method: "POST",
headers: {
"x-api-key": "your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
media: "cm4x9k2p10001ab12cd34ef56",
prompt: "The camera slowly pulls back to reveal the whole kitchen as she keeps cooking",
targetDuration: 8,
resolution: "1080p",
webhook: "https://example.com/hooks/hooked?token=YOUR_SECRET",
}),
});
const result = await response.json();
console.log(result.data.projectId);
import requests
response = requests.post(
"https://api.hooked.so/v1/project/create/extend-video",
headers={"x-api-key": "your_api_key_here"},
json={
"media": "cm4x9k2p10001ab12cd34ef56",
"prompt": "The camera slowly pulls back to reveal the whole kitchen as she keeps cooking",
"targetDuration": 8,
"resolution": "1080p",
"webhook": "https://example.com/hooks/hooked?token=YOUR_SECRET",
},
)
print(response.json()["data"]["projectId"])
{
"success": true,
"data": {
"projectId": "cm4xa1b2c0002ab12cd34ef56",
"status": "processing"
},
"message": "Extend Video successfully created"
}
{
"success": false,
"message": "extendVideoSettings.targetDuration: Target duration cannot exceed 60 seconds"
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"success": false,
"code": "missing_credentials",
"errorCode": "MISSING_CREDENTIALS",
"missingProviders": ["openrouter"],
"message": "Your team uses its own provider keys (BYOK) and these are missing or invalid: OpenRouter. Add them in Settings → AI keys."
}
{
"success": false,
"code": "subscription_required",
"errorCode": "SUBSCRIPTION_REQUIRED",
"message": "Your team generates with Hooked's keys, which are paid by your plan. Subscribe to keep generating."
}
{
"success": false,
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Not enough credits for Extend Video creation. Estimated: 95, Available: 40",
"creditsNeeded": 95,
"creditsAvailable": 40
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
What happens next
The clips are generated one after another, joined to the source video and rendered. Either:- poll
GET /v1/project/{projectId}untildata.videois present anddata.video.statusisCOMPLETED, then downloaddata.video.url; or - pass
webhookand wait for theCOMPLETED(orFAILED) call described in Webhooks:
{
"status": "COMPLETED",
"message": "Video completed",
"data": {
"videoId": "cm4xb7d8e0003ab12cd34ef56",
"projectId": "cm4xa1b2c0002ab12cd34ef56",
"status": "COMPLETED",
"url": "https://..."
}
}
Credits and your own keys
- Managed teams (Hooked’s provider keys) pay in credits: a base of 7 credits plus the model’s price per second of generated footage at the resolution it runs at. For the default Veo 3.1 Fast that is 11 credits per second at 1080p and 10 at 720p, so the default 4-second extension at 1080p costs 51 credits. The balance is checked when you create the project and the credits are taken when processing starts. If the project fails, they are refunded automatically.
- Teams on their own provider keys (BYOK) are not charged credits. They need an OpenRouter key in Settings → AI keys (every extension model runs on it). Without it the request answers
402 missing_credentialsbefore anything is created or charged.
Errors
| Status | Code / message | What to do |
|---|---|---|
| 400 | media: A video media ID is required (no media), Media not found (not a media of your team), Media is not a video | Send the id of a video in your team’s library. |
| 400 | extendVideoSettings.<field>: <reason>, extendVideoSettings.model: "…" cannot extend a video, webhook: Must be a valid HTTPS URL, Invalid JSON body | Fix the field named in the message. |
| 400 | Subscription not found | The team has no plan record. Contact support. |
| 401 | not_authenticated | Send a valid key in x-api-key. |
| 402 | missing_credentials | BYOK team: add the keys in missingProviders in Settings → AI keys. |
| 402 | subscription_required | Managed team without an active plan: subscribe. |
| 402 | INSUFFICIENT_CREDITS | Buy credits or wait for your plan to renew. creditsNeeded and creditsAvailable show the gap. |
| 403 | entitlement_required | The team does not have the Hooked app product. |
| 500 | Internal server error or another message | Check with GET /v1/video/list or your webhook before trying again. |
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.Related
Project details
Webhooks
Add Captions
List Videos
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
ID of a video in your team's library (add one with POST /v1/media/upload or POST /v1/media/import). The output keeps its aspect ratio. An unknown ID or another team's media answers 400 Media not found; an image answers 400 Media is not a video. A media that is not COMPLETED yet (an import or upload in progress) answers 400 media: Media "<id>" is not ready (status PROCESSING).
What happens next in the video
1 - 2000Project name (max 100 characters). Generated from the prompt when omitted.
100Seconds to add. Rounded to the nearest multiple of 4, then split into clips of the lengths the model makes (Veo: 4, 6 or 8 s; Kling and Seedance: 5 or 10 s; Wan: 5, 10 or 15 s; Grok: 4, 6, 8, 10 or 15 s). A remainder shorter than the model's shortest clip takes that clip, so the extension (and the charge) can be a few seconds longer than asked.
4 <= x <= 60Output resolution. A model that does not make the one asked for runs at its own default (720p for Seedance 2.0 Fast and Mini). The price follows the resolution it runs at.
720p, 1080p Generate a soundtrack for the new footage. Ignored by models without audio (wan_2_7_i2v).
The video model that continues the clip. Default: Veo 3.1 Fast. veo_3 = Veo 3.1 Standard, veo_3_1_lite = Veo 3.1 Lite, kling_3_0 = Kling 3.0 Standard, kling_3_0_pro = Kling 3.0 Pro, seedance_2_0 / seedance_2_0_fast / seedance_2_0_mini = Seedance 2.0 (Fast and Mini run at 720p at most), grok_imagine_video = Grok Imagine, wan_2_7_i2v = Wan (no audio). Any other value answers 400 extendVideoSettings.model: "<value>" cannot extend a video.
veo_3_fast, veo_3, grok_imagine_video, grok_imagine_video_lite, kling_3_0, kling_3_0_pro, seedance_2_0, seedance_2_5, wan_2_7_i2v, seedance_2_0_fast, veo_3_1_lite, seedance_2_0_mini 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). Sent back in the webhook payload. The key automationId is reserved for dashboard automations and is removed.