curl "https://api.hooked.so/v1/project/cm4x9k2p10001ab12cd34ef56" \
-H "x-api-key: your_api_key_here"
const projectId = "cm4x9k2p10001ab12cd34ef56";
const response = await fetch(`https://api.hooked.so/v1/project/${projectId}`, {
headers: { "x-api-key": process.env.HOOKED_API_KEY },
});
const { data: project } = await response.json();
console.log(project.status, project.progress);
if (project.video?.status === "COMPLETED") {
console.log("Video ready:", project.video.url);
}
import os
import requests
project_id = "cm4x9k2p10001ab12cd34ef56"
response = requests.get(
f"https://api.hooked.so/v1/project/{project_id}",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
)
project = response.json()["data"]
print(project["status"], project["progress"])
video = project["video"]
if video and video["status"] == "COMPLETED":
print("Video ready:", video["url"])
{
"success": true,
"message": "Project fetched successfully",
"data": {
"id": "cm4x9k2p10001ab12cd34ef56",
"name": "Morning routine tips",
"type": "script_to_video",
"status": "completed",
"source": "api",
"progress": 100,
"message": "Video ready",
"usedCredits": 42,
"metadata": { "campaignId": "spring-launch" },
"createdAt": "2026-10-01T09:12:03.000Z",
"updatedAt": "2026-10-01T09:16:41.000Z",
"video": {
"id": "cm4x9n7q20003ab12xy98zt10",
"name": "Morning routine tips",
"projectId": "cm4x9k2p10001ab12cd34ef56",
"projectType": "script_to_video",
"status": "COMPLETED",
"progress": 100,
"message": "Video ready",
"url": "https://files.hooked.so/team/videos/cm4x9n7q20003ab12xy98zt10.mp4?X-Amz-Signature=...",
"thumbnail": "https://files.hooked.so/team/thumbnails/cm4x9n7q20003ab12xy98zt10.jpg?X-Amz-Signature=...",
"durationInFrames": 1125,
"durationInSeconds": 45,
"size": 18874368,
"createdAt": "2026-10-01T09:15:58.000Z",
"updatedAt": "2026-10-01T09:16:41.000Z"
}
}
}
{
"success": true,
"message": "Project fetched successfully",
"data": {
"id": "cm4x9k2p10001ab12cd34ef56",
"name": "Morning routine tips",
"type": "script_to_video",
"status": "processing",
"source": "api",
"progress": 35,
"message": "Generating media...",
"usedCredits": 42,
"metadata": {},
"createdAt": "2026-10-01T09:12:03.000Z",
"updatedAt": "2026-10-01T09:13:10.000Z",
"video": null
}
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
{
"success": false,
"message": "Project not found"
}
Projects
Get Project
Read the status of a project created through the API, and its video once rendered
GET
/
v1
/
project
/
{projectId}
curl "https://api.hooked.so/v1/project/cm4x9k2p10001ab12cd34ef56" \
-H "x-api-key: your_api_key_here"
const projectId = "cm4x9k2p10001ab12cd34ef56";
const response = await fetch(`https://api.hooked.so/v1/project/${projectId}`, {
headers: { "x-api-key": process.env.HOOKED_API_KEY },
});
const { data: project } = await response.json();
console.log(project.status, project.progress);
if (project.video?.status === "COMPLETED") {
console.log("Video ready:", project.video.url);
}
import os
import requests
project_id = "cm4x9k2p10001ab12cd34ef56"
response = requests.get(
f"https://api.hooked.so/v1/project/{project_id}",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
)
project = response.json()["data"]
print(project["status"], project["progress"])
video = project["video"]
if video and video["status"] == "COMPLETED":
print("Video ready:", video["url"])
{
"success": true,
"message": "Project fetched successfully",
"data": {
"id": "cm4x9k2p10001ab12cd34ef56",
"name": "Morning routine tips",
"type": "script_to_video",
"status": "completed",
"source": "api",
"progress": 100,
"message": "Video ready",
"usedCredits": 42,
"metadata": { "campaignId": "spring-launch" },
"createdAt": "2026-10-01T09:12:03.000Z",
"updatedAt": "2026-10-01T09:16:41.000Z",
"video": {
"id": "cm4x9n7q20003ab12xy98zt10",
"name": "Morning routine tips",
"projectId": "cm4x9k2p10001ab12cd34ef56",
"projectType": "script_to_video",
"status": "COMPLETED",
"progress": 100,
"message": "Video ready",
"url": "https://files.hooked.so/team/videos/cm4x9n7q20003ab12xy98zt10.mp4?X-Amz-Signature=...",
"thumbnail": "https://files.hooked.so/team/thumbnails/cm4x9n7q20003ab12xy98zt10.jpg?X-Amz-Signature=...",
"durationInFrames": 1125,
"durationInSeconds": 45,
"size": 18874368,
"createdAt": "2026-10-01T09:15:58.000Z",
"updatedAt": "2026-10-01T09:16:41.000Z"
}
}
}
{
"success": true,
"message": "Project fetched successfully",
"data": {
"id": "cm4x9k2p10001ab12cd34ef56",
"name": "Morning routine tips",
"type": "script_to_video",
"status": "processing",
"source": "api",
"progress": 35,
"message": "Generating media...",
"usedCredits": 42,
"metadata": {},
"createdAt": "2026-10-01T09:12:03.000Z",
"updatedAt": "2026-10-01T09:13:10.000Z",
"video": null
}
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
{
"success": false,
"message": "Project not found"
}
Overview
EveryPOST /v1/project/create/* endpoint answers with a projectId. Use this endpoint to follow that project until its video is ready.
A project goes through two stages:
- Generation: script, voice, clips, captions. The project
statusisprocessing(ordraftright after creation) andvideoisnull. - Render: once the timeline is built, the project is rendered into a video.
videois filled in, andvideo.statusgoes fromSTARTEDtoCOMPLETED(orFAILED).
completed; video stays null (or STARTED) for the minute or two the render takes.
The video file is ready when video.status is COMPLETED; video.url is the download link.
A project only shows up in List Videos once its render has started. Before that, find it here or in List Projects, including a project that failed while generating.
curl "https://api.hooked.so/v1/project/cm4x9k2p10001ab12cd34ef56" \
-H "x-api-key: your_api_key_here"
const projectId = "cm4x9k2p10001ab12cd34ef56";
const response = await fetch(`https://api.hooked.so/v1/project/${projectId}`, {
headers: { "x-api-key": process.env.HOOKED_API_KEY },
});
const { data: project } = await response.json();
console.log(project.status, project.progress);
if (project.video?.status === "COMPLETED") {
console.log("Video ready:", project.video.url);
}
import os
import requests
project_id = "cm4x9k2p10001ab12cd34ef56"
response = requests.get(
f"https://api.hooked.so/v1/project/{project_id}",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
)
project = response.json()["data"]
print(project["status"], project["progress"])
video = project["video"]
if video and video["status"] == "COMPLETED":
print("Video ready:", video["url"])
{
"success": true,
"message": "Project fetched successfully",
"data": {
"id": "cm4x9k2p10001ab12cd34ef56",
"name": "Morning routine tips",
"type": "script_to_video",
"status": "completed",
"source": "api",
"progress": 100,
"message": "Video ready",
"usedCredits": 42,
"metadata": { "campaignId": "spring-launch" },
"createdAt": "2026-10-01T09:12:03.000Z",
"updatedAt": "2026-10-01T09:16:41.000Z",
"video": {
"id": "cm4x9n7q20003ab12xy98zt10",
"name": "Morning routine tips",
"projectId": "cm4x9k2p10001ab12cd34ef56",
"projectType": "script_to_video",
"status": "COMPLETED",
"progress": 100,
"message": "Video ready",
"url": "https://files.hooked.so/team/videos/cm4x9n7q20003ab12xy98zt10.mp4?X-Amz-Signature=...",
"thumbnail": "https://files.hooked.so/team/thumbnails/cm4x9n7q20003ab12xy98zt10.jpg?X-Amz-Signature=...",
"durationInFrames": 1125,
"durationInSeconds": 45,
"size": 18874368,
"createdAt": "2026-10-01T09:15:58.000Z",
"updatedAt": "2026-10-01T09:16:41.000Z"
}
}
}
{
"success": true,
"message": "Project fetched successfully",
"data": {
"id": "cm4x9k2p10001ab12cd34ef56",
"name": "Morning routine tips",
"type": "script_to_video",
"status": "processing",
"source": "api",
"progress": 35,
"message": "Generating media...",
"usedCredits": 42,
"metadata": {},
"createdAt": "2026-10-01T09:12:03.000Z",
"updatedAt": "2026-10-01T09:13:10.000Z",
"video": null
}
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
{
"success": false,
"message": "Project not found"
}
Reading the status
status | video | Meaning |
|---|---|---|
draft / processing | null | Still generating. Keep polling. |
completed | null | Generated; the render is about to start. Keep polling. |
processing / completed | status: "STARTED" | Rendering. video.url is "" until it finishes. |
completed | status: "COMPLETED" | Done. Download video.url. |
failed | null or any | The project failed. message says why. Credits charged for it are refunded automatically. |
| any | status: "FAILED" | The render failed. You can try again with Render Project, which costs no credits. |
video.url and video.thumbnail are signed links that expire. Fetch the project or the video again for a fresh link instead of storing them.
Polling
Poll every 10 to 15 seconds. Most projects finish in a few minutes; long or AI-video-heavy projects can take longer. Retrying thisGET is always safe.
async function waitForVideo(projectId, { intervalMs = 10000, timeoutMs = 30 * 60 * 1000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const response = await fetch(`https://api.hooked.so/v1/project/${projectId}`, {
headers: { "x-api-key": process.env.HOOKED_API_KEY },
});
const { data: project } = await response.json();
if (project.status === "failed") throw new Error(`Project failed: ${project.message}`);
if (project.video?.status === "FAILED") throw new Error(`Render failed: ${project.video.message}`);
if (project.video?.status === "COMPLETED") return project.video;
// The render starts on its own once the project is completed: keep polling
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
throw new Error("Timed out waiting for the video");
}
Pass a
webhook when you create the project and Hooked calls you when the video is ready or the project fails. See Webhooks.Errors
| Status | Body | Cause |
|---|---|---|
| 401 | code: "not_authenticated" | Missing or invalid x-api-key. |
| 403 | code: "entitlement_required" | Your team does not have the Hooked app product. |
| 404 | Project not found | The ID does not exist or belongs to another team. |
| 500 | Internal server error | Retry the request. |
Related
Creating videos
The create endpoints and the project lifecycle
Get Video Details
Read a rendered video by its ID
Webhooks
Get notified instead of polling
Render Project
Render a project again