# List Avatars Source: https://docs.hooked.so/api-reference/avatar/list GET /v1/avatar/list Get all available avatars (library + custom) for creating videos ## Overview This endpoint returns all avatars available to your account, including both the Hooked library avatars and any custom avatars you've created. ### Query Parameters Filter avatars by type: * `library`: Only library avatars from Hooked * `custom`: Only custom avatars created by your team * `all`: Both library and custom avatars (default) Page size. Maximum `100`. Number of rows to skip. Combine with `limit` to page through the list; you are done when `offset + limit >= total`. ### Response Indicates if the request was successful Array of avatar objects Unique identifier for the avatar Avatar display name Avatar type (AI or Realistic) Avatar gender (Male, Female, or Non-binary) Age category (Young, Middle-aged, Senior) URL of the avatar image (every video is generated from it) Whether this is a custom avatar Whether this avatar requires a Pro plan Whether this avatar supports HD quality Total number of avatars returned Number of library avatars Number of custom avatars ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/avatar/list?type=library" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/avatar/list?type=library', { headers: { 'x-api-key': 'your_api_key_here' } }); const data = await response.json(); console.log(data.data.avatars); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.hooked.so/v1/avatar/list', params={'type': 'library'}, headers={'x-api-key': 'your_api_key_here'} ) avatars = response.json()['data']['avatars'] ``` ```json Response theme={null} { "success": true, "data": { "avatars": [ { "id": "avatar_sarah_01", "name": "Sarah", "type": "Realistic", "gender": "Female", "age": "Young", "image": "https://cdn.hooked.so/avatars/sarah-thumb.jpg", "isHD": true, "isPro": false, "isNew": false, "situation": ["Business", "Casual"], "emotions": ["Happy", "Confident"], "skinTone": "medium", "isCustom": false, "source": "library" }, { "id": "avatar_john_02", "name": "John", "type": "Realistic", "gender": "Male", "age": "Middle-aged", "image": "https://cdn.hooked.so/avatars/john-thumb.jpg", "isHD": true, "isPro": true, "isNew": false, "situation": ["Business", "Professional"], "emotions": ["Neutral", "Serious"], "skinTone": "light", "isCustom": false, "source": "library" } ], "total": 2, "library": 2, "custom": 0 } } ``` ## Use Cases While the API doesn't support direct gender/age filtering, you can filter the results client-side: ```javascript theme={null} const youngFemaleAvatars = data.avatars.filter( avatar => avatar.gender === 'Female' && avatar.age === 'Young' ); ``` Get only avatars that support HD quality: ```javascript theme={null} const hdAvatars = data.avatars.filter(avatar => avatar.isHD); ``` Identify which avatars require a Pro subscription: ```python theme={null} pro_avatars = [a for a in avatars if a['isPro']] free_avatars = [a for a in avatars if not a['isPro']] ``` ## Notes Custom avatars require processing time after creation. Only avatars with `status: 2` (completed) are included in the list. Cache avatar lists for better performance. Avatar libraries don't change frequently, so you can safely cache this data for 24 hours. # Get Image Source: https://docs.hooked.so/api-reference/image/details GET /v1/image/{mediaId} An image of your library and how its background removal or expansion went ## Overview Returns an image of your library with the state of the edits started on it: the image itself (`status` is `PROCESSING` while an expansion paints it) and its background removal (`backgroundRemoval.status`). Poll it after [Remove Image Background](/api-reference/image/remove-background) or [Expand Image](/api-reference/image/expand) until the state you wait for is `COMPLETED`. ## Path Parameters The library image, as returned by the endpoint that started the edit. ```bash cURL theme={null} curl "https://api.hooked.so/v1/image/media_abc123" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/image/media_abc123', { headers: { 'x-api-key': 'your_api_key_here' } }); const { data } = await response.json(); if (data.backgroundRemoval.status === 'COMPLETED') console.log(data.backgroundRemoval.url); ``` ```json Response theme={null} { "success": true, "message": "Image details fetched successfully", "data": { "id": "media_abc123", "name": "portrait.jpg", "status": "COMPLETED", "url": "https://.../portrait.jpg", "aspectRatio": null, "backgroundRemoval": { "status": "COMPLETED", "url": "https://.../media_abc123-1726560000000.png" }, "usedCredits": 0.5, "createdAt": "2026-09-17T10:00:00.000Z", "updatedAt": "2026-09-17T10:00:12.000Z" } } ``` ## Response Fields `PROCESSING`, `COMPLETED` or `FAILED` — the image itself. Pending while an expansion is being painted. Signed URL of the image; `null` while it is still being made. `status` is `NONE`, `PROCESSING`, `COMPLETED` or `FAILED`; `url` is the signed PNG with transparency when `COMPLETED`. ## Status Codes | Status | Code | Description | | - | - | - | | Success | 200 | Image found | | Unauthorized | 401 | Missing or invalid API key | | Not found | 404 | No such image in your library | # Expand Image Source: https://docs.hooked.so/api-reference/image/expand POST /v1/image/expand Grow an image to another aspect ratio, with AI painting the new area ## Overview Starts growing an image to the aspect ratio you ask for. The original is centred on the larger canvas at its own size and the new area is generated, optionally guided by a prompt. The result is a **new** image in your library; the original is not modified. The call answers `202` with the pending image: poll [Get Image](/api-reference/image/details) on that `mediaId` until its `status` is `COMPLETED`. Accepted formats: JPEG, PNG and WEBP, up to 25 MB. An image that already has the requested ratio is rejected with 400. ## Request Body ID of an image in your media library. Required unless `imageUrl` is given. Public URL of the image. It is saved to your library before processing. Required unless `media` is given. Target ratio: `ratio_9_16`, `ratio_1_1` or `ratio_16_9`. What the new area should show, in English (up to 500 characters). Without it the scene is continued from the image itself. ```bash cURL theme={null} curl -X POST "https://api.hooked.so/v1/image/expand" \ -H "x-api-key: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "media": "media_abc123", "aspectRatio": "ratio_16_9", "prompt": "the same beach, more sand and sky" }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/image/expand', { method: 'POST', headers: { 'x-api-key': 'your_api_key_here', 'Content-Type': 'application/json' }, body: JSON.stringify({ imageUrl: 'https://example.com/product.png', aspectRatio: 'ratio_9_16' }) }); const { data } = await response.json(); console.log(data.mediaId, data.url); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.hooked.so/v1/image/expand', headers={'x-api-key': 'your_api_key_here'}, json={'media': 'media_abc123', 'aspectRatio': 'ratio_1_1'} ) print(response.json()['data']['url']) ``` ```json 202 Started theme={null} { "success": true, "message": "Image expansion started", "data": { "mediaId": "media_def456", "sourceMediaId": "media_abc123", "status": "PROCESSING", "aspectRatio": "ratio_16_9", "usedCredits": 1 } } ``` ## Response Fields The new library image being made at the requested ratio. Its `url` arrives in [Get Image](/api-reference/image/details) when `status` is `COMPLETED`. The image it is grown from. `PROCESSING` — the expansion is running. The ratio that was produced. Credits charged by this call. ## Status Codes | Status | Code | Description | | - | - | - | | Started | 202 | Expansion running; follow it with Get Image | | Validation error | 400 | Missing source, bad `aspectRatio`, or the image already has that ratio | | Unauthorized | 401 | Missing or invalid API key | | Payment required | 402 | Not enough credits, no active plan, or (BYOK) the Bria key is missing | | Provider error | 502 | Bria did not take the request; nothing was charged | If Bria fails later, the pending image ends as `status: "FAILED"` in Get Image and the credits are refunded. ## Related Endpoints Follow the expansion and read the result Cut the subject out with a transparent background Use the image in a video # Remove Image Background Source: https://docs.hooked.so/api-reference/image/remove-background POST /v1/image/remove-background Cut the subject out of an image and get a PNG with a transparent background ## Overview Starts removing the background of an image; the result is a PNG with real transparency, attached to the image in your library a moment later. The call answers `202` with the media to follow: poll [Get Image](/api-reference/image/details) until `backgroundRemoval.status` is `COMPLETED`. The image can be one already in your library (`media`) or any public URL (`imageUrl`); a URL is imported into your library first, so the result is always attached to a media you own. An image that already has its background removed answers `200` at once, without a new charge; one whose removal is already running answers `202` again with the same job. Accepted formats: JPEG, PNG and WEBP, up to 25 MB. ## Request Body ID of an image in your media library. Required unless `imageUrl` is given. Public URL of the image to process. It is saved to your library before processing. Required unless `media` is given. ```bash cURL theme={null} curl -X POST "https://api.hooked.so/v1/image/remove-background" \ -H "x-api-key: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "imageUrl": "https://example.com/portrait.jpg" }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/image/remove-background', { method: 'POST', headers: { 'x-api-key': 'your_api_key_here', 'Content-Type': 'application/json' }, body: JSON.stringify({ media: 'media_abc123' }) }); const { data } = await response.json(); console.log(data.backgroundRemovedUrl); // PNG with transparency ``` ```python Python theme={null} import requests response = requests.post( 'https://api.hooked.so/v1/image/remove-background', headers={'x-api-key': 'your_api_key_here'}, json={'imageUrl': 'https://example.com/portrait.jpg'} ) print(response.json()['data']['backgroundRemovedUrl']) ``` ```json 202 Started theme={null} { "success": true, "message": "Background removal started", "data": { "mediaId": "media_abc123", "status": "PROCESSING", "url": "https://.../portrait.jpg", "jobId": "aab23fd5fe10445c8fc1d2821b1482bd", "usedCredits": 0.5 } } ``` ```json 200 Already done theme={null} { "success": true, "message": "Background already removed", "data": { "mediaId": "media_abc123", "status": "COMPLETED", "url": "https://.../portrait.jpg", "backgroundRemovedUrl": "https://.../media_abc123-1726560000000.png", "usedCredits": 0 } } ``` ## Response Fields The library image the cut-out belongs to (the imported one when `imageUrl` was used). `PROCESSING` while Bria works (202), `COMPLETED` when the cut-out already existed (200). Signed URL of the original image. The removal job, while it runs. Signed URL of the PNG with the background removed, when `status` is `COMPLETED`. Otherwise read it from [Get Image](/api-reference/image/details) once `backgroundRemoval.status` is `COMPLETED`. Credits charged by this call. `0` when nothing new was started. ## Status Codes | Status | Code | Description | | - | - | - | | Started | 202 | Removal running; follow it with Get Image | | Already done | 200 | The image already had its cut-out | | Validation error | 400 | Missing or invalid `media` / `imageUrl` | | Unauthorized | 401 | Missing or invalid API key | | Payment required | 402 | Not enough credits, no active plan, or (BYOK) the Bria key is missing | | Provider error | 502 | Bria did not take the request; nothing was charged | If Bria fails later, the job ends as `backgroundRemoval.status: "FAILED"` in Get Image and the credits are refunded. ## Related Endpoints Follow the removal and read the result Grow an image to another aspect ratio The same for a video # Get Integration Details Source: https://docs.hooked.so/api-reference/integration/details GET /v1/integration/{integrationId} Get details and health status of a connected social platform **Try it out!** Use the API playground on the right to test the Get Integration Details endpoint directly. ## Overview Get detailed information about a specific connected social platform, including account details and connection health status. Use this to: * Verify if a connection is still valid before scheduling * Check if tokens have expired and need re-authentication * Get account details for display purposes If the health status shows `expired` or `error`, the user needs to reconnect the account through the [Hooked Dashboard](https://hooked.so/settings/integrations). *** ## Endpoint ``` GET /v1/integration/{integrationId} ``` *** ## Path Parameters The unique ID of the integration to retrieve *** ## Headers Your API key from [API Settings](https://hooked.so/settings/api) *** ## Response Whether the request was successful Unique integration ID Platform type: `youtube`, `tiktok`, or `instagram` Platform-specific identifier (channel ID, username, etc.) Connected account information Platform account ID Display name of the account Username or handle Profile picture URL Connection health status Health status: * `healthy` - Connection is working properly * `expired` - Token has expired, needs reconnection * `error` - Connection error, needs reconnection Whether the access token is still valid ISO 8601 timestamp of when health was checked When the integration was connected *** ## Request Example ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/integration/int_yt_abc123" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const integrationId = 'int_yt_abc123'; const response = await fetch(`https://api.hooked.so/v1/integration/${integrationId}`, { method: 'GET', headers: { 'x-api-key': process.env.HOOKED_API_KEY } }); const data = await response.json(); if (data.data.health.status !== 'healthy') { console.log('Integration needs reconnection!'); } else { console.log('Integration is healthy'); } ``` ```python Python theme={null} import requests integration_id = 'int_yt_abc123' response = requests.get( f'https://api.hooked.so/v1/integration/{integration_id}', headers={'x-api-key': 'your_api_key_here'} ) data = response.json() health = data['data']['health'] if health['status'] != 'healthy': print(f"Integration needs reconnection: {health['status']}") else: print("Integration is healthy") ``` *** ## Response Examples ```json Healthy Integration theme={null} { "success": true, "data": { "id": "int_yt_abc123", "type": "youtube", "identifier": "UC1234567890", "account": { "id": "UC1234567890", "name": "My YouTube Channel", "username": "mychannel", "avatarUrl": "https://yt3.ggpht.com/..." }, "health": { "status": "healthy", "tokenValid": true, "lastChecked": "2024-03-15T10:00:00.000Z" }, "createdAt": "2024-01-15T08:30:00.000Z" } } ``` ```json Expired Token theme={null} { "success": true, "data": { "id": "int_tt_xyz789", "type": "tiktok", "identifier": "@myaccount", "account": { "id": "6912345678901234567", "name": "My TikTok", "username": "myaccount", "avatarUrl": "https://p16-sign.tiktokcdn.com/..." }, "health": { "status": "expired", "tokenValid": false, "lastChecked": "2024-03-15T10:00:00.000Z" }, "createdAt": "2024-01-10T12:00:00.000Z" } } ``` ```json Error - Not Found theme={null} { "code": "not_found", "message": "Integration not found", "details": { "resource_id": "int_invalid", "resource_type": "Integration" } } ``` *** ## Health Status Values | Status | Description | Action Required | | - | - | - | | `healthy` | Connection is working, token is valid | None | | `expired` | Access token has expired | User must reconnect in dashboard | | `error` | Connection error or missing token | User must reconnect in dashboard | *** ## Checking Health Before Scheduling It's a good practice to check integration health before scheduling a video: ```javascript theme={null} async function scheduleIfHealthy(videoId, integrationId, scheduledDateTime) { // Check health first const healthResponse = await fetch( `https://api.hooked.so/v1/integration/${integrationId}`, { headers: { 'x-api-key': process.env.HOOKED_API_KEY } } ); const { data: integration } = await healthResponse.json(); if (integration.health.status !== 'healthy') { throw new Error(`Integration ${integration.type} needs reconnection`); } // Proceed with scheduling const scheduleResponse = await fetch( 'https://api.hooked.so/v1/schedule/create', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ videoId, integrationId, scheduledDateTime }) } ); return await scheduleResponse.json(); } ``` *** ## Next Steps View all connected platforms Remove a connected account Schedule a video for publishing Reconnect accounts in dashboard # Disconnect Integration Source: https://docs.hooked.so/api-reference/integration/disconnect DELETE /v1/integration/{integrationId} Remove a connected social media platform **Try it out!** Use the API playground on the right to test the Disconnect Integration endpoint directly. ## Overview Disconnect a social media platform from your account. This removes the connection and all associated tokens. **This action is permanent.** Once disconnected: * All pending scheduled posts for this platform will fail * You'll need to reconnect through the dashboard to post again * Historical post data will be preserved *** ## Endpoint ``` DELETE /v1/integration/{integrationId} ``` *** ## Path Parameters The unique ID of the integration to disconnect *** ## Headers Your API key from [API Settings](https://hooked.so/settings/api) *** ## Response Whether the request was successful Status message ID of the disconnected integration Platform type that was disconnected Confirmation of deletion (always `true`) *** ## Request Example ```bash cURL theme={null} curl -X DELETE "https://api.hooked.so/v1/integration/int_yt_abc123" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const integrationId = 'int_yt_abc123'; const response = await fetch(`https://api.hooked.so/v1/integration/${integrationId}`, { method: 'DELETE', headers: { 'x-api-key': process.env.HOOKED_API_KEY } }); const data = await response.json(); if (data.success) { console.log(`Disconnected ${data.data.type} integration`); } ``` ```python Python theme={null} import requests integration_id = 'int_yt_abc123' response = requests.delete( f'https://api.hooked.so/v1/integration/{integration_id}', headers={'x-api-key': 'your_api_key_here'} ) data = response.json() if data['success']: print(f"Disconnected {data['data']['type']} integration") ``` ```php PHP theme={null} $integrationId = 'int_yt_abc123'; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://api.hooked.so/v1/integration/{$integrationId}", CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_HTTPHEADER => [ 'x-api-key: your_api_key_here' ] ]); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); if ($data['success']) { echo "Disconnected " . $data['data']['type'] . " integration"; } ``` *** ## Response Examples ```json Success Response theme={null} { "success": true, "message": "Integration disconnected successfully", "data": { "id": "int_yt_abc123", "type": "youtube", "deleted": true } } ``` ```json Error - Not Found theme={null} { "code": "not_found", "message": "Integration not found", "details": { "resource_id": "int_invalid", "resource_type": "Integration" } } ``` ```json Error - Forbidden theme={null} { "code": "forbidden", "message": "Integration does not belong to your team", "details": {} } ``` *** ## What Happens After Disconnection ### Pending Scheduled Posts Any posts scheduled for this integration will: 1. Remain in `pending` status 2. Fail when their scheduled time arrives 3. Status will change to `failed` with an error message **Recommendation:** Cancel any pending scheduled posts before disconnecting: ```javascript theme={null} async function disconnectSafely(integrationId) { // 1. Get all pending posts for this integration const postsResponse = await fetch( `https://api.hooked.so/v1/schedule/list?status=pending`, { headers: { 'x-api-key': process.env.HOOKED_API_KEY } } ); const { data: { scheduledPosts } } = await postsResponse.json(); // 2. Filter posts for this integration const affectedPosts = scheduledPosts.filter( post => post.integration.id === integrationId ); if (affectedPosts.length > 0) { console.log(`Warning: ${affectedPosts.length} pending posts will fail`); // Optional: Cancel them first for (const post of affectedPosts) { await fetch(`https://api.hooked.so/v1/schedule/${post.id}`, { method: 'DELETE', headers: { 'x-api-key': process.env.HOOKED_API_KEY } }); } } // 3. Disconnect the integration const response = await fetch( `https://api.hooked.so/v1/integration/${integrationId}`, { method: 'DELETE', headers: { 'x-api-key': process.env.HOOKED_API_KEY } } ); return await response.json(); } ``` ### Historical Data * Previously published posts remain on the platform * Post history in Hooked is preserved for reference * Analytics data is retained *** ## Reconnecting To reconnect a platform after disconnecting: 1. Go to [Hooked Dashboard](https://hooked.so/settings/integrations) 2. Click **Connect** on the platform you want to add 3. Complete the OAuth authorization flow 4. A new integration ID will be generated Reconnecting creates a **new integration** with a new ID. Update any stored integration IDs in your application. *** ## Next Steps View remaining connected platforms Add a new platform in dashboard Cancel pending posts View scheduled posts # List Integrations Source: https://docs.hooked.so/api-reference/integration/list GET /v1/integration/list Get all connected social media platforms for publishing videos **Try it out!** Use the API playground on the right to test the List Integrations endpoint directly. ## Overview List all social media integrations connected to your account. These integrations are used to schedule and publish videos to platforms like YouTube, TikTok, and Instagram. Before scheduling a video, you need to connect at least one social platform through the Hooked dashboard. Integrations are configured in your [Hooked Dashboard](https://hooked.so/settings/integrations). Each integration represents a connected social media account. *** ## Endpoint ``` GET /v1/integration/list ``` *** ## Headers Your API key from [API Settings](https://hooked.so/settings/api) *** ## Query Parameters Page size. Maximum `100`. Number of rows to skip. Combine with `limit` to page through the list; you are done when `offset + limit >= total`. ## Response Whether the request was successful Status message Array of connected integrations Unique integration ID. Use this when scheduling videos. Platform type: `youtube`, `tiktok`, or `instagram` Platform-specific identifier (channel ID, username, etc.) Connected account information Platform account ID Display name of the account Username or handle Profile picture URL Total number of integrations *** ## Request Example ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/integration/list" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/integration/list', { method: 'GET', headers: { 'x-api-key': process.env.HOOKED_API_KEY } }); const data = await response.json(); console.log('Integrations:', data.data.integrations); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.hooked.so/v1/integration/list', headers={'x-api-key': 'your_api_key_here'} ) data = response.json() print('Integrations:', data['data']['integrations']) ``` ```php PHP theme={null} $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://api.hooked.so/v1/integration/list', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: your_api_key_here' ] ]); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); print_r($data['data']['integrations']); ``` ```go Go theme={null} package main import ( "fmt" "io" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://api.hooked.so/v1/integration/list", nil) req.Header.Set("x-api-key", "your_api_key_here") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` *** ## Response Examples ```json Success Response theme={null} { "success": true, "message": "Integrations fetched successfully", "data": { "integrations": [ { "id": "int_yt_abc123", "type": "youtube", "identifier": "UC1234567890", "account": { "id": "UC1234567890", "name": "My YouTube Channel", "username": "mychannel", "avatarUrl": "https://yt3.ggpht.com/..." } }, { "id": "int_tt_xyz789", "type": "tiktok", "identifier": "@myaccount", "account": { "id": "6912345678901234567", "name": "My TikTok", "username": "myaccount", "avatarUrl": "https://p16-sign.tiktokcdn.com/..." } }, { "id": "int_ig_def456", "type": "instagram", "identifier": "myinstagram", "account": { "id": "12345678901234567", "name": "My Instagram", "username": "myinstagram", "avatarUrl": "https://instagram.com/..." } } ], "total": 3 } } ``` ```json No Integrations theme={null} { "success": true, "message": "Integrations fetched successfully", "data": { "integrations": [], "total": 0 } } ``` ```json Error Response theme={null} { "code": "not_authenticated", "message": "Not authenticated", "details": { "x-api-key": "Header not provided or API Key invalid" } } ``` *** ## Supported Platforms | Platform | Type | Features | | - | - | - | | YouTube | `youtube` | Shorts, Long-form videos, Privacy settings | | TikTok | `tiktok` | Short videos, Direct publishing | | Instagram | `instagram` | Reels, Posts, Stories | *** ## Connecting Integrations To connect a new social platform: 1. Go to [Hooked Dashboard](https://hooked.so) 2. Navigate to **Settings** > **Integrations** 3. Click **Connect** on the platform you want to add 4. Complete the OAuth authorization flow Each platform has specific requirements: * **YouTube**: Requires a YouTube channel with uploads enabled * **TikTok**: Requires a TikTok Creator or Business account * **Instagram**: Requires an Instagram Business or Creator account connected to a Facebook Page *** ## Next Steps Schedule a video for publishing View all scheduled posts Create videos to schedule Get notified when posts are published # List Music Source: https://docs.hooked.so/api-reference/music/list GET /v1/music/list Get all available music tracks for background audio ## Overview List all background music tracks available for your videos. ### Query Parameters Filter by category (e.g., "Electronic", "Pop", "Classical") Filter by genre (e.g., "Electronic", "Viral", "Calm") ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/music/list?category=Electronic" \ -H "x-api-key: your_api_key_here" ``` ```json Response theme={null} { "success": true, "data": { "music": [ { "id": "1", "name": "Bladerunner 2049", "url": "https://cdn.hooked.so/music/bladerunner.mp3", "thumbnail": "https://cdn.hooked.so/music/thumbs/bladerunner.jpg", "category": "Futuristic", "duration": "03:00", "genres": ["Futuristic", "Electronic"] } ], "categories": [ { "id": 1, "name": "Electronic", "description": "Electronic music collection" } ], "total": 200 } } ``` # Schedule Video Source: https://docs.hooked.so/api-reference/schedule/create POST /v1/schedule/create Schedule a video for automatic publishing to social platforms **Try it out!** Use the API playground on the right to test the Schedule Video endpoint directly. ## Overview Schedule a completed video for automatic publishing to a connected social platform. The video will be published at the specified time without any manual intervention. This is perfect for: * Building a content calendar * Automating your publishing workflow * Scheduling content for optimal posting times * Bulk scheduling videos across platforms Videos must be in **completed** status before they can be scheduled. Connect your social platforms in the [Hooked Dashboard](https://hooked.so/settings/integrations) first. *** ## Endpoint ``` POST /v1/schedule/create ``` *** ## Required Fields ID of the completed video to schedule. Get this from the video creation response or the `/v1/video/list` endpoint. ID of the connected social platform. Get available integrations from `/v1/integration/list`. ISO 8601 datetime for when to publish the video. Must be in the future. Example: `2024-03-15T14:00:00.000Z` *** ## Optional Fields Platform-specific metadata for the post Post title (max 100 characters). Used for YouTube video titles. Post description (max 5000 characters). Used for video descriptions and captions. Array of tags/hashtags (max 30). Platform-specific handling: * **YouTube**: Video tags * **TikTok/Instagram**: Appended as hashtags to description Privacy setting (YouTube only): * `public` - Anyone can view * `unlisted` - Only people with the link can view * `private` - Only you can view Whether content is made for kids (YouTube only). Required for COPPA compliance. *** ## Request Examples ### Basic Schedule ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/schedule/create', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ videoId: 'vid_abc123xyz', integrationId: 'int_yt_def456', scheduledDateTime: '2024-03-15T14:00:00.000Z' }) }); const data = await response.json(); console.log('Scheduled:', data.data.scheduleId); ``` ### With Platform Data (YouTube) ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/schedule/create', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ videoId: 'vid_abc123xyz', integrationId: 'int_yt_def456', scheduledDateTime: '2024-03-15T14:00:00.000Z', platformData: { title: '10 Tips for Better Productivity', description: 'In this video, I share my top 10 productivity tips that have helped me work smarter, not harder.\n\nTimestamps:\n0:00 Intro\n0:30 Tip 1...', tags: ['productivity', 'tips', 'workflow', 'efficiency'], privacyStatus: 'public', madeForKids: false } }) }); ``` ### With Platform Data (TikTok/Instagram) ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/schedule/create', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ videoId: 'vid_abc123xyz', integrationId: 'int_tt_ghi789', scheduledDateTime: '2024-03-15T18:00:00.000Z', platformData: { description: 'This productivity hack changed everything!', tags: ['productivity', 'lifehack', 'tips', 'viral'] } }) }); ``` ### Complete Workflow Example ```javascript theme={null} async function createAndScheduleVideo() { // Step 1: Create a video const videoResponse = await fetch('https://api.hooked.so/v1/project/create/script-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ script: 'Your video script here...', voiceId: 'voice_confident', mediaType: 'ai-images' }) }); const { data: videoData } = await videoResponse.json(); const videoId = videoData.videoId; // Step 2: Wait for video to complete (use webhooks in production!) // ... // Step 3: Get available integrations const integrationsResponse = await fetch('https://api.hooked.so/v1/integration/list', { headers: { 'x-api-key': process.env.HOOKED_API_KEY } }); const { data: intData } = await integrationsResponse.json(); const youtubeIntegration = intData.integrations.find(i => i.type === 'youtube'); // Step 4: Schedule for tomorrow at 2 PM UTC const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(14, 0, 0, 0); const scheduleResponse = await fetch('https://api.hooked.so/v1/schedule/create', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ videoId: videoId, integrationId: youtubeIntegration.id, scheduledDateTime: tomorrow.toISOString(), platformData: { title: 'My Awesome Video', description: 'Check out this video!', privacyStatus: 'public' } }) }); return await scheduleResponse.json(); } ``` ```bash cURL theme={null} curl -X POST "https://api.hooked.so/v1/schedule/create" \ -H "x-api-key: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "videoId": "vid_abc123xyz", "integrationId": "int_yt_def456", "scheduledDateTime": "2024-03-15T14:00:00.000Z", "platformData": { "title": "10 Tips for Productivity", "description": "Learn how to boost your productivity...", "tags": ["productivity", "tips"], "privacyStatus": "public" } }' ``` ```python Python theme={null} import requests from datetime import datetime, timedelta # Schedule for tomorrow at 2 PM UTC scheduled_time = (datetime.utcnow() + timedelta(days=1)).replace(hour=14, minute=0, second=0) response = requests.post( 'https://api.hooked.so/v1/schedule/create', headers={ 'x-api-key': 'your_api_key_here', 'Content-Type': 'application/json' }, json={ 'videoId': 'vid_abc123xyz', 'integrationId': 'int_yt_def456', 'scheduledDateTime': scheduled_time.isoformat() + 'Z', 'platformData': { 'title': '10 Tips for Productivity', 'description': 'Learn how to boost your productivity...', 'tags': ['productivity', 'tips'], 'privacyStatus': 'public' } } ) data = response.json() print('Schedule ID:', data['data']['scheduleId']) ``` *** ## Response ```json Success Response theme={null} { "success": true, "message": "Video scheduled successfully", "data": { "scheduleId": "post_abc123xyz", "status": "pending", "scheduledDateTime": "2024-03-15T14:00:00.000Z", "platform": "youtube", "videoId": "vid_abc123xyz" } } ``` ```json Error - Video Not Completed theme={null} { "code": "bad_request", "message": "Video must be completed before scheduling", "details": { "videoStatus": "processing" } } ``` ```json Error - Time in Past theme={null} { "code": "bad_request", "message": "Scheduled time must be in the future", "details": { "scheduledDateTime": "2024-03-01T14:00:00.000Z", "currentTime": "2024-03-10T10:00:00.000Z" } } ``` ```json Error - Video Not Found theme={null} { "code": "not_found", "message": "Video not found", "details": { "resource_id": "vid_invalid", "resource_type": "Video" } } ``` ```json Error - Integration Not Found theme={null} { "code": "not_found", "message": "Integration not found", "details": { "resource_id": "int_invalid", "resource_type": "Integration" } } ``` *** ## Platform-Specific Notes ### YouTube | Field | Requirement | Notes | | - | - | - | | `title` | Recommended | Max 100 characters. Falls back to video name if not provided. | | `description` | Recommended | Max 5000 characters. Supports line breaks and links. | | `tags` | Optional | Max 30 tags. Used for search discovery. | | `privacyStatus` | Optional | Defaults to `public`. | | `madeForKids` | Optional | Required for COPPA compliance. Defaults to `false`. | ### TikTok | Field | Requirement | Notes | | - | - | - | | `description` | Recommended | Max 2200 characters. Hashtags are auto-extracted. | | `tags` | Optional | Appended as hashtags to description. | ### Instagram | Field | Requirement | Notes | | - | - | - | | `description` | Recommended | Caption for the post. Max 2200 characters. | | `tags` | Optional | Appended as hashtags to caption. | *** ## Best Practices Schedule at least 15 minutes in the future to allow for processing. Research your audience's active hours for better engagement. Get notified when posts are published or if publishing fails. Add titles, descriptions, and tags for better discoverability. *** ## Error Handling | Error | Description | Solution | | - | - | - | | `Video not found` | Invalid video ID | Use a valid video ID from `/v1/video/list` | | `Integration not found` | Invalid integration ID | Use a valid integration ID from `/v1/integration/list` | | `Video must be completed` | Video still processing | Wait for video to complete before scheduling | | `Scheduled time must be in the future` | Time has already passed | Use a future datetime | | `Video does not belong to your team` | Wrong team | Ensure you're using the correct API key | *** ## Next Steps View all your scheduled posts Change time or metadata Cancel a scheduled post Get notified on publish # Cancel Schedule Source: https://docs.hooked.so/api-reference/schedule/delete DELETE /v1/schedule/{scheduleId} Cancel a scheduled post before it's published **Try it out!** Use the API playground on the right to test the Cancel Schedule endpoint directly. ## Overview Cancel a scheduled post to prevent it from being published. This permanently removes the scheduled post from the queue. **This action is permanent.** Once cancelled, the scheduled post cannot be recovered. You'll need to create a new schedule if you want to publish the video. You can only cancel posts with `pending` status. Posts that have already been published cannot be deleted through this API - you'll need to delete them directly on the platform. *** ## Endpoint ``` DELETE /v1/schedule/{scheduleId} ``` *** ## Path Parameters The unique ID of the scheduled post to cancel *** ## Headers Your API key from [API Settings](https://hooked.so/settings/api) *** ## Request Examples ```javascript theme={null} const scheduleId = 'post_abc123xyz'; const response = await fetch(`https://api.hooked.so/v1/schedule/${scheduleId}`, { method: 'DELETE', headers: { 'x-api-key': process.env.HOOKED_API_KEY } }); const data = await response.json(); if (data.success) { console.log('Schedule cancelled successfully'); } else { console.error('Failed to cancel:', data.message); } ``` ### Bulk Cancel Example ```javascript theme={null} async function cancelMultipleSchedules(scheduleIds) { const results = await Promise.allSettled( scheduleIds.map(async (id) => { const response = await fetch(`https://api.hooked.so/v1/schedule/${id}`, { method: 'DELETE', headers: { 'x-api-key': process.env.HOOKED_API_KEY } }); return { id, result: await response.json() }; }) ); const cancelled = results .filter(r => r.status === 'fulfilled' && r.value.result.success) .map(r => r.value.id); const failed = results .filter(r => r.status === 'rejected' || !r.value.result.success) .map(r => r.value?.id || 'unknown'); return { cancelled, failed }; } // Usage const { cancelled, failed } = await cancelMultipleSchedules([ 'post_abc123', 'post_def456', 'post_ghi789' ]); console.log(`Cancelled: ${cancelled.length}, Failed: ${failed.length}`); ``` ```bash cURL theme={null} curl -X DELETE "https://api.hooked.so/v1/schedule/post_abc123xyz" \ -H "x-api-key: your_api_key_here" ``` ```python Python theme={null} import requests schedule_id = 'post_abc123xyz' response = requests.delete( f'https://api.hooked.so/v1/schedule/{schedule_id}', headers={'x-api-key': 'your_api_key_here'} ) data = response.json() if data['success']: print('Schedule cancelled successfully') else: print('Failed:', data['message']) ``` ```php PHP theme={null} $scheduleId = 'post_abc123xyz'; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://api.hooked.so/v1/schedule/{$scheduleId}", CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_HTTPHEADER => [ 'x-api-key: your_api_key_here' ] ]); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); if ($data['success']) { echo 'Schedule cancelled successfully'; } else { echo 'Failed: ' . $data['message']; } ``` ```go Go theme={null} package main import ( "fmt" "io" "net/http" ) func main() { scheduleId := "post_abc123xyz" url := fmt.Sprintf("https://api.hooked.so/v1/schedule/%s", scheduleId) req, _ := http.NewRequest("DELETE", url, nil) req.Header.Set("x-api-key", "your_api_key_here") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` *** ## Response ```json Success Response theme={null} { "success": true, "message": "Schedule cancelled successfully", "data": { "id": "post_abc123xyz", "deleted": true } } ``` ```json Error - Already Published theme={null} { "code": "bad_request", "message": "Cannot delete a published post", "details": {} } ``` ```json Error - Not Found theme={null} { "code": "not_found", "message": "Schedule not found", "details": { "resource_id": "post_invalid", "resource_type": "Schedule" } } ``` ```json Error - Forbidden theme={null} { "code": "forbidden", "message": "Schedule does not belong to your team", "details": {} } ``` *** ## Important Notes ### What Happens After Cancellation * The scheduled post is permanently deleted * The video itself is NOT deleted - only the schedule * You can schedule the same video again with a new schedule * Webhooks for this schedule will not be triggered ### Published Posts If a post has already been published (`status: published`), you cannot delete it through this API. To remove published content: 1. **YouTube**: Go to YouTube Studio and delete the video 2. **TikTok**: Open TikTok app and delete the post 3. **Instagram**: Use Instagram app to delete the reel/post *** ## Error Handling | Error | Description | Solution | | - | - | - | | `Schedule not found` | Invalid schedule ID | Check the schedule ID is correct | | `Cannot delete a published post` | Already published | Delete directly on the platform | | `Schedule does not belong to your team` | Wrong ownership | Use correct API key | *** ## Next Steps Schedule a new video View all scheduled posts View all your videos View connected platforms # Get Schedule Details Source: https://docs.hooked.so/api-reference/schedule/details GET /v1/schedule/{scheduleId} Get detailed information about a specific scheduled post **Try it out!** Use the API playground on the right to test the Get Schedule Details endpoint directly. ## Overview Get complete details about a specific scheduled post, including video information, platform data, and publishing status. This endpoint is useful for: * Checking the status of a scheduled post * Retrieving platform-specific metadata * Getting the video URL and thumbnail for preview *** ## Endpoint ``` GET /v1/schedule/{scheduleId} ``` *** ## Path Parameters The unique ID of the scheduled post to retrieve *** ## Headers Your API key from [API Settings](https://hooked.so/settings/api) *** ## Response Whether the request was successful Unique schedule ID Current status: `pending`, `published`, `failed`, or `draft` ISO 8601 datetime when the post will be/was published Target platform: `youtube`, `tiktok`, or `instagram` Connected platform account details Video information including signed URLs Platform-specific metadata (title, description, tags, etc.) When the schedule was created When the schedule was last updated *** ## Request Example ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/schedule/post_abc123xyz" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const scheduleId = 'post_abc123xyz'; const response = await fetch(`https://api.hooked.so/v1/schedule/${scheduleId}`, { method: 'GET', headers: { 'x-api-key': process.env.HOOKED_API_KEY } }); const data = await response.json(); console.log('Schedule:', data.data); console.log('Status:', data.data.status); console.log('Scheduled for:', data.data.scheduledDateTime); ``` ```python Python theme={null} import requests schedule_id = 'post_abc123xyz' response = requests.get( f'https://api.hooked.so/v1/schedule/{schedule_id}', headers={'x-api-key': 'your_api_key_here'} ) data = response.json() print('Status:', data['data']['status']) print('Scheduled for:', data['data']['scheduledDateTime']) ``` ```php PHP theme={null} $scheduleId = 'post_abc123xyz'; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://api.hooked.so/v1/schedule/{$scheduleId}", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: your_api_key_here' ] ]); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); print_r($data['data']); ``` ```go Go theme={null} package main import ( "fmt" "io" "net/http" ) func main() { scheduleId := "post_abc123xyz" url := fmt.Sprintf("https://api.hooked.so/v1/schedule/%s", scheduleId) req, _ := http.NewRequest("GET", url, nil) req.Header.Set("x-api-key", "your_api_key_here") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` *** ## Response Examples ```json Success Response - Pending theme={null} { "success": true, "data": { "id": "post_abc123xyz", "status": "pending", "scheduledDateTime": "2024-03-15T14:00:00.000Z", "platform": "youtube", "integration": { "id": "int_yt_def456", "type": "youtube", "account": { "id": "UC1234567890", "name": "My YouTube Channel", "username": "mychannel", "avatarUrl": "https://yt3.ggpht.com/..." } }, "video": { "id": "vid_ghi789", "name": "10 Tips for Productivity", "url": "https://cdn.hooked.so/signed/...", "thumbnail": "https://cdn.hooked.so/signed/...", "durationInSeconds": 45 }, "platformData": { "title": "10 Tips for Productivity", "description": "Learn how to boost your productivity...", "tags": ["productivity", "tips", "workflow"], "privacyStatus": "public", "madeForKids": false }, "createdAt": "2024-03-10T10:00:00.000Z", "updatedAt": "2024-03-10T10:00:00.000Z" } } ``` ```json Success Response - Published theme={null} { "success": true, "data": { "id": "post_abc123xyz", "status": "published", "scheduledDateTime": "2024-03-15T14:00:00.000Z", "platform": "youtube", "integration": { "id": "int_yt_def456", "type": "youtube", "account": { "id": "UC1234567890", "name": "My YouTube Channel", "username": "mychannel", "avatarUrl": "https://yt3.ggpht.com/..." } }, "video": { "id": "vid_ghi789", "name": "10 Tips for Productivity", "url": "https://cdn.hooked.so/signed/...", "thumbnail": "https://cdn.hooked.so/signed/...", "durationInSeconds": 45 }, "platformData": { "title": "10 Tips for Productivity", "description": "Learn how to boost your productivity...", "tags": ["productivity", "tips"], "privacyStatus": "public", "publishedUrl": "https://youtube.com/shorts/abc123", "publishedAt": "2024-03-15T14:00:05.000Z" }, "createdAt": "2024-03-10T10:00:00.000Z", "updatedAt": "2024-03-15T14:00:05.000Z" } } ``` ```json Error Response - Not Found theme={null} { "code": "not_found", "message": "Schedule not found", "details": { "resource_id": "post_invalid", "resource_type": "Schedule" } } ``` ```json Error Response - Forbidden theme={null} { "code": "forbidden", "message": "Schedule does not belong to your team", "details": {} } ``` *** ## Status Values | Status | Description | | - | - | | `pending` | Waiting to be published at the scheduled time | | `published` | Successfully published. Check `platformData.publishedUrl` for the live link | | `failed` | Publishing failed. Check `platformData.error` for details | | `draft` | Saved but not scheduled | *** ## Next Steps Change the scheduled time or metadata Cancel a pending scheduled post View all scheduled posts Get notified on status changes # List Scheduled Posts Source: https://docs.hooked.so/api-reference/schedule/list GET /v1/schedule/list Get all scheduled posts for your account with filtering options **Try it out!** Use the API playground on the right to test the List Scheduled Posts endpoint directly. ## Overview List all videos scheduled for publishing to your connected social platforms. Use filters to find specific posts by status or platform. This endpoint is useful for: * Building a content calendar view * Monitoring scheduled content status * Managing your publishing pipeline Scheduled posts are automatically published at their scheduled time. The status will change from `pending` to `published` or `failed` after the publishing attempt. *** ## Endpoint ``` GET /v1/schedule/list ``` *** ## Headers Your API key from [API Settings](https://hooked.so/settings/api) *** ## Query Parameters Filter by post status: * `pending` - Waiting to be published * `published` - Successfully published * `failed` - Publication failed * `draft` - Draft posts (not scheduled) Filter by platform: * `youtube` - YouTube posts * `tiktok` - TikTok posts * `instagram` - Instagram posts Number of results to return (max: 100) Offset for pagination *** ## Response Whether the request was successful Array of scheduled posts Unique schedule ID Current status: `pending`, `published`, `failed`, or `draft` ISO 8601 datetime when the post will be published Target platform: `youtube`, `tiktok`, or `instagram` Connected platform account Integration ID Platform type Account details (name, username, avatarUrl) Video information Video ID Video name Signed URL to the video file Signed URL to the thumbnail Video duration in seconds Platform-specific metadata (title, description, tags, etc.) When the schedule was created When the schedule was last updated Total number of matching posts Applied limit Applied offset *** ## Request Examples ### Basic Request ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/schedule/list', { method: 'GET', headers: { 'x-api-key': process.env.HOOKED_API_KEY } }); const data = await response.json(); console.log('Scheduled posts:', data.data.scheduledPosts); ``` ### With Filters ```javascript theme={null} // Get only pending TikTok posts const url = new URL('https://api.hooked.so/v1/schedule/list'); url.searchParams.set('status', 'pending'); url.searchParams.set('platform', 'tiktok'); url.searchParams.set('limit', '20'); const response = await fetch(url, { headers: { 'x-api-key': process.env.HOOKED_API_KEY } }); const data = await response.json(); ``` ### With Pagination ```javascript theme={null} async function getAllScheduledPosts() { const allPosts = []; let offset = 0; const limit = 50; while (true) { const response = await fetch( `https://api.hooked.so/v1/schedule/list?limit=${limit}&offset=${offset}`, { headers: { 'x-api-key': process.env.HOOKED_API_KEY } } ); const { data } = await response.json(); allPosts.push(...data.scheduledPosts); if (data.scheduledPosts.length < limit) break; offset += limit; } return allPosts; } ``` ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/schedule/list?status=pending&platform=youtube&limit=20" \ -H "x-api-key: your_api_key_here" ``` ```python Python theme={null} import requests response = requests.get( 'https://api.hooked.so/v1/schedule/list', headers={'x-api-key': 'your_api_key_here'}, params={ 'status': 'pending', 'platform': 'youtube', 'limit': 20 } ) data = response.json() for post in data['data']['scheduledPosts']: print(f"{post['video']['name']} - {post['scheduledDateTime']}") ``` *** ## Response Examples ```json Success Response theme={null} { "success": true, "message": "Scheduled posts fetched successfully", "data": { "scheduledPosts": [ { "id": "post_abc123", "status": "pending", "scheduledDateTime": "2024-03-15T14:00:00.000Z", "platform": "youtube", "integration": { "id": "int_yt_xyz789", "type": "youtube", "account": { "id": "UC1234567890", "name": "My YouTube Channel", "username": "mychannel", "avatarUrl": "https://yt3.ggpht.com/..." } }, "video": { "id": "vid_def456", "name": "10 Tips for Productivity", "url": "https://cdn.hooked.so/...", "thumbnail": "https://cdn.hooked.so/...", "durationInSeconds": 45 }, "platformData": { "title": "10 Tips for Productivity", "description": "Learn how to boost your productivity...", "tags": ["productivity", "tips", "workflow"], "privacyStatus": "public" }, "createdAt": "2024-03-10T10:00:00.000Z", "updatedAt": "2024-03-10T10:00:00.000Z" } ], "total": 1, "limit": 50, "offset": 0 } } ``` ```json Empty Response theme={null} { "success": true, "message": "Scheduled posts fetched successfully", "data": { "scheduledPosts": [], "total": 0, "limit": 50, "offset": 0 } } ``` *** ## Status Values | Status | Description | | - | - | | `pending` | Scheduled and waiting to be published at the specified time | | `published` | Successfully published to the platform | | `failed` | Publishing failed (check platformData for error details) | | `draft` | Saved but not scheduled for publishing | *** ## Best Practices Instead of polling, use webhooks to get notified when posts are published or fail. For accounts with many scheduled posts, use offset/limit pagination. Filter by `pending` to see upcoming posts, or `failed` to find issues. All times are in UTC. Convert to local time for display. *** ## Next Steps Schedule a new video for publishing Change scheduled time or metadata Cancel a scheduled post Get full details of a scheduled post # Update Schedule Source: https://docs.hooked.so/api-reference/schedule/update PATCH /v1/schedule/{scheduleId} Update the scheduled time or platform data of a pending post **Try it out!** Use the API playground on the right to test the Update Schedule endpoint directly. ## Overview Update a scheduled post to change its publishing time or platform metadata. This is useful for: * Rescheduling a post to a different time * Updating the title, description, or tags before publishing * Changing privacy settings You can only update posts with `pending` status. Published or failed posts cannot be modified. *** ## Endpoint ``` PATCH /v1/schedule/{scheduleId} ``` *** ## Path Parameters The unique ID of the scheduled post to update *** ## Headers Your API key from [API Settings](https://hooked.so/settings/api) *** ## Request Body New ISO 8601 datetime for publishing. Must be in the future. Example: `2024-03-20T16:00:00.000Z` Updated platform-specific metadata. Fields are merged with existing data. New post title (max 100 characters) New post description (max 5000 characters) New tags array (max 30 tags). Replaces existing tags. New privacy setting: `public`, `unlisted`, or `private` Update kids content flag (YouTube only) *** ## Request Examples ### Reschedule to Different Time ```javascript theme={null} const scheduleId = 'post_abc123xyz'; const response = await fetch(`https://api.hooked.so/v1/schedule/${scheduleId}`, { method: 'PATCH', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ scheduledDateTime: '2024-03-20T16:00:00.000Z' }) }); const data = await response.json(); console.log('New scheduled time:', data.data.scheduledDateTime); ``` ### Update Platform Metadata ```javascript theme={null} const scheduleId = 'post_abc123xyz'; const response = await fetch(`https://api.hooked.so/v1/schedule/${scheduleId}`, { method: 'PATCH', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ platformData: { title: 'Updated: 10 Amazing Productivity Tips', description: 'I updated this video with even better tips!\n\nWatch to learn more...', tags: ['productivity', 'tips', 'updated', '2024'] } }) }); ``` ### Update Both Time and Metadata ```javascript theme={null} const scheduleId = 'post_abc123xyz'; const response = await fetch(`https://api.hooked.so/v1/schedule/${scheduleId}`, { method: 'PATCH', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ scheduledDateTime: '2024-03-20T18:00:00.000Z', platformData: { title: 'Evening Edition: Productivity Tips', privacyStatus: 'unlisted' } }) }); ``` ```bash cURL theme={null} curl -X PATCH "https://api.hooked.so/v1/schedule/post_abc123xyz" \ -H "x-api-key: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "scheduledDateTime": "2024-03-20T16:00:00.000Z", "platformData": { "title": "Updated Title", "description": "Updated description with more details" } }' ``` ```python Python theme={null} import requests schedule_id = 'post_abc123xyz' response = requests.patch( f'https://api.hooked.so/v1/schedule/{schedule_id}', headers={ 'x-api-key': 'your_api_key_here', 'Content-Type': 'application/json' }, json={ 'scheduledDateTime': '2024-03-20T16:00:00.000Z', 'platformData': { 'title': 'Updated Title', 'description': 'Updated description' } } ) data = response.json() print('Updated:', data['data']) ``` ```php PHP theme={null} $scheduleId = 'post_abc123xyz'; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://api.hooked.so/v1/schedule/{$scheduleId}", CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'PATCH', CURLOPT_HTTPHEADER => [ 'x-api-key: your_api_key_here', 'Content-Type: application/json' ], CURLOPT_POSTFIELDS => json_encode([ 'scheduledDateTime' => '2024-03-20T16:00:00.000Z', 'platformData' => [ 'title' => 'Updated Title' ] ]) ]); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); print_r($data); ``` *** ## Response ```json Success Response theme={null} { "success": true, "message": "Schedule updated successfully", "data": { "id": "post_abc123xyz", "status": "pending", "scheduledDateTime": "2024-03-20T16:00:00.000Z", "platformData": { "title": "Updated Title", "description": "Updated description with more details", "tags": ["productivity", "tips"], "privacyStatus": "public" } } } ``` ```json Error - Already Published theme={null} { "code": "bad_request", "message": "Cannot update a published post", "details": {} } ``` ```json Error - Time in Past theme={null} { "code": "bad_request", "message": "Scheduled time must be in the future", "details": { "scheduledDateTime": "2024-03-01T14:00:00.000Z", "currentTime": "2024-03-10T10:00:00.000Z" } } ``` ```json Error - Not Found theme={null} { "code": "not_found", "message": "Schedule not found", "details": { "resource_id": "post_invalid", "resource_type": "Schedule" } } ``` ```json Error - No Fields theme={null} { "code": "bad_request", "message": "No fields to update", "details": {} } ``` *** ## Important Notes **Metadata Merging**: When updating `platformData`, the new fields are merged with existing data. To remove a field, you need to set it explicitly to `null` or an empty value. **Tags Replacement**: The `tags` array is replaced entirely, not merged. If you want to add a tag, include all existing tags plus the new one. *** ## Error Handling | Error | Description | Solution | | - | - | - | | `Schedule not found` | Invalid schedule ID | Use a valid schedule ID | | `Cannot update a published post` | Post already published | Cannot modify after publishing | | `Scheduled time must be in the future` | Time has passed | Use a future datetime | | `No fields to update` | Empty request body | Include at least one field to update | | `Schedule does not belong to your team` | Wrong team | Use correct API key | *** ## Next Steps View full schedule information Cancel the scheduled post View all scheduled posts Schedule another video # Analyze Account Source: https://docs.hooked.so/api-reference/social/account-analyze POST /v1/social/account/analyze Analyze a social media account and extract insights ## Overview This endpoint analyzes a social media account by scraping their profile, videos, and generating analytics. The analysis includes engagement metrics, content keywords, popular hashtags, and more. ### Request Body The account username (without the @ symbol). Will be normalized automatically. The platform to analyze: * `tiktok`: Analyze a TikTok account * `youtube`: Analyze a YouTube account ### Response Indicates if the request was successful Status message The analyzed account with full details Internal account ID Account username Platform (tiktok or youtube) Display name Account bio Profile picture URL Verification status Follower/following counts, videos, likes Engagement rate, virality score, average views Most used hashtags Extracted content keywords Analysis timestamp Link to the profile ```bash cURL theme={null} curl -X POST "https://api.hooked.so/v1/social/account/analyze" \ -H "x-api-key: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "username": "fitnessguru", "platform": "tiktok" }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/social/account/analyze', { method: 'POST', headers: { 'x-api-key': 'your_api_key_here', 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'fitnessguru', platform: 'tiktok' }) }); const data = await response.json(); console.log(data.data.account); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.hooked.so/v1/social/account/analyze', headers={ 'x-api-key': 'your_api_key_here', 'Content-Type': 'application/json' }, json={ 'username': 'fitnessguru', 'platform': 'tiktok' } ) account = response.json()['data']['account'] print(f"Analyzed {account['displayName']} with {account['stats']['followers']} followers") ``` ```json Response theme={null} { "success": true, "message": "Account analyzed successfully", "data": { "account": { "id": "clx123abc", "username": "fitnessguru", "platform": "tiktok", "displayName": "Fitness Guru", "bio": "Helping you get fit one video at a time", "avatarUrl": "https://cdn.tiktok.com/avatar.jpg", "isVerified": true, "stats": { "followers": 1500000, "following": 250, "videos": 342, "likes": 45000000 }, "analytics": { "engagementRate": 12.5, "viralityScore": 78.3, "avgViews": 890000 }, "topHashtags": ["fitness", "workout", "gym"], "keywords": ["fitness", "exercise", "motivation"], "lastAnalyzed": "2024-01-15T10:30:00Z", "createdAt": "2024-01-15T10:30:00Z", "profileUrl": "https://www.tiktok.com/@fitnessguru" } } } ``` ## Error Handling If the account has already been analyzed, you'll receive a 400 error. Use the [Refresh Account](/api-reference/social/account-refresh) endpoint to update existing accounts. ```json theme={null} { "success": false, "message": "Account already analyzed. Use refresh endpoint to update." } ``` If the username doesn't exist on the platform: ```json theme={null} { "success": false, "message": "Account not found: username" } ``` Analysis may consume API credits depending on the depth of data scraped. # Delete Account Source: https://docs.hooked.so/api-reference/social/account-delete DELETE /v1/social/account/{accountId} Delete an analyzed account and its associated data ## Overview This endpoint permanently deletes an analyzed account and all its associated data from your team's workspace. ### Path Parameters The internal account ID to delete ### Response Indicates if the request was successful Status message The deleted account ID Confirmation of deletion ```bash cURL theme={null} curl -X DELETE "https://api.hooked.so/v1/social/account/clx123abc" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/social/account/clx123abc', { method: 'DELETE', headers: { 'x-api-key': 'your_api_key_here' } }); const data = await response.json(); if (data.success) { console.log('Account deleted successfully'); } ``` ```python Python theme={null} import requests response = requests.delete( 'https://api.hooked.so/v1/social/account/clx123abc', headers={'x-api-key': 'your_api_key_here'} ) if response.json()['success']: print('Account deleted successfully') ``` ```json Response theme={null} { "success": true, "message": "Account deleted successfully", "data": { "id": "clx123abc", "deleted": true } } ``` This action is permanent and cannot be undone. All associated data including videos and analytics will be deleted. You can only delete accounts that belong to your team. Attempting to delete another team's account will result in a 403 Forbidden error. # Account Details Source: https://docs.hooked.so/api-reference/social/account-details GET /v1/social/account/{accountId} Get detailed information about an analyzed account ## Overview This endpoint returns detailed information about a specific analyzed account, including their top performing videos and complete analytics. ### Path Parameters The internal account ID (obtained from the list or analyze endpoints) ### Response Indicates if the request was successful Complete account details including: * Profile information (username, display name, bio, avatar) * Verification status * Statistics (followers, following, videos, likes) * Analytics (engagement rate, virality score, average views, share rate, save rate) * Top hashtags, music, and keywords * Audience insights * Posting times analysis * Top 20 performing videos * Profile URL ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/social/account/clx123abc" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/social/account/clx123abc', { headers: { 'x-api-key': 'your_api_key_here' } }); const data = await response.json(); console.log(data.data); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.hooked.so/v1/social/account/clx123abc', headers={'x-api-key': 'your_api_key_here'} ) account = response.json()['data'] print(f"Top video: {account['topVideos'][0]['title']}") ``` ```json Response theme={null} { "success": true, "data": { "id": "clx123abc", "username": "fitnessguru", "platform": "tiktok", "displayName": "Fitness Guru", "bio": "Helping you get fit one video at a time", "avatarUrl": "https://cdn.tiktok.com/avatar.jpg", "isVerified": true, "stats": { "followers": 1500000, "following": 250, "videos": 342, "likes": 45000000 }, "analytics": { "engagementRate": 12.5, "viralityScore": 78.3, "avgViews": 890000, "shareRate": 2.3, "saveRate": 4.1 }, "topHashtags": ["fitness", "workout", "gym", "motivation", "health"], "topMusic": ["Workout Beats", "Gym Motivation", "Running Mix"], "keywords": ["fitness", "exercise", "motivation", "health"], "keywordsStatus": "completed", "audience": { "ageGroups": ["18-24", "25-34"], "interests": ["fitness", "health", "lifestyle"] }, "postingTimes": [ { "day": "Monday", "hour": 9, "engagement": 15.2 }, { "day": "Wednesday", "hour": 18, "engagement": 18.5 } ], "topVideos": [ { "id": "video123", "externalId": "7234567890123456789", "title": "5 minute ab workout", "thumbnailUrl": "https://cdn.tiktok.com/thumb.jpg", "views": 5400000, "likes": 890000, "viralScore": 92.5, "publishedAt": "2024-01-15T10:30:00Z" } ], "lastAnalyzed": "2024-01-15T10:30:00Z", "createdAt": "2024-01-01T00:00:00Z", "profileUrl": "https://www.tiktok.com/@fitnessguru" } } ``` Use the `topVideos` data to understand what content performs best for this creator and apply similar strategies to your own content. # List Accounts Source: https://docs.hooked.so/api-reference/social/account-list GET /v1/social/account/list Get all social accounts analyzed by your team ## Overview This endpoint returns all social media accounts that have been analyzed by your team. Use this to view your saved creators and their analytics. ### Query Parameters Filter by platform: * `tiktok`: TikTok accounts only * `youtube`: YouTube accounts only * Omit to get accounts from all platforms Number of accounts to return (1-100) Pagination offset ### Response Indicates if the request was successful Array of analyzed account objects Internal account ID Account username/handle Platform (tiktok or youtube) Display name Account bio/description Profile picture URL Whether the account is verified Follower count, following, videos, and likes Engagement rate, virality score, and average views Most used hashtags Extracted content keywords Last analysis timestamp Link to the profile Total accounts analyzed ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/social/account/list?platform=tiktok" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/social/account/list?platform=tiktok', { headers: { 'x-api-key': 'your_api_key_here' } }); const data = await response.json(); console.log(data.data.accounts); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.hooked.so/v1/social/account/list', params={'platform': 'tiktok'}, headers={'x-api-key': 'your_api_key_here'} ) accounts = response.json()['data']['accounts'] ``` ```json Response theme={null} { "success": true, "data": { "accounts": [ { "id": "clx123abc", "username": "fitnessguru", "platform": "tiktok", "displayName": "Fitness Guru", "bio": "Helping you get fit one video at a time", "avatarUrl": "https://cdn.tiktok.com/avatar.jpg", "isVerified": true, "stats": { "followers": 1500000, "following": 250, "videos": 342, "likes": 45000000 }, "analytics": { "engagementRate": 12.5, "viralityScore": 78.3, "avgViews": 890000 }, "topHashtags": ["fitness", "workout", "gym"], "keywords": ["fitness", "exercise", "motivation"], "keywordsStatus": "completed", "lastAnalyzed": "2024-01-15T10:30:00Z", "createdAt": "2024-01-01T00:00:00Z", "profileUrl": "https://www.tiktok.com/@fitnessguru" } ], "total": 15, "limit": 50, "offset": 0 } } ``` Use the [Analyze Account](/api-reference/social/account-analyze) endpoint to add new accounts to your analysis list. # Refresh Account Source: https://docs.hooked.so/api-reference/social/account-refresh POST /v1/social/account/{accountId}/refresh Refresh account data with latest information from the platform ## Overview This endpoint refreshes an analyzed account with the latest data from the platform. Use this to update follower counts, engagement metrics, and discover new content. ### Path Parameters The internal account ID to refresh ### Response Indicates if the request was successful Status message The refreshed account with updated data ```bash cURL theme={null} curl -X POST "https://api.hooked.so/v1/social/account/clx123abc/refresh" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/social/account/clx123abc/refresh', { method: 'POST', headers: { 'x-api-key': 'your_api_key_here' } }); const data = await response.json(); console.log('Account refreshed:', data.data.account); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.hooked.so/v1/social/account/clx123abc/refresh', headers={'x-api-key': 'your_api_key_here'} ) account = response.json()['data']['account'] print(f"Updated followers: {account['stats']['followers']}") ``` ```json Response theme={null} { "success": true, "message": "Account refreshed successfully", "data": { "account": { "id": "clx123abc", "username": "fitnessguru", "platform": "tiktok", "displayName": "Fitness Guru", "bio": "Helping you get fit one video at a time", "avatarUrl": "https://cdn.tiktok.com/avatar.jpg", "isVerified": true, "stats": { "followers": 1520000, "following": 252, "videos": 348, "likes": 46500000 }, "analytics": { "engagementRate": 12.8, "viralityScore": 79.1, "avgViews": 920000 }, "lastAnalyzed": "2024-01-16T10:30:00Z", "profileUrl": "https://www.tiktok.com/@fitnessguru" } } } ``` Refreshing an account will scrape the latest data from the platform and may consume API credits. Set up a scheduled refresh to track account growth over time. Consider refreshing accounts weekly to monitor performance trends. # List Templates Source: https://docs.hooked.so/api-reference/template/list Get all your video templates ## Overview Retrieve all templates in your account. Templates are pre-configured video setups with `{{variables}}` that you can reuse to generate personalized videos at scale. ## Endpoint ``` GET https://api.hooked.so/v1/template/list ``` ## Query Parameters Filter by video type: `class`, `ugc_ads`, `ugc_video`, `script_to_video`, `hook_demo`, `scenes`, `tiktok_slideshow` Number of results (max: 100) Pagination offset ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/template/list" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/template/list', { headers: { 'x-api-key': 'your_api_key_here' } }); const { templates } = (await response.json()).data; console.log(`Found ${templates.length} templates`); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.hooked.so/v1/template/list', headers={'x-api-key': 'your_api_key_here'} ) templates = response.json()['data']['templates'] print(f'Found {len(templates)} templates') ``` ```json Response theme={null} { "success": true, "data": { "templates": [ { "id": "tmpl_abc123", "name": "Sales Outreach", "type": "class", "script": "Hi {{name}}! I noticed {{company}} is growing fast...", "variables": ["name", "company"], "avatarId": "avatar_sarah_01", "voiceId": "tzX5paJ07p5hyWFcU3uG", "scenes": [ { "script": "Hi {{name}}! Welcome to {{company}}.", "variables": [ { "key": "name", "value": "" }, { "key": "company", "value": "" } ] } ], "createdAt": "2024-01-15T10:00:00Z" } ], "total": 1, "limit": 50, "offset": 0 } } ``` ## Using Templates Once you have a template, create personalized videos by replacing variables: ```javascript theme={null} const template = templates[0]; const response = await fetch('https://api.hooked.so/v1/project/create', { method: 'POST', headers: { 'x-api-key': 'your_api_key_here', 'Content-Type': 'application/json' }, body: JSON.stringify({ type: template.type, templateId: template.id, scenes: template.scenes.map(scene => ({ ...scene, variables: scene.variables.map(v => ({ key: v.key, value: myData[v.key] })) })), webhook: 'https://your-domain.com/webhook' }) }); ``` ## Related Complete personalization example Learn about the template system Generate videos at scale View generated videos # Add Captions Source: https://docs.hooked.so/api-reference/video/add-captions POST /v1/project/create/add-captions Add automatic captions to your videos with customizable styles and positioning **Try it out!** Use the API playground on the right to test the Add Captions endpoint directly. ## Overview Add Captions automatically transcribes your video and adds professional-looking captions with customizable styles. Perfect for: * Social media content (TikTok, Instagram Reels, YouTube Shorts) * Accessibility compliance * Increasing video engagement * Multi-language content * Educational videos Captions are automatically synced with your video's audio and support multiple styling presets. *** ## Endpoint ``` POST /v1/project/create/add-captions ``` *** ## Required Fields Media ID of the video to add captions to (as a string) *** ## Optional Fields Custom name for the project (max 100 characters). If not provided, a name will be auto-generated. Background music ID from `/v1/music/list` (max 30 characters) Caption settings for the video Caption preset style. Available presets: `default`, `beast`, `umi`, `tiktok`, `wrap1`, `wrap2`, `ariel`, `hooked`, `classic`, `active`, `bubble`, `glass`, `comic`, `glow`, `pastel`, `neon`, `retroTV`, `red`, `marker`, `modern`, `blue`, `vivid`. Caption position on the video: `top`, `middle`, or `bottom` Webhook URL for status notifications (max 500 characters, must be HTTPS) Custom metadata object (max 5KB JSON) *** ## Request Examples ### Basic Caption Addition ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/add-captions', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ media: 'media_abc123' }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ### Advanced with Custom Styling ```javascript theme={null} const addCaptionsAdvanced = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/add-captions', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Social Media Captioned Video', media: 'media_xyz789', caption: { preset: 'modern', alignment: 'bottom' }, addStickers: false, musicId: 'upbeat_background_001', webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'social-media-2024', platform: 'tiktok' } }) }); return await response.json(); }; ``` ### With Background Music ```javascript theme={null} const addCaptionsWithMusic = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/add-captions', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Product Demo with Captions', media: 'media_product_demo', addStickers: false, caption: { preset: 'modern', alignment: 'bottom' }, musicId: 'calm_background_music' }) }); return await response.json(); }; ``` *** ## Response ```json Success Response theme={null} { "success": true, "data": { "videoId": "vid_caption_abc123xyz", "projectId": "proj_caption_abc123xyz", "status": "STARTED" } } ``` ```json Error Response - Invalid Media theme={null} { "success": false, "message": "media: Media ID is required" } ``` ```json Error Response - Media Not Found theme={null} { "success": false, "message": "media: Media not found" } ``` ```json Error Response - Music Not Found theme={null} { "success": false, "message": "musicId: Music \"invalid_music\" not found." } ``` *** ## Webhook Notification When your captioned video is ready, we'll POST to your webhook URL (if configured): ```json Webhook Payload theme={null} { "status": "COMPLETED", "data": { "videoId": "vid_caption_abc123xyz", "status": "COMPLETED", "url": "https://cdn.hooked.so/videos/abc123xyz.mp4", "shareUrl": "https://cdn.hooked.so/shared/abc123xyz.mp4", "metadata": { "projectId": "proj_caption_abc123xyz" } }, "message": "Video completed" } ``` *** ## Caption Presets Available caption presets for the `caption.preset` field: | Preset | Description | | - | - | | `default` | Default caption style with bold text and shadow effects | | `beast` | Bold uppercase style with Komika font | | `umi` | Yellow glowing text style | | `tiktok` | Viral & trendy style, perfect for social media | | `wrap1` | Wrapped style with red background highlight | | `wrap2` | Wrapped style with blue background highlight (uppercase) | | `ariel` | Bold uppercase style with purple highlight | | `hooked` | Brand style with purple background | | `classic` | Clean, simple captions with black background (Default) | | `active` | Green background with bold text | | `bubble` | White background bubble style | | `glass` | Glassmorphic transparency effect | | `comic` | Comic Sans font with colorful style | | `glow` | Pink and orange glow effects | | `pastel` | Soft pastel pink background | | `neon` | Green neon glow effect | | `retroTV` | Retro TV style with cyan glow | | `red` | Red glow effect with white text | | `marker` | Yellow marker/highlighter style | | `modern` | Contemporary white background style | | `blue` | Blue background style | | `vivid` | Vibrant pink background with uppercase text | *** ## Best Practices Ensure your video has clear audio for accurate transcription Choose caption styles that match your content and brand Bottom alignment works best for most social media content *** ## Error Handling | Error | Description | Solution | | - | - | - | | `media: Media ID is required` | Missing media ID | Provide a valid media ID string | | `media: Media not found` | Invalid media ID | Ensure the media ID exists and is correct | | `media: Media is not a video` | Media is not a video type | Ensure the media ID points to a video, not an image | | `musicId: Music "X" not found` | Invalid music ID | Use valid music ID from `/v1/music/list` | | `caption.alignment: Invalid enum value` | Invalid alignment | Use `bottom`, `middle`, or `top` | | `webhook: Must be a valid HTTPS URL` | Invalid webhook URL | Ensure webhook URL uses HTTPS | | `Not enough credits` | Insufficient credits | Top up your account credits | *** ## Next Steps View all your created videos Check your video processing status Browse available background music Learn how to handle webhook notifications # Create Video Source: https://docs.hooked.so/api-reference/video/create Create a new AI video project ## Overview Create a new video by sending a `POST` request to `/v1/project/create`. Hooked supports multiple video types — each with its own parameters. Video processing typically takes 2-5 minutes. Use a [webhook](/api-reference/video/webhook) to get notified when your video is ready. ## Endpoint ``` POST https://api.hooked.so/v1/project/create ``` ## Common Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `type` | string | Yes | Video type (see below) | | `name` | string | No | Video title | | `script` | string | Varies | Narration text | | `avatarId` | string | Varies | Avatar to use | | `voiceId` | string | Varies | Voice for narration | | `aspectRatio` | string | No | `ratio_9_16` (default) or `ratio_16_9` | | `captions` | boolean | No | Enable auto captions | | `music` | string | No | Background music ID | | `webhook` | string | No | Completion notification URL | ## Video Types Each type has a dedicated endpoint guide with full parameters: UGC-style talking avatar videos Convert scripts to videos with AI visuals User-generated content style ads, including website and app demos Hook intro with product demo Multi-scene template videos Generate from a text prompt TikTok-style slideshow videos Add captions to existing video Extend an existing video Remove video background Website and app demo videos (an avatar presenting while your site or app is shown) are the `app_demo` format of UGC Studio. Use the [UGC Ads](/api-reference/video/ugc-ads) endpoint for them. ## Quick Example ```bash theme={null} curl -X POST "https://api.hooked.so/v1/project/create" \ -H "x-api-key: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "type": "class", "name": "My Tutorial", "script": "Hello! Welcome to this tutorial.", "avatarId": "avatar_sarah_01", "voiceId": "tzX5paJ07p5hyWFcU3uG", "webhook": "https://your-domain.com/webhook" }' ``` ## Response ```json theme={null} { "success": true, "message": "Project created successfully", "data": { "projectId": "proj_abc123", "status": "processing", "estimatedTime": "3-5 minutes" } } ``` ## Related Check video status View all videos Receive completion notifications End-to-end tutorial # Get Video Details Source: https://docs.hooked.so/api-reference/video/details GET /v1/video/{videoId} Get detailed information about a specific video ## Overview This endpoint retrieves comprehensive details about a specific video, including status, URL, duration, and metadata. Use this to monitor individual video progress or retrieve video information for display. This endpoint requires authentication and only returns videos that belong to your team. ## Path Parameters The unique identifier of the video to retrieve ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/video/vid_abc123" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const videoId = 'vid_abc123'; const response = await fetch(`https://api.hooked.so/v1/video/${videoId}`, { headers: { 'x-api-key': 'your_api_key_here' } }); const data = await response.json(); console.log(`Video status: ${data.data.status}`); console.log(`Duration: ${data.data.durationInSeconds}s`); ``` ```python Python theme={null} import requests video_id = 'vid_abc123' response = requests.get( f'https://api.hooked.so/v1/video/{video_id}', headers={'x-api-key': 'your_api_key_here'} ) data = response.json()['data'] print(f"Video status: {data['status']}") print(f"Duration: {data['durationInSeconds']}s") ``` ```json Response theme={null} { "success": true, "message": "Video details fetched successfully", "data": { "id": "vid_abc123", "name": "Product Tutorial", "type": "class", "status": "COMPLETED", "url": "https://s3.amazonaws.com/hooked-videos/vid_abc123.mp4", "thumbnail": "https://cdn.hooked.so/thumbnails/abc123.jpg", "progress": 100, "message": "Video completed successfully", "durationInFrames": 7500, "durationInSeconds": 300, "avatarId": "avatar_sarah_01", "voiceId": "tzX5paJ07p5hyWFcU3uG", "musicId": "1", "videoSettings": { "aspectRatio": "ratio_9_16", "caption": { "disabled": false, "preset": "classic" } }, "script": "Hello! Welcome to this tutorial...", "projectId": "proj_abc123", "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-01-15T10:05:00Z", "usedCredits": 5, "webhook": null } } ``` ## Response Fields Indicates if the request was successful Status message Unique video identifier Video name/title Video type (class, ugc\_ads, ugc\_video, script\_to\_video, prompt\_to\_video, hook\_demo, scenes, add\_captions, extend\_video, remove\_background, tiktok\_slideshow) Current video status: `STARTED`, `COMPLETED`, or `FAILED` Signed S3 URL to download the video (only available when status is COMPLETED) URL to the video thumbnail image Processing progress percentage (0-100) Current status message Video duration in frames (at 25 FPS) Video duration in seconds ID of the avatar used in the video ID of the voice used for narration ID of the background music track Video configuration including aspect ratio and caption settings The narration script used in the video Associated project ID (if created from a project) ISO 8601 timestamp of creation ISO 8601 timestamp of last update Number of credits consumed by this video Webhook URL for status notifications (if configured) ## Error Responses ```json Video Not Found theme={null} { "success": false, "message": "Video not found" } ``` ```json Unauthorized Access theme={null} { "success": false, "message": "Video does not belong to the user" } ``` ```json Unauthorized theme={null} { "success": false, "message": "Not authenticated" } ``` ## Status Codes | Status | Code | Description | | - | - | - | | Success | 200 | Video found and details returned | | Not Found | 404 | Video doesn't exist | | Forbidden | 403 | Video belongs to another team | | Unauthorized | 403 | Invalid or missing API key | | Server Error | 500 | Internal server error | ## Video Status States Video is currently being processed. The `progress` field shows the current percentage. Video processing finished successfully. The `url` field contains a signed URL to download the video. URL is valid for 24 hours. Video processing failed. Check the `message` field for details about what went wrong. ## Downloading Videos Once a video has status `COMPLETED`, the `url` field provides a signed S3 URL: ```javascript Download Example theme={null} async function downloadVideo(videoId) { const response = await fetch(`https://api.hooked.so/v1/video/${videoId}`, { headers: { 'x-api-key': API_KEY } }); const video = await response.json(); if (video.data.status === 'COMPLETED') { // URL is pre-signed and ready to download window.location.href = video.data.url; } } ``` ## Polling for Completion To monitor video progress, you can poll this endpoint: ```javascript Polling Example theme={null} async function waitForVideoCompletion(videoId, maxWait = 300000) { const startTime = Date.now(); const pollInterval = 5000; // Check every 5 seconds while (Date.now() - startTime < maxWait) { const response = await fetch(`https://api.hooked.so/v1/video/${videoId}`, { headers: { 'x-api-key': API_KEY } }); const data = await response.json(); const { status, progress } = data.data; console.log(`Progress: ${progress}%`); if (status === 'COMPLETED') { return data.data; } else if (status === 'FAILED') { throw new Error(`Video failed: ${data.data.message}`); } await new Promise(resolve => setTimeout(resolve, pollInterval)); } throw new Error('Video rendering timeout'); } ``` **Better approach**: Use webhooks instead of polling. See [Video Webhook](/api-reference/video/webhook) for details. ## Use Cases Monitor video rendering progress Get download URLs for completed videos Show video status in your dashboard Detect and handle rendering failures ## Related Endpoints View all videos Create a new video Handle video completion notifications # Extend Video Source: https://docs.hooked.so/api-reference/video/extend-video POST /v1/project/create/extend-video Extend videos using AI to continue and extend your content seamlessly **Try it out!** Use the API playground on the right to test the Extend Video endpoint directly. ## Overview Extend Video uses AI (Veo 3.1) to intelligently continue and extend your videos. Perfect for: * Creating longer content from short clips * Generating smooth video continuations * Extending storytelling sequences * Producing extended versions of marketing videos * Automating video length optimization Extensions are generated in segments of 4-8 seconds each. The AI analyzes the last frame of your video and generates a seamless continuation based on your prompt. *** ## Endpoint ``` POST /v1/project/create/extend-video ``` *** ## Required Fields Media ID of the video to extend (as a string) Describe how the AI should continue the video (1-2000 characters) Example: "The camera continues to zoom out, revealing a cityscape at sunset with birds flying across the sky" *** ## Optional Fields Custom name for the project (max 100 characters) Total duration to extend in seconds (4-60 seconds, must be multiple of 4) * Minimum: 4 seconds * Maximum: 60 seconds * Default: 4 seconds Whether to generate audio for the extended portion Webhook URL for status notifications (max 500 characters, must be HTTPS) Custom metadata object (max 5KB JSON) *** ## Request Examples ### Basic Video Extension ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/extend-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ media: 'media_video123', prompt: 'The camera slowly zooms out revealing a beautiful sunset over the ocean', targetDuration: 8, generateAudio: true }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ### Extended Duration with Webhook ```javascript theme={null} const extendVideoLonger = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/extend-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ media: 'media_marketing_clip', prompt: 'The product rotates smoothly as the background transitions from blue to purple with subtle particle effects', targetDuration: 16, // 16 seconds extension generateAudio: true, webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'product-launch-2024', videoType: 'marketing' } }) }); return await response.json(); }; ``` ### Multiple Extensions ```javascript theme={null} const extendVideoMultiple = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/extend-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Extended Story Video', media: 'media_story_video', prompt: 'The scene continues with smooth camera movement, transitioning through different environments while maintaining visual consistency', targetDuration: 32, // Will be processed in 4 segments of 8 seconds each generateAudio: true }) }); return await response.json(); }; ``` *** ## Response ```json Success Response theme={null} { "success": true, "data": { "videoId": "vid_extend_abc123xyz", "projectId": "proj_extend_abc123xyz", "status": "STARTED" } } ``` ```json Error Response - Invalid Media theme={null} { "success": false, "message": "media: A video is required" } ``` ```json Error Response - Invalid Duration theme={null} { "success": false, "message": "extendVideoSettings.targetDuration: Target duration must be at least 4 seconds" } ``` ```json Error Response - Invalid Prompt theme={null} { "success": false, "message": "extendVideoSettings.prompt: Prompt is required" } ``` *** ## Webhook Notification When your extended video is ready, we'll POST to your webhook URL (if configured): ```json Webhook Payload theme={null} { "status": "COMPLETED", "data": { "videoId": "vid_extend_abc123xyz", "status": "COMPLETED", "url": "https://cdn.hooked.so/videos/abc123xyz.mp4", "shareUrl": "https://cdn.hooked.so/shared/abc123xyz.mp4", "metadata": { "projectId": "proj_extend_abc123xyz", "originalDuration": "5s", "extendedDuration": "13s" } }, "message": "Video completed" } ``` *** ## How It Works Provide your source video that you want to extend Veo 3.1 analyzes the last frame and video motion patterns AI generates extension segments (8 seconds each) based on your prompt All segments are seamlessly concatenated with the source video Receive your extended video with smooth transitions *** ## Best Practices Describe the continuation clearly and specifically for best results Reference the visual style of your source video in the prompt Start with 8-16 seconds to ensure quality, then scale up Enable generateAudio for better continuity with the source *** ## Prompt Writing Tips ### Good Prompts ✅ "The camera continues to pan right, revealing a mountain range at golden hour with soft clouds drifting across the sky" ✅ "The dancer spins gracefully as the lighting shifts from warm orange to cool blue" ✅ "The product rotates smoothly on the pedestal while particles float upward in the background" ### Avoid ❌ "Continue the video" (too vague) ❌ "Make it longer" (no direction) ❌ "Add random stuff" (unclear intent) *** ## Error Handling | Error | Description | Solution | | - | - | - | | `media: A video is required` | Missing or invalid media ID | Provide a valid video media ID | | `Media not found` | Media ID doesn't exist | Check that the media ID is correct and exists | | `Media is not a video` | Media is not a video type | Ensure the media ID points to a video, not an image | | `prompt: Prompt is required` | Missing prompt | Provide a descriptive prompt for the extension | | `targetDuration: Target duration must be at least 4 seconds` | Duration too short | Use minimum 4 seconds | | `targetDuration: Target duration cannot exceed 60 seconds` | Duration too long | Use maximum 60 seconds | | `aspectRatio: Invalid enum value` | Invalid aspect ratio | Use `ratio_9_16`, `ratio_16_9`, or `ratio_1_1` | | `Not enough credits` | Insufficient credits | Top up your account credits | *** ## Processing Time Extension processing time varies based on: * **Target Duration**: Longer extensions take more time (each 8-second segment \~2-3 minutes) * **Audio Generation**: Enabling audio adds processing time * **Queue Load**: Peak times may have longer waits **Typical processing times:** * 8 seconds: 2-3 minutes * 16 seconds: 4-6 minutes * 32 seconds: 8-12 minutes * 60 seconds: 15-20 minutes *** ## Next Steps View all your created videos Check your video processing status Learn how to handle webhook notifications See more extend video examples # Hook + Demo Source: https://docs.hooked.so/api-reference/video/hook-demo POST /v1/project/create/hook-demo Create engaging Hook + Demo videos with text overlays and background media **Try it out!** Use the API playground on the right to test the Hook + Demo endpoint directly. The schema is pre-configured for `hook_demo` type. ## Overview Hook + Demo videos are perfect for creating attention-grabbing content with text hooks displayed over demo media (images or videos). This format is ideal for: * Product demonstrations with compelling hooks * Social media content with text overlays * Marketing videos with call-to-action text * Quick promotional clips Hook + Demo videos are generated in real-time, making them one of the fastest video types to create. *** ## Endpoint ``` POST /v1/project/create/hook-demo ``` The endpoint is the same for all video types. Set `type: "hook_demo"` to create a Hook + Demo video. *** ## Required Fields Avatar template ID from `/v1/avatar/list`. This is the avatar that will appear in your Hook + Demo video. The hook text to display on the video (1-5,000 characters). This is the main text that will appear as an overlay on your media. *** ## Optional Fields Video name (max 100 characters) Background media IDs (max 50). Array of media ID strings. These are the images or videos that will play behind your text hook. Music template ID from `/v1/music/list` or custom music ID. Text display settings for the hook overlay Text preset style. Available presets include: `modern`, `tiktok`, `social`, `impact`, `slicedText`, `minimalMonochrome`, `highlight`, `gradient`, `neonFuture`, `avantGarde`, `glassmorphic`, `stageEffect`, `neonEffect`, `glowEffect`, `wrapEffect` Text position on the video: `top`, `middle`, or `bottom` Custom text color in hex format (e.g., `#ffffff`). Max 100 characters. Custom background color for the text box in hex format (e.g., `#000000`). Max 100 characters. Set to `true` to hide the text overlay (useful when you only want the media) Video aspect ratio: * `ratio_9_16`: Vertical (TikTok, Reels, Shorts) - **Recommended** * `ratio_16_9`: Horizontal (YouTube) * `ratio_1_1`: Square (Instagram) HTTPS URL to receive completion notification (max 500 characters). **Highly recommended** for production use. Custom metadata object (max 5KB). Store any additional data you need to associate with this video. ```json theme={null} { "campaignId": "summer2024", "productSku": "SKU-12345", "customField": "any value" } ``` *** ## Request Examples ### Basic Hook + Demo ```json theme={null} { "avatarId": "6", "name": "Product Launch Hook", "text": "This changed everything..." } ``` ### With Background Media ```json theme={null} { "avatarId": 6, "name": "Product Demo Video", "musicId": "music_id", "text": "Wait for it... This product will blow your mind!", "textSettings": { "preset": "impact", "alignment": "bottom", "disabled": false }, "media": ["media_001", "media_002"], "aspectRatio": "ratio_9_16", "webhook": "https://yoursite.com/webhooks/video-complete" } ``` ### With Custom Colors and Metadata ```json theme={null} { "avatarId": "6", "name": "Brand Campaign Hook", "text": "3 secrets they don't want you to know...", "textSettings": { "preset": "gradient", "alignment": "top", "textColor": "#00ff88", "backgroundColor": "transparent", "disabled": false }, "media": ["media_bg"], "aspectRatio": "ratio_9_16", "webhook": "https://api.yoursite.com/hooks/hooked", "metadata": { "campaignId": "Q1-2024-hooks", "abTestVariant": "A", "internalRef": "MKT-1234" } } ``` *** ## Response ```json Success Response theme={null} { "success": true, "data": { "videoId": "vid_ugc_abc123xyz", "projectId": "proj_hook_abc123xyz", "status": "COMPLETED" }, "message": "Hook Demo successfully created" } ``` ```json Error Response - Validation Error theme={null} { "success": false, "message": "text: Text is required" } ``` ```json Error Response - Invalid Webhook theme={null} { "success": false, "message": "webhook: Must be a valid HTTPS URL" } ``` *** ## Webhook Notification When your video is ready, we'll POST to your webhook URL: ```json Webhook Payload theme={null} { "status": "COMPLETED", "data": { "videoId": "video_xyz789", "status": "COMPLETED", "url": "https://cdn.hooked.so/videos/xyz789.mp4", "shareUrl": "https://cdn.hooked.so/shared/xyz789.mp4", "metadata": { "projectId": "proj_hook_abc123xyz", "campaignId": "Q1-2024-hooks", "abTestVariant": "A" }, }, "message": "Video completed", } ``` Your webhook endpoint must return a `200` status code. We'll retry up to 3 times if the request fails. *** ## Text Presets Available text presets for the `textSettings.preset` field: | Preset | Description | | - | - | | `modern` | Clean, contemporary style with subtle shadows (Default) | | `tiktok` | Viral & trendy style with bold outlines, perfect for social media | | `social` | White background with black text, great for readability | | `impact` | Bold & commanding style with uppercase text and strong shadows | | `slicedText` | Edgy & modern with colorful shadow effects (Pro) | | `minimalMonochrome` | Ultra minimal style with lowercase text and subtle borders | | `highlight` | Modern & distinctive with colored background boxes | | `gradient` | Dynamic & modern with gradient text colors (Pro) | | `neonFuture` | Electric & bold with cyan neon glow effect | | `avantGarde` | Fashion forward with split gradient text (Pro) | | `glassmorphic` | Modern transparency effect with glass-like styling | | `stageEffect` | Gradient stage effect with colorful text | | `neonEffect` | Electric glow with magenta neon shadows | | `glowEffect` | Soft white glow effect for elegant styling | | `wrapEffect` | Wrapped style with gradient text and blur backdrop | You can override the preset colors using `textColor` and `backgroundColor` while keeping the preset's font styling and animations. *** ## Best Practices Effective hooks are 5-15 words. Get attention quickly. Upload media at least 1080p for best results. MP4 for videos, JPG/PNG for images. Ensure your media matches your target aspect ratio to avoid cropping. Always use webhooks in production instead of polling for video status. *** ## Common Use Cases Create attention-grabbing hooks for TikTok, Instagram Reels, or YouTube Shorts. ```json theme={null} { "avatarId": "6", "name": "Viral Hook", "text": "POV: You just discovered this hack", "textSettings": { "preset": "tiktok", "alignment": "middle", "textColor": "#00ff88", "backgroundColor": "transparent", "disabled": false }, "aspectRatio": "ratio_9_16" } ``` Showcase products with compelling text overlays. ```json theme={null} { "avatarId": "6", "name": "Product Feature", "text": "Watch what happens when you press this button...", "textSettings": { "preset": "modern", "alignment": "bottom", "textColor": "#00ff88", "backgroundColor": "transparent", "disabled": false }, "media": ["demo_vid"] } ``` Highlight customer quotes or testimonials. ```json theme={null} { "avatarId": "6", "name": "Customer Quote", "text": "\"This saved me 10 hours every week\" - Sarah M.", "textSettings": { "preset": "highlight", "alignment": "middle", "textColor": "#ffffff", "backgroundColor": "#1a1a1a" } } ``` *** ## Error Handling | Error | Description | Solution | | - | - | - | | `text: Text is required` | Missing hook text | Add the `text` field with your hook content | | `webhook: Must be a valid HTTPS URL` | Invalid webhook URL | Ensure webhook URL starts with `https://` | | `media: Cannot have more than 50 media items` | Too many media items | Reduce media array to 50 items or fewer | | `textSettings.alignment: Invalid alignment value` | Invalid alignment | Use `top`, `middle`, or `bottom` | *** ## Next Steps View all your created videos Check video status and download URL Learn how to handle webhook notifications Tips for creating effective videos # List Videos Source: https://docs.hooked.so/api-reference/video/list GET /v1/video/list Get all your created videos (excludes templates) ## Overview This endpoint returns all videos (projects) you've created, excluding templates. Use this to retrieve your video history, check statuses, and manage your content. This endpoint only returns regular videos. For templates, use [List Templates](/api-reference/template/list). ### Query Parameters Filter by video type: * `class`: Educational videos * `ugc_ads`: UGC advertising * `ugc_video`: Talking avatar videos * `script_to_video`: Script to Videos * `prompt_to_video`: Prompt to Videos * `hook_demo`: Hook + Demo * `scenes`: Scenes videos * `add_captions`: Add Captions videos * `extend_video`: Extend Video * `remove_background`: Remove background videos * `tiktok_slideshow`: TikTok slideshows Filter by status: * `draft`: Draft videos * `processing`: Currently processing * `completed`: Completed videos * `failed`: Failed videos Number of results (max: 100) Pagination offset ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/video/list?status=completed&limit=10" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/video/list?status=completed', { headers: { 'x-api-key': 'your_api_key_here' } }); const { videos } = await response.json().data; console.log(`Found ${videos.length} completed videos`); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.hooked.so/v1/video/list', params={'status': 'completed', 'limit': 10}, headers={'x-api-key': 'your_api_key_here'} ) videos = response.json()['data']['videos'] print(f'Found {len(videos)} videos') ``` ```json Response theme={null} { "success": true, "data": { "videos": [ { "id": "vid_abc123", "name": "Product Tutorial", "status": "COMPLETED", "url": "https://signed-s3-url.example.com/videos/vid_abc123.mp4", "durationInFrames": 6250, "projectId": "proj_class_001", "thumbnail": "https://cdn.hooked.so/thumbnails/vid_abc123.jpg", "size": 15728640, "progress": 100, "message": "Video ready", "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-01-15T10:05:00Z" }, { "id": "vid_def456", "name": "Social Media Clip", "status": "COMPLETED", "url": "https://signed-s3-url.example.com/videos/vid_def456.mp4", "durationInFrames": 3750, "projectId": "proj_ugc_ads_002", "thumbnail": "https://cdn.hooked.so/thumbnails/vid_def456.jpg", "size": 9437184, "progress": 100, "message": "Video ready", "createdAt": "2024-01-15T11:00:00Z", "updatedAt": "2024-01-15T11:03:00Z" } ], "total": 2, "limit": 50, "offset": 0 } } ``` ## Video Status Each video has one of these statuses: Video is created but not yet processing Video is currently being generated (usually 2-5 minutes) Video is ready! Download URL is available Video generation failed - check the `message` field for details ## Pagination For large video lists, use pagination: ```javascript Pagination Example theme={null} async function getAllVideos() { const allVideos = []; let offset = 0; const limit = 100; while (true) { const response = await fetch( `https://api.hooked.so/v1/video/list?limit=${limit}&offset=${offset}`, { headers: { 'x-api-key': API_KEY } } ); const { videos, total } = await response.json().data; allVideos.push(...videos); if (allVideos.length >= total) break; offset += limit; } return allVideos; } ``` ## Use Cases Build a video management dashboard Track video processing status Analyze video creation trends Manage multiple videos at once ## Filtering Examples ```bash theme={null} # Get all completed videos curl "https://api.hooked.so/v1/video/list?status=completed" \ -H "x-api-key: your_api_key_here" ``` ```bash theme={null} # Get all educational videos curl "https://api.hooked.so/v1/video/list?type=class" \ -H "x-api-key: your_api_key_here" ``` ```bash theme={null} # Get completed UGC ads curl "https://api.hooked.so/v1/video/list?type=ugc_ads&status=completed&limit=20" \ -H "x-api-key: your_api_key_here" ``` ## Related Endpoints Create a new video View your templates # Podcast Source: https://docs.hooked.so/api-reference/video/podcast-interview POST /v1/project/create/podcast-interview Create a podcast interview with two AI avatars **Try it out!** Use the API playground on the right to test the Podcast endpoint directly. ## Overview Two AI avatars talking, in one of two shapes: * **Podcast**: each person filmed on their own and cut back and forth, like a real podcast. * **Dualcast**: both people in the same shot; in each clip one talks while the other listens and reacts. You write the dialogue; every turn becomes a clip of the person who says it, with their own voice. Ideal for: * Educational and opinion clips * Product mentions in a natural conversation * Q\&A and "one question" formats Voice, lip movement and gestures are generated natively by the video model. Each person keeps the same voice in every clip, and the whole conversation shares one accent. *** ## Endpoint ``` POST /v1/project/create/podcast-interview ``` *** ## Required Fields `podcast` (needs `avatarId` and `guest`) or `dualcast` (needs `dualcastImageKey`, `dualcastAvatarId`, or `dualcastAvatars` to make the image). The dialogue, each turn wrapped in who says it: `[A] … [/A]` (the host, or the person on the left in dualcast) and `[B] … [/B]` (the guest, or the person on the right). Lines starting with `A:` / `B:` are read the same. Both people must speak. A long turn is split between sentences into clips of up to 10 seconds, up to 24 clips per video. Inside a turn: * an action in brackets, in your words: `[nods]` at the start happens before speaking, in the middle while saying the words around it, at the end after the line * `[camera] … [/camera]` marks the words said to the camera, to the audience, instead of to the other one (the closing CTA, for instance) * `[product] … [/product]` marks the words while the product photo (`productImageKey`) is on screen, and whoever is talking points up to it as it appears (unless you wrote an action right there) Podcast mode: the host (A): an avatar ID from `/v1/avatar/list`. An avatar on a podcast set works best: a generated guest sits in the same room. Podcast mode: the guest (B). In dualcast mode only `guest.voice` is read, as the voice of the person on the right. * `generated`: a new person in the same podcast room, seen from the other seat * `avatar`: another avatar, seated in the same podcast room from the other seat Required when `source` is `generated` (max 300 characters): who the guest is, e.g. `a man in his 30s, casual, average looking`. Required when `source` is `avatar`: an avatar ID from `/v1/avatar/list`. The guest's voice: `young-woman-25-30`, `woman-mid-20s`, `woman-mid-30s`, `older-woman-50`, `young-man-25-30`, `man-mid-20s`, `man-mid-30s` or `older-man-50`. Unset: the one that fits the guest. Dualcast mode, instead of `dualcastImageKey`: an avatar ID from `/v1/avatar/list` whose image already shows both people (made in the avatar editor with "Both people in one shot"). A is the person on the left, B the one on the right. It must show exactly two people. Dualcast mode, instead of `dualcastImageKey`: two avatar IDs (`left`, `right`) from `/v1/avatar/list`. They are put side by side on a podcast set, each with their microphone (one more image): `left` is A, `right` is B. With `dualcastAvatars`, optional: the set they sit on, in your words (max 300 characters), e.g. `a modern studio with a wooden slat wall`. Dualcast mode: the image with both people, as a storage key from your media library or an https URL. A is the person on the left, B the one on the right. It must show exactly two people; the automatic voices follow who it shows. *** ## Optional Fields Podcast mode: move the host to this set first, in your words (max 300 characters), e.g. `a cozy podcast studio with a boom microphone`. Only the background changes, and the guest sits in the same room. Useful when the avatar is not on a podcast set. A product photo (storage key from your media library or an https URL), shown at the top of the video on the words marked `[product] … [/product]`. A's voice, from the same list as `guest.voice`. Unset: the one that fits the host (podcast), or the person on the left of the image (dualcast). One accent for both people, from the English and Spanish accents (e.g. `us-neutral-general-american`, `spanish-mexico`). Unset: the language's neutral accent. The conversation's language (e.g. `en`, `es`). Unset: detected from the script. Video name (max 100 characters) Music ID from `/v1/music/list` for background music. Caption settings, same as the other video endpoints (`preset`, `alignment`, `disabled`). HTTPS URL to receive completion notification (max 500 characters). Custom metadata object (max 5KB). *** ## Request Example ```json theme={null} { "script": "[A] 8 out of 10 people say they struggle to make it to the end of the month. [/A]\n[B] Quick question. Do you consider yourself middle class? [/B]\n[A] [pauses, thinking] Hmm, I'd say yes. [/A]\n[B] [camera] If you want to learn to save, comment SAVE below. [/camera] [/B]", "avatarId": "avatar_podcast_host_01", "guest": { "source": "generated", "description": "a man in his 30s, casual, average looking" }, "caption": { "preset": "tiktok", "alignment": "bottom", "disabled": false }, "webhook": "https://yoursite.com/webhook" } ``` ### Dualcast Example ```json theme={null} { "mode": "dualcast", "script": "[A] I look at myself in the mirror in these leggings and go: damn, I look good. [she bursts out laughing] [/A]\n[B] They lift you and cinch your waist, it's next level. [/B]\n[A] I'm leaving a photo up here [product] because otherwise you won't get it. [/product] [/A]\n[B] The link is down below, go take a look. [they both wave goodbye] [/B]", "dualcastImageKey": "https://yoursite.com/two-hosts.jpg", "hostVoice": "woman-mid-20s", "guest": { "source": "generated", "voice": "young-woman-25-30" } } ``` # Prompt to Video Source: https://docs.hooked.so/api-reference/video/prompt-to-video POST /v1/project/create/prompt-to-video Create videos from prompts with AI-generated scripts and media **Try it out!** Use the API playground on the right to test the Prompt to Video endpoint directly. ## Overview Prompt to Video transforms your text prompts into engaging videos with AI-generated scripts, voice narration and auto-generated or custom media. This format is ideal for: * Quick content creation without writing scripts * Educational content and tutorials * Social media content creation * Marketing videos * Storytelling and narrative content Prompt to Video automatically generates a script from your prompt, then creates visuals based on that script. You can also provide custom media for full creative control. *** ## Endpoint ``` POST /v1/project/create/prompt-to-video ``` *** ## Required Fields Describe the video you want to create (1-4000 characters). The AI will generate a professional script from this prompt. Example: "Create an engaging video about the top 5 productivity tips for remote workers" Voice ID from `/v1/voice/list`. The voice used for narrating the generated script. Type of media to generate: * `ai-images`: AI-generated images based on script * `ai-videos`: AI-generated video clips * `media`: Use stock media * `gameplay`: Use gameplay footage *** ## Optional Fields Video name (max 100 characters). If not provided, a name will be auto-generated from your prompt. Target duration for the generated video in seconds * Minimum: 10 seconds * Maximum: 120 seconds * Default: 60 seconds Settings for auto-generated media (used when `media` is not provided) Visual style preset for AI-generated images/videos. Available presets: `realistic`, `anime`, `cinematic`, `fantasy`, `cyberpunk-anime`, `pixar`, `comic-book`, `real-anime`, `ghibli-studio`, `sketch-black-and-white`, `art-style`, `retro-anime`, `80s-fantasy-movie`, `cartoon`, `creative`, `gta-v`, `sketch-color`, `japanese-ink`, `space-marines-40k`, `haunted-linework`, `ink-style`, `neon-futuristic`, `minecraft`, `pixel-art`, `collage`, `lego`, `technical-blueprints` Generation quality: `base`, `pro`, or `ultra` Array of media IDs (max 50). The media must already be uploaded to your account. If not provided, media will be auto-generated based on `presetSettings`. Settings for gameplay footage (when `presetSettings.mediaType` is `gameplay`) Game identifier for gameplay footage. Available games: `minecraft`, `subway-s`, `temple-run`, `gta` Specific gameplay video to use. Available videos: * Minecraft: `minecraft-1` through `minecraft-8` * Subway S: `subway-s-1` through `subway-s-11` * Temple Run: `temple-run-1` through `temple-run-8` * GTA: `gta-1` through `gta-12` Caption settings for the video Caption preset style. Available presets: `default`, `beast`, `umi`, `tiktok`, `wrap1`, `wrap2`, `ariel`, `hooked`, `classic`, `active`, `bubble`, `glass`, `comic`, `glow`, `pastel`, `neon`, `retroTV`, `red`, `marker`, `modern`, `blue`, `vivid`. Caption position on the video: `top`, `middle`, or `bottom` Set to `true` to hide captions on the video Video aspect ratio: * `ratio_9_16`: Vertical (TikTok, Reels, Shorts) * `ratio_16_9`: Horizontal (YouTube) * `ratio_1_1`: Square (Instagram) Enable automatic sticker generation for the video. Adds engaging visual elements automatically. Music ID from `/v1/music/list` for background music. Voice audio settings Voice speed multiplier (0.7 to 1.2) Voice stability (0 to 1). Higher values produce more consistent speech. Voice similarity boost (0 to 1). Higher values make the voice more similar to the original. Voice style exaggeration (0 to 1). Enable speaker boost for clearer audio. HTTPS URL to receive completion notification (max 500 characters). **Highly recommended** for production use. Custom metadata object (max 5KB). Store any additional data you need to associate with this video. ```json theme={null} { "campaignId": "summer2024", "contentType": "educational" } ``` *** ## Request Examples ## With AI-Generated Images ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/prompt-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Create an engaging video about the top 5 productivity tips for remote workers. Include practical advice and make it motivational.', voiceId: 'confident_voice_id', targetDuration: 60, mediaType: "ai-images", presetSettings: { preset: "realistic", quality: "pro" }, musicId: "2", aspectRatio: 'ratio_9_16', caption: { preset: 'beast', alignment: 'bottom', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'productivity' } }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ## With AI-Generated Videos ```javascript theme={null} const createAIVideoFromPrompt = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/prompt-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'AI Technology Explainer', prompt: 'Explain how artificial intelligence is transforming the way we work and live. Cover smart assistants, self-driving cars, and future applications.', voiceId: 'confident_voice_id', targetDuration: 60, mediaType: 'ai-videos', presetSettings: { preset: 'anime', quality: 'base' }, musicId: 'music_ambient_01', aspectRatio: 'ratio_9_16', caption: { preset: 'modern', alignment: 'bottom', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'artificial-intelligence' } }) }); return await response.json(); }; ``` ## With Custom Media ```javascript theme={null} const createCustomMediaVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/prompt-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Product Showcase', prompt: 'Create a compelling product introduction video. Highlight the revolutionary features that make life easier and more productive.', voiceId: 'enthusiastic_voice_id', targetDuration: 45, mediaType: 'media' media: ['intro_shot', 'demo_shot', 'feature_image'], musicId: 'music_upbeat_01', aspectRatio: 'ratio_9_16', caption: { preset: 'wrap1', alignment: 'bottom', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'marketing', topic: 'product-launch' } }) }); return await response.json(); }; ``` ## With Gameplay ```javascript theme={null} const createGamingVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/prompt-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Gaming Tips Video', prompt: 'Create a video with three pro tips to level up gaming skills and dominate opponents. Make it energetic and engaging for gamers.', voiceId: 'energetic_voice_id', targetDuration: 45, mediaType: 'gameplay' gameplaySettings: { selectedGame: 'minecraft', selectedVideo: 'minecraft-1' }, musicId: 'music_upbeat_01', aspectRatio: 'ratio_9_16', caption: { preset: 'beast', alignment: 'top', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'gaming', topic: 'tips' } }) }); return await response.json(); }; ``` ## Short Video (30 seconds) ```javascript theme={null} const createShortVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/prompt-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Share one mind-blowing fact about the ocean that most people dont know. Make it hook viewers in the first 3 seconds.', voiceId: 'confident_voice_id', targetDuration: 30, mediaType: 'ai-images', presetSettings: { preset: 'realistic', quality: 'pro' }, aspectRatio: 'ratio_9_16', caption: { preset: 'tiktok', alignment: 'bottom', disabled: false }, webhook: 'https://yoursite.com/webhook' }) }); return await response.json(); }; ``` ## Long Video (120 seconds) ```javascript theme={null} const createLongVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/prompt-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Create a comprehensive guide on how to start a morning routine that boosts productivity. Include wake-up habits, exercise tips, and mindset techniques.', voiceId: 'confident_voice_id', targetDuration: 120, mediaType: 'ai-images', presetSettings: { preset: 'cinematic', quality: 'ultra' }, musicId: 'music_motivational_01', aspectRatio: 'ratio_16_9', caption: { preset: 'modern', alignment: 'bottom', disabled: false }, addStickers: true, webhook: 'https://yoursite.com/webhook', metadata: { platform: 'youtube', category: 'self-improvement' } }) }); return await response.json(); }; ``` *** ## Response ```json Success Response theme={null} { "success": true, "data": { "videoId": "vid_p2v_abc123xyz", "projectId": "proj_p2v_abc123xyz", "status": "STARTED" }, "message": "Prompt to Video successfully created" } ``` ```json Error Response - Validation Error theme={null} { "success": false, "message": "prompt: Prompt is required" } ``` ```json Error Response - Voice Not Found theme={null} { "success": false, "message": "voiceId: Voice not found" } ``` ```json Error Response - Invalid Duration theme={null} { "success": false, "message": "targetDuration: Target duration must be at least 30 seconds" } ``` *** ## Webhook Notification When your video is ready, we'll POST to your webhook URL: ```json Webhook Payload theme={null} { "status": "COMPLETED", "data": { "videoId": "vid_p2v_abc123xyz", "status": "COMPLETED", "url": "https://cdn.hooked.so/videos/abc123xyz.mp4", "shareUrl": "https://cdn.hooked.so/shared/abc123xyz.mp4", "metadata": { "projectId": "proj_p2v_abc123xyz", "contentType": "educational" } }, "message": "Video completed" } ``` Your webhook endpoint must return a `200` status code. We'll retry up to 3 times if the request fails. *** ## Visual Style Presets Available visual style presets for the `presetSettings.preset` field: | Preset | Description | | - | - | | `realistic` | Pure photorealistic style with natural lighting and lifelike details | | `anime` | Classic anime style with large expressive eyes, vibrant colors, clean line art | | `cinematic` | Hollywood movie cinematography with dramatic lighting and wide shots | | `fantasy` | Epic fantasy art with magical elements, mythical creatures, enchanted environments | | `cyberpunk-anime` | Futuristic anime style with neon colors, cybernetic elements, dystopian atmosphere | | `pixar` | 3D animated style inspired by Pixar with smooth rendering and expressive characters | | `comic-book` | American comic book style with bold colors, dynamic action, superhero aesthetics | | `real-anime` | Realistic anime style blending photorealistic elements with anime aesthetics | | `ghibli-studio` | Studio Ghibli animation style with soft colors, whimsical characters, magical atmosphere | | `sketch-black-and-white` | Monochrome pencil sketch with detailed shading and artistic line work | | `art-style` | Fine art style with painterly techniques, artistic composition, classical aesthetics | | `retro-anime` | 1980s-90s anime aesthetic with vintage color palette and classic animation style | | `80s-fantasy-movie` | Retro 80s fantasy film aesthetic with practical effects, vibrant colors | | `cartoon` | Classic cartoon style with bold outlines, flat colors, exaggerated features | | `creative` | Abstract creative style with experimental techniques and innovative artistic approaches | | `gta-v` | Grand Theft Auto V video game style with urban aesthetic and satirical tone | | `sketch-color` | Colored sketch with vibrant markers and artistic illustration techniques | | `japanese-ink` | Traditional Japanese sumi-e ink painting with black ink, red accents, flowing brushstrokes | | `space-marines-40k` | Warhammer 40K Space Marines style with power armor, gothic architecture, grimdark atmosphere | | `haunted-linework` | Gothic horror style with intricate linework, dark atmosphere, supernatural elements | | `ink-style` | Modern ink art with dynamic strokes, abstract elements, energetic composition | | `neon-futuristic` | Cyberpunk aesthetic with bright neon colors, futuristic technology, sci-fi atmosphere | | `minecraft` | Blocky pixel art style inspired by Minecraft with cubic shapes and vibrant colors | | `pixel-art` | 8-bit and 16-bit pixel art style with retro gaming aesthetics and limited color palette | | `collage` | Mixed media collage with newspaper elements, geometric shapes, layered textures | | `lego` | LEGO brick style with plastic toy aesthetic and modular construction elements | | `technical-blueprints` | Engineering blueprint style with technical drawings, measurements, schematic details | *** ## Media Types Available media types for `presetSettings.mediaType`: | Type | Description | | - | - | | `ai-images` | AI-generated images based on your generated script content | | `ai-videos` | AI-generated video clips synchronized with narration | | `media` | Stock media automatically selected based on script | | `gameplay` | Gameplay footage from popular games | *** ## Caption Presets Available caption presets for the `caption.preset` field: | Preset | Description | | - | - | | `default` | Default caption style with bold text and shadow effects | | `beast` | Bold uppercase style with Komika font | | `umi` | Yellow glowing text style | | `tiktok` | Viral & trendy style, perfect for social media | | `wrap1` | Wrapped style with red background highlight | | `wrap2` | Wrapped style with blue background highlight (uppercase) | | `ariel` | Bold uppercase style with purple highlight | | `hooked` | Brand style with purple background | | `classic` | Clean, simple captions with black background | | `active` | Green background with bold text | | `bubble` | White background bubble style | | `glass` | Glassmorphic transparency effect | | `comic` | Comic Sans font with colorful style | | `glow` | Pink and orange glow effects | | `pastel` | Soft pastel pink background | | `neon` | Green neon glow effect | | `retroTV` | Retro TV style with cyan glow | | `red` | Red glow effect with white text | | `marker` | Yellow marker/highlighter style | | `modern` | Contemporary white background style | | `blue` | Blue background style | | `vivid` | Vibrant pink background with uppercase text | *** ## Target Duration Guide Choose the right duration for your use case: | Duration | Use Case | Processing Time | | - | - | - | | 30s | Quick tips, single facts, viral hooks | 2-4 minutes | | 45s | Short tutorials, product highlights | 3-5 minutes | | 60s | Standard explainers, listicles | 4-6 minutes | | 90s | Comprehensive guides, deep dives | 6-10 minutes | | 120s | Extended content, detailed tutorials | 8-15 minutes | *** ## Best Practices Be specific about the topic, tone, and target audience for better script generation. Use ai-images for educational content, gameplay for gaming, custom media for brands. Choose a voice that fits your content tone - energetic for gaming, professional for business. Always use webhooks in production instead of polling for video status. *** ## Prompt Writing Tips ### Good Prompts "Create an engaging video about the top 5 productivity tips for remote workers. Target busy professionals and use a motivational tone." "Explain how blockchain technology works using simple analogies. Make it beginner-friendly and include real-world examples." "Share 3 mind-blowing facts about space that most people don't know. Hook viewers in the first 3 seconds." ### Avoid "Make a video" (too vague) "Video about stuff" (no direction) "Something interesting" (unclear intent) *** ## Error Handling | Error | Description | Solution | | - | - | - | | `prompt: Prompt is required` | Missing or empty prompt | Add the `prompt` field with your description | | `prompt: Prompt cannot exceed 4000 characters` | Prompt too long | Shorten your prompt to under 4000 characters | | `voiceId: Voice not found` | Invalid voice ID | Use a valid voice ID from `/v1/voice/list` | | `targetDuration: Target duration must be at least 30 seconds` | Duration too short | Use minimum 30 seconds | | `targetDuration: Target duration cannot exceed 120 seconds` | Duration too long | Use maximum 120 seconds | | `presetSettings.preset: Preset is required` | Missing preset when using auto-generated media | Add the `preset` field in `presetSettings` | | `webhook: Must be a valid HTTPS URL` | Invalid webhook URL | Ensure webhook URL starts with `https://` | | `media: Cannot have more than 50 media items` | Too many media items | Reduce media array to 50 items or fewer | | `Not enough credits` | Insufficient credits | Top up your account credits | *** ## Next Steps Find the perfect voice for your video Add background music to your videos View all your created videos Learn how to handle webhook notifications # Remove Background Source: https://docs.hooked.so/api-reference/video/remove-background POST /v1/project/create/remove-background Remove backgrounds from videos **Try it out!** Use the API playground on the right to test the Remove Background endpoint directly. ## Overview Remove Background automatically removes the background from your videos, creating a transparent background effect. Perfect for: * Product videos * Talking head videos * Social media content * Professional presentations * Green screen replacements The API processes your video and returns a clean version with the background removed, ready to overlay on any content. Videos up to 10 minutes are accepted; longer sources are processed in parts and joined back automatically. *** ## Endpoint ``` POST /v1/project/create/remove-background ``` *** ## Required Fields Media ID of the video to process. The video must already be uploaded to your account. *** ## Optional Fields Captions are off by default. Set to `false` to generate and burn subtitles on the result. `videoSettings.caption.preset` and `videoSettings.caption.alignment` (`top` | `middle` | `bottom`) style them; `videoSettings.language` sets the caption language. *** ## Request Example ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/remove-background', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ media: 'video_01', videoSettings: { caption: { disabled: false, preset: 'classic', alignment: 'bottom' } }, webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'summer-launch-2024', variant: 'A', createdBy: 'marketing-automation' } }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` *** ## Response ```json Success Response theme={null} { "success": true, "data": { "videoId": "vid_rbg_abc123xyz", "projectId": "proj_rbg_abc123xyz", "status": "STARTED" }, "message": "Remove Background successfully created" } ``` ```json Error Response - Validation Error theme={null} { "success": false, "message": "media: Video is required" } ``` ```json Error Response - Invalid Video Type theme={null} { "success": false, "message": "media: Media must be a video file" } ``` *** ## Webhook Notification When your video is ready, we'll POST to your webhook URL (if configured): ```json Webhook Payload theme={null} { "status": "COMPLETED", "data": { "videoId": "vid_rbg_abc123xyz", "status": "COMPLETED", "url": "https://cdn.hooked.so/videos/abc123xyz.mp4", "shareUrl": "https://cdn.hooked.so/shared/abc123xyz.mp4", "metadata": { "projectId": "proj_rbg_abc123xyz" } }, "message": "Video completed" } ``` *** ## Best Practices Use high-quality source videos for best background removal results. Videos with good lighting and clear subject separation work best. Sharper videos work better for background removal. Ensure good contrast between subject and background. *** ## Error Handling | Error | Description | Solution | | - | - | - | | `media: Video is required` | Missing media ID | Provide a media ID string | | `media: Video not found` | Media ID doesn't exist in your account | Ensure the media ID is correct and belongs to your team | | `media: Media must be a video file` | Media is not a video type | Provide a video media ID, not an image media ID | | `Not enough credits` | Insufficient credits | Top up your account credits | *** ## Next Steps View all your created videos Check your video processing status Learn how to handle webhook notifications Create talking avatar videos with AI avatars # Scenes Source: https://docs.hooked.so/api-reference/video/scenes POST /v1/project/create/scenes Create multi-scene videos combining avatars, video clips, and AI-generated transitions **Try it out!** Use the API playground on the right to test the Scenes endpoint directly. ## Overview Scenes allows you to create complex, multi-scene videos by combining different content types in a storyboard format. Each scene can be one of four types: * **Avatar**: A talking avatar scene with a script * **Video**: A video or image clip with optional voiceover * **Picture in Picture**: An avatar overlaid on a video/image background * **Start/End Frame**: AI-generated video transition between two images This format is ideal for: * Long-form content with multiple segments * Tutorials with avatar explanations and demo clips * Marketing videos with varied visual styles * Storytelling with scene transitions * Educational content mixing talking heads and visuals Scenes automatically handles transitions between different scene types and maintains consistent captions throughout the video. *** ## Endpoint ``` POST /v1/project/create/scenes ``` *** ## Required Fields Array of scene objects (1-50 scenes). Each scene defines a segment of your video. The type of scene: * `avatar`: Talking avatar scene * `video`: Video/image clip scene * `picture_in_picture`: Avatar overlaid on video/image * `start_end_frame`: AI-generated video from start/end images The script for the avatar to speak (max 5,000 characters). Required for `avatar` and `picture_in_picture` types. Avatar ID from `/v1/avatar/list`. Required for `avatar` and `picture_in_picture` types. Voice ID from `/v1/voice/list`. Used for the voiceover of `video` and `start_end_frame` scenes that carry a script. Not used by `avatar` and `picture_in_picture` scenes: the avatar speaks the script with its own voice. Media ID for video/image content. Required for `video` and `picture_in_picture` types. Media ID for the starting frame image. Required for `start_end_frame` type. Media ID for the ending frame image. Required for `start_end_frame` type. AI model for generating the transition video: * `kling_2_5`: Kling 2.5 model * `veo_3_1`: Google Veo 3.1 model Prompt describing the transition between start and end frames (max 2,000 characters). Duration of the AI-generated transition video in seconds. Must be between 1 and 300. Default: 5. *** ## Optional Fields Video name (max 100 characters). If not provided, a name will be auto-generated from the scene scripts. Background music ID from `/v1/music/list`. Caption settings for the video Caption preset style. Available presets: `default`, `beast`, `umi`, `tiktok`, `wrap1`, `wrap2`, `ariel`, `hooked`, `classic`, `active`, `bubble`, `glass`, `comic`, `glow`, `pastel`, `neon`, `retroTV`, `red`, `marker`, `modern`, `blue`, `vivid`. Caption position on the video: `top`, `middle`, or `bottom` Set to `false` to show captions on the video Video aspect ratio: * `ratio_9_16`: Vertical (TikTok, Reels, Shorts) - **Recommended** * `ratio_16_9`: Horizontal (YouTube) * `ratio_1_1`: Square (Instagram) Language code for the video (2 characters). Example: `en`, `es` HTTPS URL to receive completion notification (max 500 characters). **Highly recommended** for production use. Custom metadata object (max 5KB). Store any additional data you need to associate with this video. ```json theme={null} { "courseId": "course_123", "lessonNumber": 5 } ``` *** ## Scene Types ### Avatar Scene A talking avatar speaks the script on camera. Voice, lip movement and gestures are generated natively by the video model. ```json theme={null} { "type": "avatar", "script": "Welcome to our tutorial! Today we'll learn about...", "avatarId": "professional_avatar_id", "voiceId": "confident_voice_id" } ``` **Required fields**: `script`, `avatarId`, `voiceId` ### Video Scene A video or image clip plays, optionally with voiceover. ```json theme={null} { "type": "video", "mediaId": "demo_video_id", "script": "As you can see in this demo...", "voiceId": "professional_voice_id" } ``` **Required fields**: `mediaId` ### Picture in Picture Scene An avatar appears overlaid on a video/image background. ```json theme={null} { "type": "picture_in_picture", "script": "As you can see in this demo...", "avatarId": "presenter_avatar_id", "voiceId": "professional_voice_id", "mediaId": "background_video_id" } ``` **Required fields**: `script`, `avatarId`, `voiceId`, `mediaId` ### Start/End Frame Scene AI generates a video transition between two images. ```json theme={null} { "type": "start_end_frame", "startFrameMediaId": "start_image_id", "endFrameMediaId": "end_image_id", "frameModel": "veo_3_1", "framePrompt": "Smooth camera zoom out revealing the full scene", "frameDuration": 5 } ``` **Required fields**: `startFrameMediaId`, `endFrameMediaId`, `framePrompt` *** ## Request Examples ## Basic Multi-Scene Video ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/scenes', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ scenes: [ { type: 'avatar', script: 'Welcome to our product demo! Let me show you how it works.', avatarId: 'professional_avatar_id', voiceId: 'confident_voice_id' }, { type: 'video', mediaId: 'product_demo_video_id' }, { type: 'avatar', script: 'As you can see, it is incredibly easy to use. Try it today!', avatarId: 'professional_avatar_id', voiceId: 'confident_voice_id' } ] }) }); const data = await response.json(); console.log('Project ID:', data.projectId); ``` ## Tutorial with Picture in Picture ```javascript theme={null} const createTutorial = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/scenes', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Getting Started Tutorial', scenes: [ { type: 'avatar', script: 'Hi there! In this tutorial, I will walk you through the setup process step by step.', avatarId: 'friendly_avatar_id', voiceId: 'warm_voice_id' }, { type: 'picture_in_picture', script: 'First, click on the settings icon in the top right corner.', avatarId: 'friendly_avatar_id', voiceId: 'warm_voice_id', mediaId: 'settings_screenshot_id' }, { type: 'picture_in_picture', script: 'Then, navigate to the integrations tab and enable the API access.', avatarId: 'friendly_avatar_id', voiceId: 'warm_voice_id', mediaId: 'integrations_screenshot_id' }, { type: 'avatar', script: 'And that is it! You are all set up and ready to go.', avatarId: 'friendly_avatar_id', voiceId: 'warm_voice_id' } ], musicId: 'upbeat_background_id', aspectRatio: 'ratio_16_9', caption: { preset: 'modern', alignment: 'bottom', disabled: false }, language: 'en', webhook: 'https://yoursite.com/webhook', metadata: { tutorialId: 'getting-started', version: '1.0' } }) }); return await response.json(); }; ``` ## AI Transition Video ```javascript theme={null} const createTransitionVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/scenes', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ scenes: [ { type: 'avatar', script: 'Watch this amazing transformation!', avatarId: 'energetic_avatar_id', voiceId: 'excited_voice_id' }, { type: 'start_end_frame', startFrameMediaId: 'before_image_id', endFrameMediaId: 'after_image_id', frameModel: 'veo_3_1', framePrompt: 'Smooth morphing transition from the before state to the after state with elegant camera movement' }, { type: 'avatar', script: 'Incredible, right? This is the power of our solution.', avatarId: 'energetic_avatar_id', voiceId: 'excited_voice_id' } ], aspectRatio: 'ratio_9_16' }) }); return await response.json(); }; ``` ## Response ```json Success Response theme={null} { "success": true, "projectId": "proj_scenes_abc123xyz", "message": "Scenes project successfully created" } ``` ```json Error Response - No Scenes theme={null} { "success": false, "message": "At least one scene is required" } ``` ```json Error Response - Invalid Scene theme={null} { "success": false, "message": "scenes.0.script: Script is required for avatar scenes" } ``` ```json Error Response - Not Enough Credits theme={null} { "success": false, "message": "Not enough credits for scenes video. Estimated: 50, Available: 25" } ``` *** ## Webhook Notification When your video is ready, we'll POST to your webhook URL: ```json Webhook Payload theme={null} { "status": "COMPLETED", "data": { "videoId": "vid_scenes_abc123xyz", "status": "COMPLETED", "url": "https://cdn.hooked.so/videos/abc123xyz.mp4", "shareUrl": "https://cdn.hooked.so/shared/abc123xyz.mp4", "metadata": { "projectId": "proj_scenes_abc123xyz", "tutorialId": "getting-started" } }, "message": "Video completed" } ``` Your webhook endpoint must return a `200` status code. We'll retry up to 3 times if the request fails. *** ## Scene Type Requirements | Scene Type | Required Fields | Optional Fields | | - | - | - | | `avatar` | `script`, `avatarId`, `voiceId` | | | `video` | `mediaId` | `script`, `voiceId`, | | `picture_in_picture` | `script`, `avatarId`, `voiceId`, `mediaId` | | | `start_end_frame` | `startFrameMediaId`, `endFrameMediaId`, `framePrompt` | `frameModel`, `frameDuration` | *** ## AI Models for Start/End Frame | Model | Description | Best For | | - | - | - | | `kling_2_5` | Kling 2.5 model | Fast generation, general transitions | | `veo_3_1` | Google Veo 3.1 | High quality, complex camera movements | *** ## Caption Presets Available caption presets for the `caption.preset` field: | Preset | Description | | - | - | | `default` | Default caption style with bold text and shadow effects | | `beast` | Bold uppercase style with Komika font | | `umi` | Yellow glowing text style | | `tiktok` | Viral & trendy style, perfect for social media | | `wrap1` | Wrapped style with red background highlight | | `wrap2` | Wrapped style with blue background highlight (uppercase) | | `ariel` | Bold uppercase style with purple highlight | | `hooked` | Brand style with purple background | | `classic` | Clean, simple captions with black background | | `active` | Green background with bold text | | `bubble` | White background bubble style | | `glass` | Glassmorphic transparency effect | | `comic` | Comic Sans font with colorful style | | `glow` | Pink and orange glow effects | | `pastel` | Soft pastel pink background | | `neon` | Green neon glow effect | | `retroTV` | Retro TV style with cyan glow | | `red` | Red glow effect with white text | | `marker` | Yellow marker/highlighter style | | `modern` | Contemporary white background style | | `blue` | Blue background style | | `vivid` | Vibrant pink background with uppercase text | *** ## Best Practices Outline your scenes before creating. Each scene should serve a clear purpose in the narrative. Use the same avatar across scenes for continuity, unless intentionally switching perspectives. Mix avatar, video, and PiP scenes to keep viewers engaged and add visual variety. Write scripts that flow naturally between scenes. End one scene leading into the next. Keep individual scenes focused. Split long content into multiple shorter scenes. Leverage AI transitions for impressive visual effects between static images. *** ## Error Handling | Error | Description | Solution | | - | - | - | | `At least one scene is required` | Empty scenes array | Add at least one scene to the array | | `Cannot have more than 50 scenes` | Too many scenes | Reduce the number of scenes to 50 or fewer | | `Script is required for avatar scenes` | Missing script in avatar scene | Add the `script` field | | `Avatar ID is required for avatar scenes` | Missing avatarId | Add a valid `avatarId` from `/v1/avatar/list` | | `Voice ID is required for avatar scenes` | Missing voiceId | Add a valid `voiceId` from `/v1/voice/list` | | `Media ID is required for video scenes` | Missing mediaId in video scene | Add a valid `mediaId` | | `Not enough credits` | Insufficient credits | Top up your account credits | *** ## Credits Scenes videos use a credit model based on: * **Base credits**: Fixed cost per video * **Avatar scene credits**: Charged based on script duration (similar to UGC Video) * **Video scenes**: Included in base credits (no additional charge) * **Start/End Frame**: Additional credits for AI generation *** ## Next Steps Browse available avatars for your scenes Find the perfect voice for your avatars Add background music to your video Learn how to handle webhook notifications # Script to Video Source: https://docs.hooked.so/api-reference/video/script-to-video POST /v1/project/create/script-to-video Create videos from scripts with AI-generated or custom media **Try it out!** Use the API playground on the right to test the Script to Video endpoint directly. ## Overview Script to Video transforms your written scripts into engaging videos with voice narration and auto-generated or custom media. This format is ideal for: * Educational content and tutorials * Social media content creation * Marketing videos * Storytelling and narrative content * News-style videos Script to Video automatically generates visuals based on your script or allows you to provide custom media for full creative control. *** ## Endpoint ``` POST /v1/project/create/script-to-video ``` *** ## Required Fields The script for the video narration (1-10,000 characters). This will be converted to speech and synchronized with visuals. Voice ID from `/v1/voice/list`. The voice used for narrating the script. Type of media to generate: * `ai-images`: AI-generated images based on script * `ai-videos`: AI-generated video clips * `media`: Use stock media (requires `media` array) * `gameplay`: Use gameplay footage (requires `gameplaySettings`) *** ## Optional Fields Video name (max 100 characters) Settings for auto-generated media (used when `mediaType` is `ai-images` or `ai-videos`) Visual style preset for AI-generated images/videos. Available presets: `realistic`, `anime`, `cinematic`, `fantasy`, `cyberpunk-anime`, `pixar`, `comic-book`, `real-anime`, `ghibli-studio`, `sketch-black-and-white`, `art-style`, `retro-anime`, `80s-fantasy-movie`, `cartoon`, `creative`, `gta-v`, `sketch-color`, `japanese-ink`, `space-marines-40k`, `haunted-linework`, `ink-style`, `neon-futuristic`, `minecraft`, `pixel-art`, `collage`, `lego`, `technical-blueprints` Generation quality: `base`, `pro`, or `ultra` Array of media IDs (max 50). The media must already be uploaded to your account. Required when `mediaType` is `media`. If not provided, media will be auto-generated based on `mediaType`. Settings for gameplay footage (required when `mediaType` is `gameplay`) Game identifier for gameplay footage. Available games: `minecraft`, `subway-s`, `temple-run`, `gta` Specific gameplay video to use. Available videos: * Minecraft: `minecraft-1` through `minecraft-8` * Subway S: `subway-s-1` through `subway-s-11` * Temple Run: `temple-run-1` through `temple-run-8` * GTA: `gta-1` through `gta-12` Music ID from `/v1/music/list` for background music. Caption settings for the video Caption preset style. Available presets: `default`, `beast`, `umi`, `tiktok`, `wrap1`, `wrap2`, `ariel`, `hooked`, `classic`, `active`, `bubble`, `glass`, `comic`, `glow`, `pastel`, `neon`, `retroTV`, `red`, `marker`, `modern`, `blue`, `vivid`. Caption position on the video: `top`, `middle`, or `bottom` Set to `true` to hide captions on the video Video aspect ratio: * `ratio_9_16`: Vertical (TikTok, Reels, Shorts) * `ratio_16_9`: Horizontal (YouTube) * `ratio_1_1`: Square (Instagram) Voice audio settings Voice speed multiplier (0.7 to 1.2) Voice stability (0 to 1). Higher values produce more consistent speech. Voice similarity boost (0 to 1). Higher values make the voice more similar to the original. Voice style exaggeration (0 to 1). Enable speaker boost for clearer audio. Enable automatic sticker generation for the video. Adds engaging visual elements automatically. HTTPS URL to receive completion notification (max 500 characters). **Highly recommended** for production use. Custom metadata object (max 5KB). Store any additional data you need to associate with this video. ```json theme={null} { "campaignId": "summer2024", "contentType": "educational" } ``` *** ## Request Examples ## With AI-Generated Images ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/script-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ script: 'Artificial intelligence is transforming the way we work and live. From smart assistants to self-driving cars, AI is everywhere.', voiceId: 'confident_voice_id', mediaType: "ai-images", presetSettings: { preset: "realistic", quality: "pro" }, musicId: "2", aspectRatio: 'ratio_9_16', caption: { preset: 'beast', alignment: 'bottom', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'artificial-intelligence' } }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ## With AI-Generated Videos ```javascript theme={null} const createAIImageVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/script-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'AI Technology Explainer', script: 'Artificial intelligence is transforming the way we work and live. From smart assistants to self-driving cars, AI is everywhere. Let\'s explore how this technology is shaping our future.', voiceId: 'confident_voice_id', mediaType: 'ai-videos', presetSettings: { preset: 'anime', quality: 'base' }, musicId: 'music_ambient_01', aspectRatio: 'ratio_9_16', caption: { preset: 'modern', alignment: 'bottom', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'artificial-intelligence' } }) }); return await response.json(); }; ``` ## With Custom Media ```javascript theme={null} const createCustomMediaVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/script-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Product Showcase', script: 'Introducing our revolutionary new product. It\'s designed to make your life easier and more productive. Watch as we demonstrate its incredible features.', voiceId: 'enthusiastic_voice_id', media: ['intro_shot', 'demo_shot', 'feature_image'], musicId: 'music_upbeat_01', aspectRatio: 'ratio_9_16', caption: { preset: 'wrap1', alignment: 'bottom', disabled: false }, mediaType: 'media', addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'artificial-intelligence' } }) }); return await response.json(); }; ``` ## With Gameplay ```javascript theme={null} const createGamingVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/script-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Gaming Tips Video', script: 'Want to level up your gaming skills? Here are three pro tips that will help you dominate your opponents. Tip one: always check your surroundings...', voiceId: 'energetic_voice_id', mediaType: 'gameplay', gameplaySettings: { selectedGame: 'minecraft', selectedVideo: 'minecraft-1' }, musicId: 'music_upbeat_01', aspectRatio: 'ratio_9_16', caption: { preset: 'beast', alignment: 'top', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'artificial-intelligence' } }) }); return await response.json(); }; ``` *** ## Response ```json Success Response theme={null} { "success": true, "data": { "videoId": "vid_s2v_abc123xyz", "projectId": "proj_s2v_abc123xyz", "status": "STARTED" }, "message": "Script to Video successfully created" } ``` ```json Error Response - Validation Error theme={null} { "success": false, "message": "script: Script is required" } ``` ```json Error Response - Voice Not Found theme={null} { "success": false, "message": "voiceId: Voice not found" } ``` *** ## Webhook Notification When your video is ready, we'll POST to your webhook URL: ```json Webhook Payload theme={null} { "status": "COMPLETED", "data": { "videoId": "vid_s2v_abc123xyz", "status": "COMPLETED", "url": "https://cdn.hooked.so/videos/abc123xyz.mp4", "shareUrl": "https://cdn.hooked.so/shared/abc123xyz.mp4", "metadata": { "projectId": "proj_s2v_abc123xyz", "productId": "PROD-001" } }, "message": "Video completed" } ``` Your webhook endpoint must return a `200` status code. We'll retry up to 3 times if the request fails. *** ## Visual Style Presets Available visual style presets for the `presetSettings.preset` field: | Preset | Description | | - | - | | `realistic` | Pure photorealistic style with natural lighting and lifelike details | | `anime` | Classic anime style with large expressive eyes, vibrant colors, clean line art | | `cinematic` | Hollywood movie cinematography with dramatic lighting and wide shots | | `fantasy` | Epic fantasy art with magical elements, mythical creatures, enchanted environments | | `cyberpunk-anime` | Futuristic anime style with neon colors, cybernetic elements, dystopian atmosphere | | `pixar` | 3D animated style inspired by Pixar with smooth rendering and expressive characters | | `comic-book` | American comic book style with bold colors, dynamic action, superhero aesthetics | | `real-anime` | Realistic anime style blending photorealistic elements with anime aesthetics | | `ghibli-studio` | Studio Ghibli animation style with soft colors, whimsical characters, magical atmosphere | | `sketch-black-and-white` | Monochrome pencil sketch with detailed shading and artistic line work | | `art-style` | Fine art style with painterly techniques, artistic composition, classical aesthetics | | `retro-anime` | 1980s-90s anime aesthetic with vintage color palette and classic animation style | | `80s-fantasy-movie` | Retro 80s fantasy film aesthetic with practical effects, vibrant colors | | `cartoon` | Classic cartoon style with bold outlines, flat colors, exaggerated features | | `creative` | Abstract creative style with experimental techniques and innovative artistic approaches | | `gta-v` | Grand Theft Auto V video game style with urban aesthetic and satirical tone | | `sketch-color` | Colored sketch with vibrant markers and artistic illustration techniques | | `japanese-ink` | Traditional Japanese sumi-e ink painting with black ink, red accents, flowing brushstrokes | | `space-marines-40k` | Warhammer 40K Space Marines style with power armor, gothic architecture, grimdark atmosphere | | `haunted-linework` | Gothic horror style with intricate linework, dark atmosphere, supernatural elements | | `ink-style` | Modern ink art with dynamic strokes, abstract elements, energetic composition | | `neon-futuristic` | Cyberpunk aesthetic with bright neon colors, futuristic technology, sci-fi atmosphere | | `minecraft` | Blocky pixel art style inspired by Minecraft with cubic shapes and vibrant colors | | `pixel-art` | 8-bit and 16-bit pixel art style with retro gaming aesthetics and limited color palette | | `collage` | Mixed media collage with newspaper elements, geometric shapes, layered textures | | `lego` | LEGO brick style with plastic toy aesthetic and modular construction elements | | `technical-blueprints` | Engineering blueprint style with technical drawings, measurements, schematic details | *** ## Media Types Available media types for `mediaType`: | Type | Description | | - | - | | `ai-images` | AI-generated images based on your script content | | `ai-videos` | AI-generated video clips synchronized with narration | | `media` | Stock media automatically selected based on script | | `gameplay` | Gameplay footage from popular games | *** ## Caption Presets Available caption presets for the `caption.preset` field: | Preset | Description | | - | - | | `default` | Default caption style with bold text and shadow effects | | `beast` | Bold uppercase style with Komika font | | `umi` | Yellow glowing text style | | `tiktok` | Viral & trendy style, perfect for social media | | `wrap1` | Wrapped style with red background highlight | | `wrap2` | Wrapped style with blue background highlight (uppercase) | | `ariel` | Bold uppercase style with purple highlight | | `hooked` | Brand style with purple background | | `classic` | Clean, simple captions with black background | | `active` | Green background with bold text | | `bubble` | White background bubble style | | `glass` | Glassmorphic transparency effect | | `comic` | Comic Sans font with colorful style | | `glow` | Pink and orange glow effects | | `pastel` | Soft pastel pink background | | `neon` | Green neon glow effect | | `retroTV` | Retro TV style with cyan glow | | `red` | Red glow effect with white text | | `marker` | Yellow marker/highlighter style | | `modern` | Contemporary white background style | | `blue` | Blue background style | | `vivid` | Vibrant pink background with uppercase text | *** ## Best Practices Structure your script with clear sentences and natural pauses for better pacing. Use ai-images for educational content, gameplay for gaming, custom media for brands. Choose a voice that fits your content tone - energetic for gaming, professional for business. Always use webhooks in production instead of polling for video status. *** ## Error Handling | Error | Description | Solution | | - | - | - | | `script: Script is required` | Missing or empty script | Add the `script` field with your content | | `voiceId: Voice not found` | Invalid voice ID | Use a valid voice ID from `/v1/voice/list` | | `presetSettings.preset: Preset is required` | Missing preset when using auto-generated media | Add the `preset` field in `presetSettings` | | `webhook: Must be a valid HTTPS URL` | Invalid webhook URL | Ensure webhook URL starts with `https://` | | `media: Cannot have more than 50 media items` | Too many media items | Reduce media array to 50 items or fewer | | `Not enough credits` | Insufficient credits | Top up your account credits | *** ## Next Steps Find the perfect voice for your video Add background music to your videos View all your created videos Learn how to handle webhook notifications # Talking Avatar Source: https://docs.hooked.so/api-reference/video/talking-avatar POST /v1/project/create/talking-avatar Create talking avatar videos with AI avatars **Try it out!** Use the API playground on the right to test the Talking Avatar endpoint directly. ## Overview Talking Avatar creates videos of an AI avatar speaking your script. The voice, lip movement and gestures are generated natively by the video model: you pick the avatar and write the script, nothing else. This format is ideal for: * Testimonial-style videos * Social media content * Personal video messages * Quick video updates * Product presentations The script is split into short clips and each clip is generated with the avatar speaking on camera. There is no `voiceId` and no lipsync model to choose: the avatar comes with its voice. *** ## Endpoint ``` POST /v1/project/create/talking-avatar ``` *** ## Required Fields The script for the avatar to speak (1-10,000 characters). Write naturally as if a real person is speaking. Avatar ID from `/v1/avatar/list`. Choose an avatar that matches your target audience. *** ## Optional Fields Video name (max 100 characters) Music ID from `/v1/music/list` for background music. Caption settings for the video Caption preset style. Available presets: `default`, `beast`, `umi`, `tiktok`, `wrap1`, `wrap2`, `ariel`, `hooked`, `classic`, `active`, `bubble`, `glass`, `comic`, `glow`, `pastel`, `neon`, `retroTV`, `red`, `marker`, `modern`, `blue`, `vivid`. Caption position on the video: `top`, `middle`, or `bottom` Set to `true` to hide captions on the video Accepted for backwards compatibility and ignored. The avatar's speech is generated by the video model together with the picture, so there is no separate voice track to tune. HTTPS URL to receive completion notification (max 500 characters). **Highly recommended** for production use. Custom metadata object (max 5KB). Store any additional data you need to associate with this video. ```json theme={null} { "campaignId": "summer2024", "customField": "any value" } ``` *** ## Request Examples ### Basic Talking Avatar ```json theme={null} { "script": "Check out this amazing product! It's super easy to use and I love the results.", "avatarId": "2" } ``` ### Complete Talking Avatar ```json theme={null} { "name": "Welcome Video", "script": "Hey there! Thanks for checking out our channel. We create awesome content every week, so make sure to subscribe and hit that notification bell!", "avatarId": "2", "caption": { "preset": "tiktok", "alignment": "bottom", "disabled": false }, "musicId": "music_upbeat_01", "webhook": "https://yoursite.com/webhook", "metadata": { "channelId": "my-channel", "videoType": "welcome" } } ``` *** ## Response ```json Success Response theme={null} { "success": true, "data": { "videoId": "vid_talking_avatar_abc123xyz", "projectId": "proj_ugc_abc123xyz", "status": "STARTED" }, "message": "Talking avatar successfully created" } ``` ```json Error Response - Validation Error theme={null} { "success": false, "message": "script: Script must be at least 1 character" } ``` ```json Error Response - Avatar Not Found theme={null} { "success": false, "message": "avatarId: Avatar \"invalid_id\" not found." } ``` *** ## Webhook Notification When your video is ready, we'll POST to your webhook URL: ```json Webhook Payload theme={null} { "status": "COMPLETED", "data": { "videoId": "vid_talking_avatar_abc123xyz", "status": "COMPLETED", "url": "https://cdn.hooked.so/videos/abc123xyz.mp4", "shareUrl": "https://cdn.hooked.so/shared/abc123xyz.mp4", "metadata": { "projectId": "proj_ugc_abc123xyz", "channelId": "my-channel" } }, "message": "Video completed" } ``` Your webhook endpoint must return a `200` status code. We'll retry up to 3 times if the request fails. *** ## Caption Presets Available caption presets for the `caption.preset` field: | Preset | Description | | - | - | | `default` | Default caption style with bold text and shadow effects | | `beast` | Bold uppercase style with Komika font | | `umi` | Yellow glowing text style | | `tiktok` | Viral & trendy style, perfect for social media | | `wrap1` | Wrapped style with red background highlight | | `wrap2` | Wrapped style with blue background highlight (uppercase) | | `ariel` | Bold uppercase style with purple highlight | | `hooked` | Brand style with purple background | | `classic` | Clean, simple captions with black background | | `active` | Green background with bold text | | `bubble` | White background bubble style | | `glass` | Glassmorphic transparency effect | | `comic` | Comic Sans font with colorful style | | `glow` | Pink and orange glow effects | | `pastel` | Soft pastel pink background | | `neon` | Green neon glow effect | | `retroTV` | Retro TV style with cyan glow | | `red` | Red glow effect with white text | | `marker` | Yellow marker/highlighter style | | `modern` | Contemporary white background style | | `blue` | Blue background style | | `vivid` | Vibrant pink background with uppercase text | *** ## Best Practices Scripts should sound conversational. Use contractions, pauses, and natural speech patterns. The avatar reads what you write, the way you write it. Exclamation marks, pauses and short sentences shape the delivery. Voice and look come together with the avatar, so choose the one that matches who the video is for. Shorter videos (15-60 seconds) tend to perform better on social media. Always use webhooks in production instead of polling for video status. *** ## Error Handling | Error | Description | Solution | | - | - | - | | `script: Script must be at least 1 character` | Missing or empty script | Add the `script` field with your content | | `avatarId: Avatar not found` | Invalid avatar ID | Use a valid avatar ID from `/v1/avatar/list` | | `webhook: Must be a valid HTTPS URL` | Invalid webhook URL | Ensure webhook URL starts with `https://` | | `Not enough credits` | Insufficient credits | Top up your account credits | *** ## Next Steps Browse available avatars Find the perfect voice View all your created videos Learn how to handle webhook notifications # TikTok Slideshow Source: https://docs.hooked.so/api-reference/video/tiktok-slideshow POST /v1/project/create/tiktok-slideshow Create TikTok-style slideshow videos with text overlays and custom positioning **Try it out!** Use the API playground on the right to test the TikTok Slideshow endpoint directly. ## Overview TikTok Slideshow videos combine multiple images or video clips with text overlays, music and, optionally, an avatar that narrates your script on camera over the slides. Perfect for: * Storytelling with visual elements * Product showcases with descriptions * Educational content with slides * Social media posts with multiple images * Presentation-style videos Each slide can contain up to 10 text elements with custom positioning, styling, and animations. *** ## Endpoint ``` POST /v1/project/create/tiktok-slideshow ``` *** ## Required Fields Array of slide objects for the slideshow (1-50 slides). Each slide contains a media ID and optional text overlays. Media ID from your media library. The media must already be uploaded to your account. Slide duration in seconds (1-10) Array of text elements for this slide (0-10 texts) Text content (1-500 characters) X coordinate (must be non-negative and within canvas) Y coordinate (must be non-negative and within canvas) Width in pixels (minimum 10) Height in pixels (minimum 10) Font size in pixels (8-200) Font weight (e.g., "400", "600", "bold") Text color as hex code (e.g., "#ffffff") Background color for text box Text opacity (0-1) Text rotation in degrees (-360 to 360) Text scale multiplier (0.1-10) Layer order (higher = on top) Text alignment: `left`, `center`, or `right` Font family name Border radius in pixels (0+) Text padding in pixels (0+) Maximum width as percentage (0-100) Word wrapping: `normal` or `break-word` *** ## Optional Fields Project name (1-100 characters). If not provided, a name will be auto-generated. What the avatar says over the slides (max 10,000 characters). Requires `avatarId`, and `avatarId` requires it. Avatar ID from /v1/avatar/list (max 30 characters). The avatar narrates the script on camera, keyed over the slides. Its voice is generated together with the picture, so no voice ID is needed. Requires `script`. **Deprecated, not used.** A voiceover without an avatar is not available: sending `voiceId` without `avatarId` returns an error, and with `avatarId` it is ignored. **Deprecated, not used.** These voice settings applied to the voiceover without an avatar, which is not available. They are ignored. Voice speed multiplier (0.7 to 1.2) Voice stability (0 to 1) Voice similarity boost (0 to 1) Voice style exaggeration (0 to 1) Enable speaker boost for clearer audio Background music ID from `/v1/music/list` (max 30 characters) Webhook URL for status notifications (max 500 characters, must be HTTPS) Custom metadata object (max 5KB JSON) *** ## Request Examples ### Basic Slideshow ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/tiktok-slideshow', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Product Launch', slides: [ { media: 'media_001', duration: 3, texts: [ { text: 'New Product Launch', x: 100, y: 800, width: 880, height: 120, fontSize: 48, fontWeight: '700', color: '#ffffff', backgroundColor: '#000000', textAlign: 'center', borderRadius: 8, padding: 10 } ] }, { media: 'media_002', duration: 3, texts: [ { text: 'Available Now!', x: 150, y: 1650, width: 780, height: 100, fontSize: 42, fontWeight: '800', color: '#000000', backgroundColor: '#00ff00', textAlign: 'center', borderRadius: 15, padding: 8 } ] } ] }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ### Advanced with Avatar and Music ```javascript theme={null} const createAdvancedSlideshow = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/tiktok-slideshow', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Summer Collection 2024', script: 'Check out our amazing summer collection. Three new styles, perfect for the season. Get yours today!', avatarId: 'avatar_id', musicId: 'upbeat_001', slides: [ { media: 'media_summer_001', duration: 4, texts: [ { text: 'Summer Collection 2024', x: 100, y: 150, width: 880, height: 100, fontSize: 52, fontWeight: '700', color: '#ffffff', backgroundColor: '#ff6b6b', textAlign: 'center', borderRadius: 12, padding: 20, opacity: 0.95 }, { text: 'Fresh & Stylish', x: 200, y: 280, width: 680, height: 70, fontSize: 32, fontWeight: '600', color: '#ffed4e', backgroundColor: 'transparent', textAlign: 'center' } ] }, { media: 'media_summer_002', duration: 4, texts: [ { text: '100% Premium Cotton', x: 150, y: 1400, width: 780, height: 90, fontSize: 36, fontWeight: '600', color: '#ffffff', backgroundColor: '#000000', textAlign: 'center', borderRadius: 10, padding: 12, opacity: 0.9 } ] }, { media: 'media_summer_003', duration: 4, texts: [ { text: 'Shop Now!', x: 200, y: 1650, width: 680, height: 120, fontSize: 56, fontWeight: '800', color: '#000000', backgroundColor: '#ffed4e', textAlign: 'center', borderRadius: 20, padding: 25, scale: 1.1 } ] } ], webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'summer-2024', collection: 'beachwear', variant: 'A' } }) }); return await response.json(); }; ``` *** ## Response ```json Success Response theme={null} { "success": true, "data": { "videoId": "vid_tiktok_abc123xyz", "projectId": "proj_tiktok_abc123xyz", "status": "STARTED" }, "message": "TikTok Slideshow successfully created" } ``` ```json Error Response - Validation Error theme={null} { "success": false, "message": "slides: At least one slide is required" } ``` ```json Error Response - Invalid Text Position theme={null} { "success": false, "message": "slides.0.texts.0.x: Number must be greater than or equal to 0" } ``` ```json Error Response - Script Without Avatar theme={null} { "success": false, "message": "avatarId: An avatar is required to narrate the script" } ``` *** ## Webhook Notification When your video is ready, we'll POST to your webhook URL (if configured): ```json Webhook Payload theme={null} { "status": "COMPLETED", "data": { "videoId": "vid_tiktok_abc123xyz", "status": "COMPLETED", "url": "https://cdn.hooked.so/videos/abc123xyz.mp4", "shareUrl": "https://cdn.hooked.so/shared/abc123xyz.mp4", "metadata": { "projectId": "proj_tiktok_abc123xyz" } }, "message": "Video completed" } ``` *** ## Best Practices Canvas dimensions depend on aspect ratio. For 9:16 (1080x1920), ensure coordinates are within bounds. Keep slides between 2-5 seconds for optimal engagement. Use high contrast colors and appropriate font sizes (24-48px) for mobile viewing. Use high-resolution images (1080px width minimum) for best results. *** ## Coordinate System The coordinate system for text positioning varies by aspect ratio: | Aspect Ratio | Canvas Size | Description | | - | - | - | | `ratio_9_16` | 1080 x 1920 | Vertical (TikTok/Reels) | | `ratio_16_9` | 1920 x 1080 | Horizontal (YouTube) | | `ratio_1_1` | 1080 x 1080 | Square (Instagram) | **Example**: For 9:16 format (1080x1920), a text box with `x: 100, y: 150, width: 880, height: 100` creates a full-width text near the top. *** ## Error Handling | Error | Description | Solution | | - | - | - | | `slides: At least one slide is required` | Empty or missing slides array | Provide at least 1 slide | | `slides: Cannot have more than 50 slides` | Too many slides | Limit to 50 slides maximum | | `slides.0.media: String must contain at least 1 character(s)` | Missing media ID | Provide media ID for each slide | | `slides.0.duration: Number must be between 1 and 10` | Invalid duration | Duration must be between 1 and 10 seconds | | `slides.0.texts: Cannot have more than 10 text elements` | Too many texts per slide | Limit to 10 text elements per slide | | `slides.0.texts.0.text: String must contain at least 1 character(s)` | Missing or empty text content | Provide text content (1-500 characters) | | `slides.0.texts.0.x: Number must be greater than or equal to 0` | Invalid X position | X position must be within canvas bounds (0-canvas width) | | `slides.0.texts.0.width: Number must be greater than or equal to 10` | Invalid width | Width must be at least 10 pixels | | `slides.0.texts.0.height: Number must be greater than or equal to 10` | Invalid height | Height must be at least 10 pixels | | `avatarId: An avatar is required to narrate the script` | `script` sent without `avatarId` | Add an avatar, or remove the script | | `script: A script is required with an avatar` | `avatarId` sent without `script` | Add the script the avatar says | | `avatarId and script: Avatar ID and Script are required` | `voiceId` sent without an avatar (a voiceover alone is not available) | Remove `voiceId`, or add `avatarId` and `script` | | `avatarId: Avatar "X" not found` | Invalid avatar ID | Use valid avatar ID from `/v1/avatar/list` | | `musicId: Music "X" not found` | Invalid music ID | Use valid music ID from `/v1/music/list` | | `webhook: Must be a valid HTTPS URL` | Invalid webhook URL | Ensure webhook URL uses HTTPS | | `Not enough credits` | Insufficient credits | Top up your account credits | *** ## Next Steps View all your created videos Check your video processing status Learn how to handle webhook notifications Create UGC-style advertisement videos # UGC Ads Source: https://docs.hooked.so/api-reference/video/ugc-ads POST /v1/project/create/ugc-ads Create authentic user-generated content style ads with AI avatars **Try it out!** Use the API playground on the right to test the UGC Ads endpoint directly. ## Overview UGC Ads videos are perfect for creating authentic-looking user-generated content style advertisements. This format is ideal for: * Social media advertising (TikTok, Instagram Reels, Facebook) * Product reviews and testimonials * Influencer-style marketing content * Authentic brand storytelling The avatar speaks your script on camera: voice, lip movement and gestures are generated natively by the video model, and your B-roll rides over or next to it. There is no `voiceId` to pick and no lipsync model — except in the `voiceover` format, where nobody talks to camera and a narrator voice reads the script. *** ## Endpoint ``` POST /v1/project/create/ugc-ads ``` *** ## Required Fields The script for the avatar to speak (1-10,000 characters). Write naturally as if a real person is speaking. Avatar ID from `/v1/avatar/list`. Choose an avatar that matches your target audience. B-roll media IDs (min 1, max 50). Array of media ID strings. At least one media item is required. The media must already be uploaded to your account. *** ## Optional Fields Video name (max 100 characters) Ad-specific settings for B-roll behavior B-roll display type: * `down`: Avatar on top, B-roll on bottom * `up`: B-roll on top, avatar on bottom * `left`: Avatar on right, B-roll on left * `right`: Avatar on left, B-roll on right * `rounded-cover`: Rounded avatar overlay on B-roll * `full-width`: B-roll covers entire screen, avatar hidden Remove the avatar's background for a cleaner look Music ID from `/v1/music/list` for background music. How the ad is built. * `product_in_hand`: the avatar holds and uses your product (needs `productImageKey`) * `before_after`: before and after photos of the avatar enter on the lines that name them * `app_demo`: your screen recording covers the video while the avatar narrates * `fashion`: the avatar wears each outfit you upload * `voiceover`: nobody talks to camera. A narrator (`voiceId`) reads the script over silent scenes of the avatar going about their day, one scene per sentence. The `media` B-roll is not shown in this format. Storage key or URL of the product photo. Required by `product_in_hand`; optional in `voiceover`, where one scene shows the product alone. `voiceover` format only: the narrator, a voice ID from `/v1/voice/list`. Required when `ugcStudio.format` is `voiceover`; the other formats speak with the avatar's own voice and ignore it. Video aspect ratio: * `ratio_9_16`: Vertical (TikTok, Reels, Shorts) - **Recommended for UGC** * `ratio_16_9`: Horizontal (YouTube) * `ratio_1_1`: Square (Instagram) Caption settings for the video Caption preset style. Available presets: `default`, `beast`, `umi`, `tiktok`, `wrap1`, `wrap2`, `ariel`, `hooked`, `classic`, `active`, `bubble`, `glass`, `comic`, `glow`, `pastel`, `neon`, `retroTV`, `red`, `marker`, `modern`, `blue`, `vivid`. See the Caption Presets section below for details. Caption position on the video: `top`, `middle`, or `bottom` Set to `true` to hide captions on the video Accepted for backwards compatibility and ignored. The avatar's speech is generated by the video model together with the picture, so there is no separate voice track to tune. HTTPS URL to receive completion notification (max 500 characters). **Highly recommended** for production use. Custom metadata object (max 5KB). Store any additional data you need to associate with this video. ```json theme={null} { "campaignId": "summer2024", "productSku": "SKU-12345", "customField": "any value" } ``` *** ## Request Examples ### Basic UGC Ad ```json theme={null} { "script": "I've been using this product for a month now and I'm obsessed! Let me show you why it's so amazing.", "avatarId": "avatar_influencer_01", "media": ["product_demo"] } ``` ### Voiceover Ad A narrator reads the script over silent scenes of the avatar. With a product photo, one scene shows the product alone. ```json theme={null} { "script": "I used to spend half an hour every morning fighting the frizz. By noon it was back, every single day. Now I put this on after washing and forget about it. Try it this week and tell me how it goes.", "avatarId": "avatar_casual_01", "voiceId": "nPczCjzI2devNBz1zQrb", "ugcStudio": { "format": "voiceover", "productImageKey": "https://yoursite.com/product.jpg" }, "media": ["product_image"] } ``` ### Complete UGC Ad ```json theme={null} { "name": "Brand Campaign Ad", "script": "Stop scrolling! I need to tell you about something that literally changed my morning routine. I was skeptical at first, but after trying this for just one week... I'm never going back.", "avatarId": "avatar_professional_01", "adSettings": { "bRollType": "down", "removeAvatarBackground": false }, "media": ["demo_video", "product_image"], "musicId": "trending_music_01", "aspectRatio": "ratio_9_16", "caption": { "preset": "beast", "alignment": "bottom", "disabled": false }, "webhook": "https://api.yoursite.com/hooks/hooked", "metadata": { "campaignId": "Q1-2024-ugc", "abTestVariant": "B", "internalRef": "MKT-5678" } } ``` ### UGC Ad with Picture-in-Picture Avatar ```json theme={null} { "name": "Product Review", "script": "I've been testing this product for weeks and I'm blown away by the results. Let me show you exactly what makes it special.", "avatarId": "avatar_influencer_01", "adSettings": { "bRollType": "rounded-cover", "removeAvatarBackground": true }, "media": ["product_demo", "unboxing_video"], "musicId": "trending_music_01", "aspectRatio": "ratio_9_16", "caption": { "preset": "tiktok", "alignment": "bottom", "disabled": false }, "webhook": "https://api.yoursite.com/hooks/hooked", "metadata": { "campaignId": "product-review-2024", "productId": "PROD-12345" } } ``` *** ## Response ```json Success Response theme={null} { "success": true, "data": { "videoId": "vid_ugc_abc123xyz", "projectId": "proj_ugc_abc123xyz", "status": "STARTED" }, "message": "UGC ad successfully created" } ``` ```json Error Response - Validation Error theme={null} { "success": false, "message": "script: Script must be at least 1 character" } ``` ```json Error Response - Avatar Not Found theme={null} { "success": false, "message": "avatarId: Avatar \"invalid_id\" not found." } ``` *** ## Webhook Notification When your video is ready, we'll POST to your webhook URL: ```json Webhook Payload theme={null} { "status": "COMPLETED", "data": { "videoId": "vid_ugc_abc123xyz", "status": "COMPLETED", "url": "https://cdn.hooked.so/videos/abc123xyz.mp4", "shareUrl": "https://cdn.hooked.so/shared/abc123xyz.mp4", "metadata": { "projectId": "proj_ugc_abc123xyz", "campaignId": "Q1-2024-ugc", "abTestVariant": "B" } }, "message": "Video completed" } ``` Your webhook endpoint must return a `200` status code. We'll retry up to 3 times if the request fails. *** ## Caption Presets Available caption presets for the `caption.preset` field: | Preset | Description | | - | - | | `default` | Default caption style with bold text and shadow effects | | `beast` | Bold uppercase style with Komika font | | `umi` | Yellow glowing text style | | `tiktok` | Viral & trendy style, perfect for social media | | `wrap1` | Wrapped style with red background highlight | | `wrap2` | Wrapped style with blue background highlight (uppercase) | | `ariel` | Bold uppercase style with purple highlight | | `hooked` | Brand style with purple background | | `classic` | Clean, simple captions with black background (Default) | | `active` | Green background with bold text | | `bubble` | White background bubble style | | `glass` | Glassmorphic transparency effect | | `comic` | Comic Sans font with colorful style | | `glow` | Pink and orange glow effects | | `pastel` | Soft pastel pink background | | `neon` | Green neon glow effect | | `retroTV` | Retro TV style with cyan glow | | `red` | Red glow effect with white text | | `marker` | Yellow marker/highlighter style | | `modern` | Contemporary white background style | | `blue` | Blue background style | | `vivid` | Vibrant pink background with uppercase text | *** ## B-Roll Types Available B-roll display options for `adSettings.bRollType`: | Type | Description | | - | - | | `down` | Split screen down - Avatar on top half, B-roll on bottom half (Default) | | `up` | Split screen up - B-roll on top half, avatar on bottom half | | `left` | Split screen left - Avatar on right, B-roll on left - balanced layout | | `right` | Split screen right - Avatar on left, B-roll on right - balanced layout | | `rounded-cover` | Rounded - Rounded avatar overlay on B-roll background | | `full-width` | Full screen - B-roll covers entire screen, avatar hidden | *** ## Best Practices Scripts should sound conversational. Use contractions, pauses, and natural speech patterns. Start with an attention-grabbing hook in the first 2-3 seconds to stop scrolling. 15-60 seconds is ideal for social media ads. Get to the point quickly. Add product shots or demos to keep viewers engaged and showcase your product. *** ## Common Use Cases Create authentic-looking product review videos. ```json theme={null} { "name": "5-Star Review", "script": "I bought this not expecting much, but WOW! This exceeded all my expectations. The build quality is amazing and it works exactly as advertised. 10/10 would recommend!", "avatarId": "avatar_casual_01", "media": [ { "id": "product_review", "type": "video", "url": "https://example.com/review.mp4" } ], "caption": { "preset": "tiktok", "alignment": "bottom", "disabled": false } } ``` Showcase customer success stories. ```json theme={null} { "name": "Customer Testimonial", "script": "Before I found this product, I was struggling every day. Now? My life is completely different. I can't imagine going back to how things were before.", "avatarId": "avatar_professional_01", "media": ["testimonial_broll"], "caption": { "preset": "modern", "alignment": "bottom", "disabled": false }, "adSettings": { "removeAvatarBackground": true } } ``` Create engaging unboxing content. ```json theme={null} { "name": "Unboxing Experience", "script": "Okay so my package just arrived and I'm SO excited to open this with you! Let's see what's inside... Oh my gosh, look at this packaging! They really went all out!", "avatarId": "avatar_enthusiastic_01", "caption": { "preset": "modern", "alignment": "bottom", "disabled": false }, "media": ["unbox_1"], "adSettings": { "bRollType": "down" } } ``` *** ## Error Handling | Error | Description | Solution | | - | - | - | | `script: Script must be at least 1 character` | Missing or empty script | Add the `script` field with your ad content | | `avatarId: Avatar not found` | Invalid avatar ID | Use a valid avatar ID from `/v1/avatar/list` | | `webhook: Must be a valid HTTPS URL` | Invalid webhook URL | Ensure webhook URL starts with `https://` | | `media: Cannot have more than 50 media items` | Too many media items | Reduce media array to 50 items or fewer | | `Not enough credits` | Insufficient credits | Top up your account credits | *** ## Next Steps Browse available avatars for your ads Find the perfect voice for your content View all your created videos Learn how to handle webhook notifications # Video Webhook Source: https://docs.hooked.so/api-reference/video/webhook Receive real-time notifications when your video is ready ## Overview Instead of polling the [Get Video Details](/api-reference/video/details) endpoint, configure a webhook URL when creating a video to receive a notification when processing completes. **Recommended**: Always use webhooks for production applications. Polling is inefficient and can hit rate limits. ## Setting Up Webhooks Include a `webhook` URL in your video creation request: ```bash theme={null} curl -X POST "https://api.hooked.so/v1/project/create" \ -H "x-api-key: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "type": "class", "script": "Your video script here...", "avatarId": "avatar_sarah_01", "voiceId": "tzX5paJ07p5hyWFcU3uG", "webhook": "https://your-domain.com/api/webhook" }' ``` ## Webhook Payload When your video completes, Hooked sends a `POST` request to your webhook URL: ### Success ```json theme={null} { "event": "project.completed", "projectId": "proj_abc123", "status": "completed", "video": { "id": "video_xyz789", "url": "https://cdn.hooked.so/videos/xyz789.mp4", "duration": 45, "thumbnail": "https://cdn.hooked.so/thumbnails/xyz789.jpg", "aspectRatio": "9:16", "resolution": "1080x1920" }, "createdAt": "2024-01-15T10:30:00Z", "completedAt": "2024-01-15T10:33:00Z" } ``` ### Failure ```json theme={null} { "event": "project.failed", "projectId": "proj_abc123", "status": "failed", "message": "Script too short to generate video", "createdAt": "2024-01-15T10:30:00Z" } ``` ## Payload Fields | Field | Type | Description | | - | - | - | | `event` | string | `project.completed` or `project.failed` | | `projectId` | string | The project ID | | `status` | string | `completed` or `failed` | | `video` | object | Video details (only when completed) | | `video.url` | string | Download URL (expires in 7 days) | | `video.duration` | number | Duration in seconds | | `video.thumbnail` | string | Thumbnail image URL | | `message` | string | Error message (only when failed) | ## Implementation Example ```javascript theme={null} app.post('/api/webhook', express.json(), (req, res) => { const { event, projectId, status, video } = req.body; if (event === 'project.completed' && status === 'completed') { console.log('Video ready:', video.url); // Save URL, notify user, etc. } else if (event === 'project.failed') { console.error('Video failed:', req.body.message); } res.status(200).send('OK'); }); ``` ## Retry Policy If your endpoint doesn't return a `200` status, Hooked retries: | Attempt | Delay | | - | - | | 1st retry | 1 minute | | 2nd retry | 5 minutes | | 3rd retry | 15 minutes | After 3 failed attempts, no more retries are made. Always return `200 OK` immediately, even if your processing fails. This prevents unnecessary retries. ## Related In-depth webhook guide with security and testing Poll video status as an alternative View all your videos Handle errors gracefully # Video Workflow Source: https://docs.hooked.so/api-reference/video/workflow End-to-end video creation workflow with the Hooked API ## Overview The typical workflow for creating videos with Hooked involves four steps: fetching resources, creating a project, waiting for processing, and downloading the result. ## Workflow Steps Fetch available avatars, voices, and music to configure your video. ```bash theme={null} curl -X GET "https://api.hooked.so/v1/avatar/list" \ -H "x-api-key: your_api_key_here" curl -X GET "https://api.hooked.so/v1/voice/list?language=English" \ -H "x-api-key: your_api_key_here" curl -X GET "https://api.hooked.so/v1/music/list" \ -H "x-api-key: your_api_key_here" ``` Send a creation request with your script and selected resources. ```bash theme={null} curl -X POST "https://api.hooked.so/v1/project/create" \ -H "x-api-key: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "type": "class", "script": "Your video script here...", "avatarId": "avatar_sarah_01", "voiceId": "tzX5paJ07p5hyWFcU3uG", "webhook": "https://your-domain.com/webhook" }' ``` Video processing takes 2-5 minutes. Use a [webhook](/api-reference/video/webhook) or poll [video details](/api-reference/video/details). Once completed, download from the URL in the webhook payload or video details response. ## Two Approaches ### From Scratch Specify all parameters manually — avatar, voice, script, music, etc. Best for one-off videos. ### From Templates Use pre-configured templates with `{{variable}}` replacement. Best for personalized campaigns at scale. For the complete guide with code examples for both approaches, see [Complete Workflow](/guides/complete-workflow). ## Related Detailed guide with full code examples API reference for creating videos Handle completion notifications See complete code examples # List Voices Source: https://docs.hooked.so/api-reference/voice/list GET /v1/voice/list Get all available voices (ElevenLabs library + custom cloned voices) ## Overview This endpoint returns all voices available for text-to-speech, including ElevenLabs library voices and custom cloned voices. ### Query Parameters * `library`: Only ElevenLabs library voices * `custom`: Only custom cloned voices * `all`: Both library and custom voices (default) Filter by language (e.g., "English", "Spanish", "French") Filter by gender ("Male", "Female") Page size. Maximum `100`. Number of rows to skip. Combine with `limit` to page through the list; you are done when `offset + limit >= total`. ```bash cURL theme={null} curl -X GET "https://api.hooked.so/v1/voice/list?language=English&gender=Female" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.hooked.so/v1/voice/list?language=English&gender=Female', { headers: { 'x-api-key': 'your_api_key_here' } }); const { voices } = await response.json().data; ``` ```json Response theme={null} { "success": true, "data": { "voices": [ { "id": "1004", "voiceId": "tzX5paJ07p5hyWFcU3uG", "name": "Jude", "gender": "Male", "language": "English", "accent": "British", "country": "GB", "templateUrl": "https://cdn.hooked.so/voices/jude.mp3", "thumbnail": "/images/voice-default.png", "isCustom": false, "source": "library" } ], "total": 150 } } ``` # Authentication Source: https://docs.hooked.so/authentication Learn how to authenticate your API requests with Hooked ## API Key Authentication All API requests must be authenticated using an API key passed in the request headers. ### Getting Your API Key Log in to [hooked.so](https://hooked.so) Go to **Settings** → **API Keys** Click "Generate New API Key" and give it a descriptive name Copy and store your API key securely - you won't be able to see it again ## Try It in the Playground **You can test all API endpoints directly in this documentation!** Click on any endpoint (like [List Avatars](/api-reference/avatar/list)) and you'll see an **Authorization** field where you can enter your API key to make real requests. ### How to Use the API Playground: 1. Navigate to any API endpoint page 2. Look for the **Authorization** or **x-api-key** field at the top 3. Enter your API key 4. Modify the request parameters if needed 5. Click **"Send"** to see the real response Your API key is securely stored in your browser's localStorage, so you only need to enter it once per browser. Start with [List Avatars](/api-reference/avatar/list) - it's a simple GET request perfect for testing! ## Using Your API Key in Code Include your API key in the `x-api-key` header of every request: ```bash theme={null} curl -X GET "https://api.hooked.so/v1/avatar/list" \ -H "x-api-key: your_api_key_here" ``` ```javascript Node.js theme={null} const axios = require('axios'); const api = axios.create({ baseURL: 'https://api.hooked.so/v1', headers: { 'x-api-key': 'your_api_key_here', 'Content-Type': 'application/json' } }); // Example request const response = await api.get('/avatar/list'); console.log(response.data); ``` ```python Python theme={null} import requests API_KEY = "your_api_key_here" BASE_URL = "https://api.hooked.so/v1" headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } # Example request response = requests.get(f"{BASE_URL}/avatar/list", headers=headers) print(response.json()) ``` ```php PHP theme={null} ``` ```go Go theme={null} package main import ( "fmt" "io" "net/http" ) func main() { apiKey := "your_api_key_here" baseURL := "https://api.hooked.so/v1" client := &http.Client{} req, _ := http.NewRequest("GET", baseURL+"/avatar/list", nil) req.Header.Set("x-api-key", apiKey) req.Header.Set("Content-Type", "application/json") resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ## Security Best Practices **Never expose your API key in client-side code or public repositories.** ### Environment Variables Store your API key in environment variables: ```bash .env theme={null} HOOKED_API_KEY=your_api_key_here ``` ```javascript Node.js theme={null} require('dotenv').config(); const API_KEY = process.env.HOOKED_API_KEY; ``` ```python Python theme={null} import os from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv('HOOKED_API_KEY') ``` ### API Key Management For production applications, rotate your API keys every 90 days. Create a new key before deleting the old one to avoid downtime. Create separate API keys for development, staging, and production environments. We're adding support for IP whitelisting to further secure your API keys. Check your API usage in the dashboard to detect any unusual activity. ## Error Responses If authentication fails, you'll receive a `401 Unauthorized` response: ```json theme={null} { "success": false, "message": "Authentication required. Please provide a valid API key in the x-api-key header." } ``` ### Common Authentication Errors | Error | Cause | Solution | | - | - | - | | `401 Unauthorized` | Missing or invalid API key | Check that your key is correct and included in headers | | `403 Forbidden` | API key doesn't have required permissions | Verify your subscription plan includes API access | | `429 Too Many Requests` | Rate limit exceeded | Wait before making more requests or upgrade your plan | ## Rate Limiting API requests are rate-limited based on your subscription plan: | Plan | Rate Limit | Burst Limit | | - | - | - | | Free | 10 requests/minute | 20 requests | | Starter | 60 requests/minute | 100 requests | | Pro | 300 requests/minute | 500 requests | | Premium | 1000 requests/minute | 2000 requests | ### Rate Limit Headers Each response includes rate limit information in the headers: ``` X-RateLimit-Limit: 60 X-RateLimit-Remaining: 45 X-RateLimit-Reset: 1642416000 ``` ### Handling Rate Limits Implement exponential backoff when you receive a `429` response: ```javascript theme={null} async function makeRequestWithRetry(url, options, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i); await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); continue; } return response; } catch (error) { if (i === maxRetries - 1) throw error; } } } ``` ## Testing Your Authentication Test your API key with a simple request: ```bash theme={null} curl -X GET "https://api.hooked.so/v1/avatar/list?type=library" \ -H "x-api-key: your_api_key_here" ``` If you receive a successful response with avatar data, your authentication is working correctly! ## Need Help? Manage your API keys Contact our support team # Add Captions Example Source: https://docs.hooked.so/examples/add-captions Add automatic captions to videos with customizable styles and positioning ## Overview Add Captions automatically transcribes your video and adds professional captions. Perfect for social media content, accessibility, and increasing engagement. ## Basic Example ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/add-captions', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ media: 'media_abc123' }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ## With Custom Caption Style ```javascript theme={null} const addCaptionsStyled = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/add-captions', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Tutorial Video with Captions', media: 'media_tutorial_video', addStickers: false, caption: { preset: 'beast', alignment: 'bottom' } }) }); return await response.json(); }; ``` ## With Background Music ```javascript theme={null} const addCaptionsWithMusic = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/add-captions', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Product Demo with Music', media: 'media_product_demo', addStickers: false, caption: { preset: 'modern', alignment: 'bottom' }, musicId: 'upbeat_001', webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'product-launch-2024', platform: 'tiktok' } }) }); return await response.json(); }; ``` *** ## Tips for Add Captions **Clear audio**: Ensure your video has clear, high-quality audio for best transcription accuracy **Style selection**: Choose caption presets that match your content style and target audience **Bottom alignment**: For social media, bottom alignment typically performs best **Test different presets**: Try various caption styles to see what resonates with your audience *** ## Aspect Ratio Guide Choose the right aspect ratio for your platform: | Platform | Aspect Ratio | Value | | - | - | - | | TikTok, Instagram Reels, YouTube Shorts | Vertical | `ratio_9_16` | | YouTube, LinkedIn | Horizontal | `ratio_16_9` | | Instagram Feed | Square | `ratio_1_1` | *** ## Handling the Webhook Response ```javascript theme={null} // Express.js webhook handler app.post('/webhook', (req, res) => { const { data, status, message } = req.body; if (status === "COMPLETED") { const { videoId, url, shareUrl } = data; console.log('Captions added successfully!'); console.log('Video ID:', videoId); console.log('Download URL:', url); console.log('Share URL:', shareUrl); // Save to your database, notify team, etc. } else if (status === "FAILED") { console.error('Caption generation failed:', message); // Handle failure, notify user, retry, etc. } res.status(200).send('OK'); }); ``` *** ## Common Use Cases * **Social Media Content**: Add captions to make videos more engaging and accessible * **Educational Videos**: Help learners follow along with automatic captions * **Accessibility**: Make content accessible to deaf and hard-of-hearing viewers * **Multi-Language**: Transcribe videos in different languages for global audiences * **SEO**: Improve video SEO with searchable caption content * **Silent Viewing**: Enable viewers to watch without sound in public places *** # Class Video Source: https://docs.hooked.so/examples/class-video Create an educational class-style video with an AI avatar presenter ## Overview Class videos are educational-style videos where an AI avatar presents content with a script. They're ideal for tutorials, onboarding, and course content. ## Quick Example ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create', { method: 'POST', headers: { 'x-api-key': 'your_api_key_here', 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'class', name: 'Product Tutorial', script: `Hello! Welcome to this tutorial. Today, I will show you how to use our product effectively. Let me walk you through the key features step by step.`, avatarId: 'avatar_sarah_01', voiceId: 'tzX5paJ07p5hyWFcU3uG', aspectRatio: 'ratio_9_16', captions: true, webhook: 'https://your-domain.com/webhook' }) }); const data = await response.json(); console.log('Project ID:', data.data.projectId); ``` ## Step-by-Step Fetch available avatars and voices. ```bash theme={null} curl -X GET "https://api.hooked.so/v1/avatar/list" \ -H "x-api-key: your_api_key_here" curl -X GET "https://api.hooked.so/v1/voice/list?language=English" \ -H "x-api-key: your_api_key_here" ``` ```bash theme={null} curl -X POST "https://api.hooked.so/v1/project/create" \ -H "x-api-key: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "type": "class", "name": "My Tutorial", "script": "Hello! Welcome to this tutorial. Today I will show you how to use our product.", "avatarId": "avatar_sarah_01", "voiceId": "tzX5paJ07p5hyWFcU3uG", "aspectRatio": "ratio_9_16", "captions": true, "webhook": "https://your-domain.com/webhook" }' ``` When the video finishes processing (typically 2-5 minutes), your webhook receives: ```json theme={null} { "event": "project.completed", "projectId": "proj_abc123", "status": "completed", "video": { "url": "https://cdn.hooked.so/videos/xyz789.mp4", "duration": 45, "thumbnail": "https://cdn.hooked.so/thumbnails/xyz789.jpg" } } ``` ## Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `type` | string | Yes | Must be `"class"` | | `name` | string | No | Video title | | `script` | string | Yes | The narration text (10-5000 characters) | | `avatarId` | string | Yes | Avatar to use as presenter | | `voiceId` | string | No | ElevenLabs voice for narration. Only needed when a scene has no avatar: an avatar speaks the script itself. | | `aspectRatio` | string | No | `ratio_9_16` (default) or `ratio_16_9` | | `captions` | boolean | No | Enable auto-generated captions | | `music` | string | No | Background music track ID | | `webhook` | string | No | URL for completion notification | ## Tips **Write natural scripts**: Use conversational language with natural pauses (commas, periods) for more authentic delivery. **Match voice to avatar**: Select voices that match the avatar's gender and age for the best results. ## Related Get started in 5 minutes UGC-style talking avatar videos Generate videos from scripts Handle completion notifications # Extend Video Example Source: https://docs.hooked.so/examples/extend-video Extend videos using AI to continue and extend your content seamlessly ## Overview Extend Video uses Veo 3.1 AI to intelligently continue and extend your videos. Perfect for creating longer content, extending storytelling sequences, and optimizing video length. ## Basic Extension (8 seconds) ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/extend-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Extended Story Video', media: 'media_video123', prompt: 'The camera slowly zooms out revealing a beautiful sunset over the ocean', targetDuration: 8, generateAudio: true }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ## Medium Extension (16 seconds) ```javascript theme={null} const extendVideoMedium = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/extend-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Extended Story Video', media: 'media_product_demo', prompt: 'The product rotates smoothly as the background transitions from blue to purple with subtle particle effects', targetDuration: 16, generateAudio: true, webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'q1-product-launch', platform: 'youtube', targetAudience: 'tech-enthusiasts' } }) }); return await response.json(); }; ``` ## Long Extension (32 seconds) ```javascript theme={null} const extendVideoLong = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/extend-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Extended Story Video', media: 'media_story_video', prompt: 'The scene continues with smooth camera movement, transitioning through different environments while maintaining visual consistency', targetDuration: 32, // Processed in 4 segments of 8 seconds generateAudio: true, webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'storytelling-2024', type: 'narrative' } }) }); return await response.json(); }; ``` ## Marketing Video Extension ```javascript theme={null} const extendMarketingVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/extend-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Extended Story Video', media: 'media_ad_clip', prompt: 'The camera pulls back to show the full product lineup as the brand logo fades in with a golden glow effect', targetDuration: 12, generateAudio: true, webhook: 'https://yoursite.com/webhook/video-complete', metadata: { campaignId: 'q1-product-launch', platform: 'youtube', targetAudience: 'tech-enthusiasts' } }) }); return await response.json(); }; ``` *** ## Tips for Extend Video **Specific prompts**: Be detailed about camera movement, lighting changes, and object behavior **Match the source**: Reference the style and mood of your source video in the prompt **Start small**: Test with 8-16 seconds first, then scale to longer durations **Audio consistency**: Enable generateAudio for smooth audio transitions **Duration multiples**: Target duration must be a multiple of 4 seconds *** ## Duration Guide Choose the right extension duration for your use case: | Duration | Use Case | Processing Time | | - | - | - | | 4-8s | Quick transitions, social clips | 2-3 minutes | | 12-16s | Short scene extensions | 4-6 minutes | | 20-32s | Medium storytelling sequences | 8-12 minutes | | 36-60s | Long-form content extensions | 15-20 minutes | *** ## Handling the Webhook Response ```javascript theme={null} // Express.js webhook handler app.post('/webhook', (req, res) => { const { data, status, message } = req.body; if (status === "COMPLETED") { const { videoId, url, shareUrl, metadata } = data; console.log('Video extended successfully!'); console.log('Video ID:', videoId); console.log('Download URL:', url); console.log('Share URL:', shareUrl); console.log('Original Duration:', metadata.originalDuration); console.log('Extended Duration:', metadata.extendedDuration); // Save to your database, notify user, etc. } else if (status === "FAILED") { console.error('Video extension failed:', message); // Handle failure, notify user, retry, etc. } res.status(200).send('OK'); }); ``` *** ## Common Use Cases * **Content Creation**: Extend short clips into longer, more engaging content * **Storytelling**: Continue narrative sequences with AI-generated footage * **Marketing**: Create extended product demos and promotional videos * **Social Media**: Optimize video length for platform requirements * **B-Roll Generation**: Generate additional footage that matches your style * **Video Loop Creation**: Extend videos to create seamless loops *** ## Prompt Examples by Category ### Camera Movement * "The camera continues to dolly backward, revealing the full scene as it maintains focus on the subject" * "Smooth pan to the right, discovering new elements in the environment" * "Gentle zoom in on the product while maintaining the same lighting" ### Transitions * "The scene gradually transitions from day to night with natural lighting changes" * "Soft cross-fade into a new environment while maintaining visual continuity" * "The background shifts from warm tones to cool tones over the duration" ### Action Continuation * "The dancer completes the spin and strikes a final pose with arms extended" * "The product rotates a full 360 degrees, showcasing all angles" * "The character walks toward the horizon as the sun sets behind them" ### Atmospheric Effects * "Particles begin to float upward as the lighting becomes more dramatic" * "Rain starts falling gently as the mood becomes more contemplative" * "Fog rolls in from the sides, creating a mysterious atmosphere" # Hook + Demo Example Source: https://docs.hooked.so/examples/hook-demo Create attention-grabbing videos with text hooks and demo media ## Overview Hook + Demo videos combine compelling text hooks with background media to create scroll-stopping content. Perfect for social media marketing, product teasers, and viral content. ## Quick Example ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/hook-demo', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ avatarId: 6, name: 'Product Teaser Hook', text: 'Wait for it... This changed EVERYTHING', media: ['demo_video_1'] }) }); const data = await response.json(); console.log('Project ID:', data.projectId); ``` ## Complete Example with All Options ```javascript theme={null} const createHookDemo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/hook-demo', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ avatarId: 6, name: 'Viral Product Hook', text: 'POV: You just discovered the hack everyone is talking about', textSettings: { preset: 'impact', alignment: 'top', textColor: '#00ff88', backgroundColor: 'transparent', disabled: false }, media: ['media_id','media_id_2'], aspectRatio: 'ratio_9_16', musicId: 'your-custom-music-id', webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'summer-launch-2024', variant: 'A', createdBy: 'marketing-automation' } }) }); return await response.json(); }; ``` ## Tips for Hook + Demo Videos **Hook in first 2 seconds**: Your text should grab attention immediately **Use action words**: "Wait for it", "Watch this", "POV:" work great **Match media to message**: The demo should deliver on the hook's promise **Keep it vertical**: 9:16 ratio performs best on TikTok, Reels, and Shorts ## Effective Hook Formulas | Formula | Example | | - | - | | **POV:** | "POV: You just found the best productivity hack" | | **Wait for it...** | "Wait for it... This will blow your mind" | | **X things...** | "3 things millionaires do every morning" | | **This is why...** | "This is why your skin isn't clearing up" | | **Stop scrolling if...** | "Stop scrolling if you want to save money" | ## Use Cases * **Product Teasers**: Build anticipation for launches * **Social Proof**: Showcase results or testimonials * **Tutorial Hooks**: Tease how-to content * **Behind the Scenes**: Reveal interesting processes * **Before/After**: Show transformations ## Handling the Webhook Response ```javascript theme={null} // Express.js webhook handler app.post('/webhook', (req, res) => { const { data, status, message} = req.body; if (status === "COMPLETED") { const { videoId, status: statusVideo, url, shareUrl, metadata } = data; console.log('Video status!', statusVideo); console.log('Download URL:', url); console.log('share Url:', shareUrl); console.log('Campaign:', metadata.campaignId); // Save to your database, notify team, etc. } res.status(200).send('OK'); }); ``` # Prompt to Video Example Source: https://docs.hooked.so/examples/prompt-to-video Create videos from prompts with AI-generated scripts and media ## Overview Transform text prompts into engaging videos with AI-generated scripts, voice narration and auto-generated or custom visuals. Prompt to Video is perfect for quick content creation, educational videos, social media content, and marketing materials without writing scripts. ## With AI-Generated Images ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/prompt-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Create an engaging video about the top 5 productivity tips for remote workers. Include practical advice and make it motivational.', voiceId: 'confident_voice_id', targetDuration: 60, mediaType: "ai-images", presetSettings: { preset: "realistic", quality: "pro" }, musicId: "2", aspectRatio: 'ratio_9_16', caption: { preset: 'beast', alignment: 'bottom', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'productivity' } }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ## With AI-Generated Videos ```javascript theme={null} const createAIVideoFromPrompt = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/prompt-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'AI Technology Explainer', prompt: 'Explain how artificial intelligence is transforming the way we work and live. Cover smart assistants, self-driving cars, and future applications.', voiceId: 'confident_voice_id', targetDuration: 60, mediaType: 'ai-videos', presetSettings: { preset: 'anime', quality: 'base' }, musicId: 'music_ambient_01', aspectRatio: 'ratio_9_16', caption: { preset: 'modern', alignment: 'bottom', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'artificial-intelligence' } }) }); return await response.json(); }; ``` ## With Custom Media ```javascript theme={null} const createCustomMediaVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/prompt-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Product Showcase', prompt: 'Create a compelling product introduction video. Highlight the revolutionary features that make life easier and more productive.', voiceId: 'enthusiastic_voice_id', targetDuration: 45, mediaType: 'media' media: ['intro_shot', 'demo_shot', 'feature_image'], musicId: 'music_upbeat_01', aspectRatio: 'ratio_9_16', caption: { preset: 'wrap1', alignment: 'bottom', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'marketing', topic: 'product-launch' } }) }); return await response.json(); }; ``` ## With Gameplay ```javascript theme={null} const createGamingVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/prompt-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Gaming Tips Video', prompt: 'Create a video with three pro tips to level up gaming skills and dominate opponents. Make it energetic and engaging for gamers.', voiceId: 'energetic_voice_id', targetDuration: 45, mediaType: 'GAMEPLAY', gameplaySettings: { selectedGame: 'minecraft', selectedVideo: 'parkour_gameplay_01' }, musicId: 'music_upbeat_01', aspectRatio: 'ratio_9_16', caption: { preset: 'beast', alignment: 'top', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'gaming', topic: 'tips' } }) }); return await response.json(); }; ``` ## Tips for Prompt to Video **Be specific**: Include topic, tone, and target audience in your prompt for better script generation **Match media to content**: Use AI\_IMAGES for explainers, GAMEPLAY for gaming, custom media for brands **Choose duration wisely**: 30s for quick tips, 60s for standard content, 90s for comprehensive guides, 120s for extended content **Choose the right voice**: Match voice tone to your content - energetic for gaming, professional for business **Use background music**: Add music to enhance engagement without overpowering the narration ## Visual Style Presets Available presets for the `presetSettings.preset` field (used with `ai-images` and `ai-videos` media types): | Preset | Description | | - | - | | `realistic` | Pure photorealistic style with natural lighting and lifelike details | | `anime` | Classic anime style with large expressive eyes, vibrant colors, clean line art | | `cinematic` | Hollywood movie cinematography with dramatic lighting and wide shots | | `fantasy` | Epic fantasy art with magical elements, mythical creatures, enchanted environments | | `cyberpunk-anime` | Futuristic anime style with neon colors, cybernetic elements, dystopian atmosphere | | `pixar` | 3D animated style inspired by Pixar with smooth rendering and expressive characters | | `comic-book` | American comic book style with bold colors, dynamic action, superhero aesthetics | | `real-anime` | Realistic anime style blending photorealistic elements with anime aesthetics | | `ghibli-studio` | Studio Ghibli animation style with soft colors, whimsical characters, magical atmosphere | | `sketch-black-and-white` | Monochrome pencil sketch with detailed shading and artistic line work | | `art-style` | Fine art style with painterly techniques, artistic composition, classical aesthetics | | `retro-anime` | 1980s-90s anime aesthetic with vintage color palette and classic animation style | | `80s-fantasy-movie` | Retro 80s fantasy film aesthetic with practical effects, vibrant colors | | `cartoon` | Classic cartoon style with bold outlines, flat colors, exaggerated features | | `creative` | Abstract creative style with experimental techniques and innovative artistic approaches | | `gta-v` | Grand Theft Auto V video game style with urban aesthetic and satirical tone | | `sketch-color` | Colored sketch with vibrant markers and artistic illustration techniques | | `japanese-ink` | Traditional Japanese sumi-e ink painting with black ink, red accents, flowing brushstrokes | | `space-marines-40k` | Warhammer 40K Space Marines style with power armor, gothic architecture, grimdark atmosphere | | `haunted-linework` | Gothic horror style with intricate linework, dark atmosphere, supernatural elements | | `ink-style` | Modern ink art with dynamic strokes, abstract elements, energetic composition | | `neon-futuristic` | Cyberpunk aesthetic with bright neon colors, futuristic technology, sci-fi atmosphere | | `minecraft` | Blocky pixel art style inspired by Minecraft with cubic shapes and vibrant colors | | `pixel-art` | 8-bit and 16-bit pixel art style with retro gaming aesthetics and limited color palette | | `collage` | Mixed media collage with newspaper elements, geometric shapes, layered textures | | `lego` | LEGO brick style with plastic toy aesthetic and modular construction elements | | `technical-blueprints` | Engineering blueprint style with technical drawings, measurements, schematic details | ## Media Types | Type | Best For | | - | - | | `ai-images` | Educational content, explainers, storytelling | | `ai-videos` | Dynamic content, trailers, teasers | | `media` | Stock footage for generic content | | `gameplay` | Gaming tips, tutorials, compilations | | Custom `media` | Brand content, product showcases | ## Caption Presets Available caption presets for the `caption.preset` field: | Preset | Description | | - | - | | `default` | Default caption style with bold text and shadow effects | | `beast` | Bold uppercase style with Komika font | | `umi` | Yellow glowing text style | | `tiktok` | Viral & trendy style, perfect for social media | | `wrap1` | Wrapped style with red background highlight | | `wrap2` | Wrapped style with blue background highlight (uppercase) | | `ariel` | Bold uppercase style with purple highlight | | `hooked` | Brand style with purple background | | `classic` | Clean, simple captions with black background (Default) | | `active` | Green background with bold text | | `bubble` | White background bubble style | | `glass` | Glassmorphic transparency effect | | `comic` | Comic Sans font with colorful style | | `glow` | Pink and orange glow effects | | `pastel` | Soft pastel pink background | | `neon` | Green neon glow effect | | `retroTV` | Retro TV style with cyan glow | | `red` | Red glow effect with white text | | `marker` | Yellow marker/highlighter style | | `modern` | Contemporary white background style | | `blue` | Blue background style | | `vivid` | Vibrant pink background with uppercase text | *** ## Target Duration Guide | Duration | Use Case | Processing Time | | - | - | - | | 30s | Quick tips, single facts, viral hooks | 2-4 minutes | | 45s | Short tutorials, product highlights | 3-5 minutes | | 60s | Standard explainers, listicles | 4-6 minutes | | 90s | Comprehensive guides, deep dives | 6-10 minutes | | 120s | Extended content, detailed tutorials | 8-15 minutes | *** ## Use Cases * **Quick Content Creation**: Generate videos from simple descriptions without writing scripts * **Educational Content**: Explainers, tutorials, how-to videos * **Social Media**: TikTok, Instagram Reels, YouTube Shorts * **Marketing**: Product showcases, brand stories, promotions * **Gaming**: Tips, tutorials, compilations * **News & Updates**: Company announcements, industry updates ## Handling the Webhook Response ```javascript theme={null} // Express.js webhook handler app.post('/webhook', (req, res) => { const { data, status, message } = req.body; if (status === "COMPLETED") { const { videoId, status: videoStatus, url, shareUrl, metadata } = data; console.log('Video completed!'); console.log('Video ID:', videoId); console.log('Download URL:', url); console.log('Share URL:', shareUrl); console.log('Content Type:', metadata.contentType); // Save to your database, notify team, etc. } res.status(200).send('OK'); }); ``` # Remove Background Example Source: https://docs.hooked.so/examples/remove-background Remove backgrounds from videos ## Overview Remove the background from any video to create professional, overlay-ready content. Perfect for product demos, presentations, and social media content. ## Basic Background Removal ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/remove-background', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ media: 'video_01' }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ## Complete Background Removal ```javascript theme={null} const createProductVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/remove-background', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ media: 'product_demo', webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'summer-launch-2024', variant: 'A', createdBy: 'marketing-automation' } }) }); return await response.json(); }; ``` ## Tips for Background Removal **Use good lighting**: Videos with clear, even lighting produce the best results **Clear separation**: Ensure good contrast between subject and background **Avoid motion blur**: Sharper videos work better for background removal **High quality source**: Start with the highest quality video possible **Test different videos**: Some videos work better than others - experiment! *** ## Common Use Cases * **E-commerce**: Create clean product videos without distracting backgrounds * **Education**: Professional-looking instructor videos * **Marketing**: Eye-catching social media content * **Corporate**: Professional presentations and announcements * **Content Creation**: Overlay creators on custom backgrounds ## Handling the Webhook Response ```javascript theme={null} // Express.js webhook handler app.post('/webhook', (req, res) => { const { data, status, message } = req.body; if (status === "COMPLETED") { const { videoId, status: videoStatus, url, shareUrl } = data; console.log('Video completed!'); console.log('Video ID:', videoId); console.log('Download URL:', url); console.log('Share URL:', shareUrl); // Save to your database, notify team, etc. } res.status(200).send('OK'); }); ``` # Scenes Example Source: https://docs.hooked.so/examples/scenes Create multi-scene videos combining avatars, video clips, and AI-generated transitions ## Overview Scenes allows you to create complex, multi-scene videos by combining different content types in a storyboard format. Perfect for tutorials, marketing videos, educational content, and storytelling with multiple segments. ## Basic Multi-Scene Video ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/scenes', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Getting Started Tutorial', scenes: [ { type: 'video', script: 'Welcome to our product demo! Let me show you how it works.', mediaId: 'product_demo_video_id' }, { type: 'video', script: 'Welcome to our product demo! Let me show you how it works.', mediaId: 'product_demo_video_id', voiceId: 'warm_voice_id' } ] }) }); const data = await response.json(); console.log('Project ID:', data.projectId); ``` ## Tutorial with Picture in Picture ```javascript theme={null} const createTutorial = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/scenes', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Getting Started Tutorial', scenes: [ { type: 'picture_in_picture', script: 'First, click on the settings icon in the top right corner.', avatarId: 'friendly_avatar_id', voiceId: 'warm_voice_id', mediaId: 'settings_screenshot_id' }, { type: 'picture_in_picture', script: 'Then, navigate to the integrations tab and enable the API access.', avatarId: 'friendly_avatar_id', voiceId: 'warm_voice_id', mediaId: 'integrations_screenshot_id' } ], musicId: 'upbeat_background_id', aspectRatio: 'ratio_16_9', caption: { preset: 'modern', alignment: 'bottom', disabled: false }, language: 'en', addStickers: false, webhook: 'https://yoursite.com/webhook', metadata: { tutorialId: 'getting-started', version: '1.0' } }) }); return await response.json(); }; ``` ## AI Transition Video ```javascript theme={null} const createTransitionVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/scenes', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Getting Started Tutorial', scenes: [ { type: 'avatar', script: 'Watch this amazing transformation!', avatarId: 'energetic_avatar_id', voiceId: 'excited_voice_id' }, { type: 'start_end_frame', startFrameMediaId: 'before_image_id', endFrameMediaId: 'after_image_id', frameModel: 'veo_3_1', framePrompt: 'Smooth morphing transition from the before state to the after state with elegant camera movement', frameDuration: 5 }, { type: 'avatar', script: 'Incredible, right? This is the power of our solution.', avatarId: 'energetic_avatar_id', voiceId: 'excited_voice_id' } ], musicId: 'upbeat_background_id', aspectRatio: 'ratio_16_9', caption: { preset: 'modern', alignment: 'bottom', disabled: false }, language: 'en', addStickers: false, webhook: 'https://yoursite.com/webhook', metadata: { tutorialId: 'getting-started', version: '1.0' } }) }); return await response.json(); }; ``` ## Tips for Scenes Videos **Plan Your Storyboard**: Outline your scenes before creating. Each scene should serve a clear purpose in the narrative. **Consistent Avatar**: Use the same avatar across scenes for continuity, unless intentionally switching perspectives. **Vary Scene Types**: Mix avatar, video, and PiP scenes to keep viewers engaged and add visual variety. **Smooth Transitions**: Write scripts that flow naturally between scenes. End one scene leading into the next. **Optimize Duration**: Keep individual scenes focused. Split long content into multiple shorter scenes. **Use AI Transitions**: Leverage start/end frame scenes for impressive visual effects between static images. ## Scene Types | Type | Description | Required Fields | | - | - | - | | `avatar` | Talking avatar scene | `script`, `avatarId`, `voiceId` | | `video` | Video/image clip | `mediaId` | | `picture_in_picture` | Avatar over video/image | `script`, `avatarId`, `voiceId`, `mediaId` | | `start_end_frame` | AI-generated transition | `startFrameMediaId`, `endFrameMediaId`, `framePrompt`, `frameModel`, `frameDuration` | ## AI Models for Transitions | Model | Description | Best For | | - | - | - | | `kling_2_5` | Kling 2.5 model | Fast generation, general transitions | | `veo_3_1` | Google Veo 3.1 | High quality, complex camera movements | *** ## Use Cases * **Tutorials**: Combine avatar explanations with screen recordings and demos * **Marketing Videos**: Mix talking head with product shots and lifestyle footage * **Educational Content**: Alternate between instructor and visual demonstrations * **Storytelling**: Create narrative videos with multiple scenes and perspectives * **Before/After**: Use AI transitions to show transformations dramatically ## Handling the Webhook Response ```javascript theme={null} // Express.js webhook handler app.post('/webhook', (req, res) => { const { data, status, message } = req.body; if (status === "COMPLETED") { const { videoId, status: videoStatus, url, shareUrl, metadata } = data; console.log('Scenes video completed!'); console.log('Video ID:', videoId); console.log('Download URL:', url); console.log('Share URL:', shareUrl); console.log('Tutorial ID:', metadata.tutorialId); // Save to your database, notify team, etc. } res.status(200).send('OK'); }); ``` # Script to Video Example Source: https://docs.hooked.so/examples/script-to-video Create videos from scripts with AI-generated or custom media ## Overview Transform written scripts into engaging videos with voice narration and auto-generated or custom visuals. Script to Video is perfect for educational content, social media videos, and marketing materials. ## With AI-Generated Images ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/script-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ script: 'Artificial intelligence is transforming the way we work and live. From smart assistants to self-driving cars, AI is everywhere.', voiceId: 'confident_voice_id', mediaType: "ai-images", presetSettings: { preset: "realistic", quality: "pro" }, musicId: "2", aspectRatio: 'ratio_9_16', caption: { preset: 'beast', alignment: 'bottom', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'artificial-intelligence' } }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ## With AI-Generated Videos ```javascript theme={null} const createAIImageVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/script-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'AI Technology Explainer', script: 'Artificial intelligence is transforming the way we work and live. From smart assistants to self-driving cars, AI is everywhere. Let\'s explore how this technology is shaping our future.', voiceId: 'confident_voice_id', mediaType: 'ai-videos', presetSettings: { preset: 'anime', quality: 'base' }, musicId: 'music_ambient_01', aspectRatio: 'ratio_9_16', caption: { preset: 'modern', alignment: 'bottom', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'artificial-intelligence' } }) }); return await response.json(); }; ``` ## With Custom Media ```javascript theme={null} const createCustomMediaVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/script-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Product Showcase', script: 'Introducing our revolutionary new product. It\'s designed to make your life easier and more productive. Watch as we demonstrate its incredible features.', voiceId: 'enthusiastic_voice_id', media: ['intro_shot', 'demo_shot', 'feature_image'], musicId: 'music_upbeat_01', aspectRatio: 'ratio_9_16', caption: { preset: 'wrap1', alignment: 'bottom', disabled: false }, mediaType: 'media', addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'artificial-intelligence' } }) }); return await response.json(); }; ``` ## With Gameplay ```javascript theme={null} const createGamingVideo = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/script-to-video', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Gaming Tips Video', script: 'Want to level up your gaming skills? Here are three pro tips that will help you dominate your opponents. Tip one: always check your surroundings...', voiceId: 'energetic_voice_id', mediaType: 'gameplay', gameplaySettings: { selectedGame: 'minecraft', selectedVideo: 'parkour_gameplay_01' }, musicId: 'music_upbeat_01', aspectRatio: 'ratio_9_16', caption: { preset: 'beast', alignment: 'top', disabled: false }, addStickers: true, audio: { speed: 1, stability: 0.5, similarityBoost: 0.75, style: 0, useSpeakerBoost: true }, webhook: 'https://yoursite.com/webhook', metadata: { contentType: 'educational', topic: 'artificial-intelligence' } }) }); return await response.json(); }; ``` ## Tips for Script to Video **Structure your script**: Break content into clear sections with natural pauses for better pacing **Match media to content**: Use AI\_IMAGES for explainers, GAMEPLAY for gaming, custom media for brands **Keep it concise**: 30-90 seconds works best for social media content **Choose the right voice**: Match voice tone to your content - energetic for gaming, professional for business **Use background music**: Add music to enhance engagement without overpowering the narration ## Visual Style Presets Available presets for the `presetSettings.preset` field (used with `ai-images` and `ai-videos` media types): | Preset | Description | | - | - | | `realistic` | Pure photorealistic style with natural lighting and lifelike details | | `anime` | Classic anime style with large expressive eyes, vibrant colors, clean line art | | `cinematic` | Hollywood movie cinematography with dramatic lighting and wide shots | | `fantasy` | Epic fantasy art with magical elements, mythical creatures, enchanted environments | | `cyberpunk-anime` | Futuristic anime style with neon colors, cybernetic elements, dystopian atmosphere | | `pixar` | 3D animated style inspired by Pixar with smooth rendering and expressive characters | | `comic-book` | American comic book style with bold colors, dynamic action, superhero aesthetics | | `real-anime` | Realistic anime style blending photorealistic elements with anime aesthetics | | `ghibli-studio` | Studio Ghibli animation style with soft colors, whimsical characters, magical atmosphere | | `sketch-black-and-white` | Monochrome pencil sketch with detailed shading and artistic line work | | `art-style` | Fine art style with painterly techniques, artistic composition, classical aesthetics | | `retro-anime` | 1980s-90s anime aesthetic with vintage color palette and classic animation style | | `80s-fantasy-movie` | Retro 80s fantasy film aesthetic with practical effects, vibrant colors | | `cartoon` | Classic cartoon style with bold outlines, flat colors, exaggerated features | | `creative` | Abstract creative style with experimental techniques and innovative artistic approaches | | `gta-v` | Grand Theft Auto V video game style with urban aesthetic and satirical tone | | `sketch-color` | Colored sketch with vibrant markers and artistic illustration techniques | | `japanese-ink` | Traditional Japanese sumi-e ink painting with black ink, red accents, flowing brushstrokes | | `space-marines-40k` | Warhammer 40K Space Marines style with power armor, gothic architecture, grimdark atmosphere | | `haunted-linework` | Gothic horror style with intricate linework, dark atmosphere, supernatural elements | | `ink-style` | Modern ink art with dynamic strokes, abstract elements, energetic composition | | `neon-futuristic` | Cyberpunk aesthetic with bright neon colors, futuristic technology, sci-fi atmosphere | | `minecraft` | Blocky pixel art style inspired by Minecraft with cubic shapes and vibrant colors | | `pixel-art` | 8-bit and 16-bit pixel art style with retro gaming aesthetics and limited color palette | | `collage` | Mixed media collage with newspaper elements, geometric shapes, layered textures | | `lego` | LEGO brick style with plastic toy aesthetic and modular construction elements | | `technical-blueprints` | Engineering blueprint style with technical drawings, measurements, schematic details | ## Media Types | Type | Best For | | - | - | | `ai-images` | Educational content, explainers, storytelling | | `ai-videos` | Dynamic content, trailers, teasers | | `media` | Stock footage for generic content | | `gameplay` | Gaming tips, tutorials, compilations | | Custom `media` | Brand content, product showcases | ## Caption Presets Available caption presets for the `caption.preset` field: | Preset | Description | | - | - | | `default` | Default caption style with bold text and shadow effects | | `beast` | Bold uppercase style with Komika font | | `umi` | Yellow glowing text style | | `tiktok` | Viral & trendy style, perfect for social media | | `wrap1` | Wrapped style with red background highlight | | `wrap2` | Wrapped style with blue background highlight (uppercase) | | `ariel` | Bold uppercase style with purple highlight | | `hooked` | Brand style with purple background | | `classic` | Clean, simple captions with black background (Default) | | `active` | Green background with bold text | | `bubble` | White background bubble style | | `glass` | Glassmorphic transparency effect | | `comic` | Comic Sans font with colorful style | | `glow` | Pink and orange glow effects | | `pastel` | Soft pastel pink background | | `neon` | Green neon glow effect | | `retroTV` | Retro TV style with cyan glow | | `red` | Red glow effect with white text | | `marker` | Yellow marker/highlighter style | | `modern` | Contemporary white background style | | `blue` | Blue background style | | `vivid` | Vibrant pink background with uppercase text | *** ## Use Cases * **Educational Content**: Explainers, tutorials, how-to videos * **Social Media**: TikTok, Instagram Reels, YouTube Shorts * **Marketing**: Product showcases, brand stories, promotions * **Gaming**: Tips, tutorials, compilations * **News & Updates**: Company announcements, industry updates ## Handling the Webhook Response ```javascript theme={null} // Express.js webhook handler app.post('/webhook', (req, res) => { const { data, status, message } = req.body; if (status === "COMPLETED") { const { videoId, status: videoStatus, url, shareUrl, metadata } = data; console.log('Video completed!'); console.log('Video ID:', videoId); console.log('Download URL:', url); console.log('Share URL:', shareUrl); console.log('Content Type:', metadata.contentType); // Save to your database, notify team, etc. } res.status(200).send('OK'); }); ``` # Talking Avatar Example Source: https://docs.hooked.so/examples/talking-avatar Create talking avatar videos with AI avatars ## Overview Create videos of an AI avatar speaking your script. Voice, lip movement and gestures are generated natively by the video model: you choose the avatar and write the script, and there is no `voiceId` or lipsync model to configure. ## Quick Example ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/talking-avatar', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ script: 'Check out this amazing product! It's super easy to use and I love the results.', avatarId: '2' }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ## Complete Example ```javascript theme={null} const createTalkingAvatar = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/talking-avatar', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Welcome Video', script: 'Hey there! Thanks for checking out our channel. We create awesome content every week, so make sure to subscribe and hit that notification bell!', avatarId: '2', caption: { preset: 'tiktok', alignment: 'bottom', disabled: false }, musicId: 'music_upbeat_01', webhook: 'https://yoursite.com/webhook', metadata: { channelId: 'my-channel', videoType: 'welcome' } }) }); return await response.json(); }; ``` ## Tips for Talking Avatar Videos **Be authentic**: Write scripts that sound natural and conversational, not scripted **Keep it short**: 15-60 seconds works best for social media content **Put the tone in the script**: the avatar reads what you write the way you write it, so punctuation and short sentences shape the delivery **Pick the avatar for the audience**: voice and look come together with the avatar **Use webhooks**: Always use webhooks in production instead of polling for video status ## Effective Script Formulas | Formula | Example | | - | - | | **Conversational Greeting** | "Hey! How's it going? I wanted to share something cool with you..." | | **Personal Story** | "So I tried this yesterday and I was blown away..." | | **Quick Tip** | "Quick tip that changed everything for me..." | | **Testimonial Style** | "I've been using this for a month now and here's my honest review\..." | | **Friendly Update** | "Hey everyone! Just wanted to give you a quick update..." | ## Caption Presets Available caption presets for the `caption.preset` field: | Preset | Description | | - | - | | `default` | Default caption style with bold text and shadow effects | | `beast` | Bold uppercase style with Komika font | | `umi` | Yellow glowing text style | | `tiktok` | Viral & trendy style, perfect for social media | | `wrap1` | Wrapped style with red background highlight | | `wrap2` | Wrapped style with blue background highlight (uppercase) | | `ariel` | Bold uppercase style with purple highlight | | `hooked` | Brand style with purple background | | `classic` | Clean, simple captions with black background (Default) | | `active` | Green background with bold text | | `bubble` | White background bubble style | | `glass` | Glassmorphic transparency effect | | `comic` | Comic Sans font with colorful style | | `glow` | Pink and orange glow effects | | `pastel` | Soft pastel pink background | | `neon` | Green neon glow effect | | `retroTV` | Retro TV style with cyan glow | | `red` | Red glow effect with white text | | `marker` | Yellow marker/highlighter style | | `modern` | Contemporary white background style | | `blue` | Blue background style | | `vivid` | Vibrant pink background with uppercase text | *** ## Use Cases * **Testimonials**: Authentic-looking customer testimonials * **Social Media Content**: TikTok, Instagram Reels, YouTube Shorts * **Personal Messages**: Video messages for customers or team * **Quick Updates**: Company updates or announcements * **Educational Content**: Quick tips or how-to videos * **Product Presentations**: Showcase products with talking avatars ## Handling the Webhook Response ```javascript theme={null} // Express.js webhook handler app.post('/webhook', (req, res) => { const { data, status, message } = req.body; if (status === "COMPLETED") { const { videoId, status: videoStatus, url, shareUrl, metadata } = data; console.log('Video completed!'); console.log('Video ID:', videoId); console.log('Download URL:', url); console.log('Share URL:', shareUrl); console.log('Channel:', metadata.channelId); // Save to your database, notify team, etc. } res.status(200).send('OK'); }); ``` # Template Personalization Source: https://docs.hooked.so/examples/template-personalization Create personalized videos at scale using templates and variable replacement ## Overview Template personalization lets you create a single video template and generate hundreds of unique videos by replacing variables. This is ideal for outreach, onboarding, and marketing campaigns. ## Quick Example ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create', { method: 'POST', headers: { 'x-api-key': 'your_api_key_here', 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'class', templateId: 'template_abc123', scenes: [ { script: 'Hi {{name}}! Welcome to {{company}}.', variables: [ { key: 'name', value: 'Sarah Johnson' }, { key: 'company', value: 'TechStart Inc' } ] }, { script: 'Your {{plan}} plan includes {{features}}.', variables: [ { key: 'plan', value: 'Professional' }, { key: 'features', value: 'unlimited videos and priority support' } ] } ], webhook: 'https://your-domain.com/webhook' }) }); const data = await response.json(); console.log('Project ID:', data.data.projectId); ``` ## Bulk Personalization Generate videos for multiple recipients: ```javascript theme={null} const API_KEY = process.env.HOOKED_API_KEY; const BASE_URL = 'https://api.hooked.so/v1'; const recipients = [ { name: 'John', company: 'Acme Corp', plan: 'Pro' }, { name: 'Sarah', company: 'TechStart', plan: 'Starter' }, { name: 'Mike', company: 'BigCo', plan: 'Enterprise' } ]; for (const recipient of recipients) { const response = await fetch(`${BASE_URL}/project/create`, { method: 'POST', headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'class', templateId: 'template_abc123', scenes: [ { script: `Hi {{name}}! Welcome to {{company}}.`, variables: [ { key: 'name', value: recipient.name }, { key: 'company', value: recipient.company } ] } ], webhook: `https://your-domain.com/webhook?user=${recipient.name}` }) }); const data = await response.json(); console.log(`Created video for ${recipient.name}:`, data.data.projectId); await new Promise(resolve => setTimeout(resolve, 1000)); } ``` ## Variable Rules 1. **Provide all required variables** — every `{{variable}}` in the script needs a value 2. **Case-sensitive** — variable names must match exactly 3. **Overlay text** — variables appear as text overlays on the video slide 4. **Empty values allowed** — pass `""` if a variable is optional ```javascript theme={null} // ✅ All variables provided { script: "Hello {{name}} from {{company}}!", variables: [ { key: "name", value: "John Doe" }, { key: "company", value: "Acme Corp" } ] } // ❌ Missing 'company' variable { script: "Hello {{name}} from {{company}}!", variables: [ { key: "name", value: "John Doe" } ] } ``` ## Related Deep dive into the template system Advanced personalization strategies Scale to hundreds of videos Create UGC advertising videos # TikTok Slideshow Example Source: https://docs.hooked.so/examples/tiktok-slideshow Create engaging slideshow videos with text overlays and custom positioning ## Overview TikTok Slideshow videos are perfect for storytelling with multiple images or clips, each with custom text overlays. Ideal for product showcases, tutorials, and social media content. ## Basic Slideshow ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/tiktok-slideshow', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Product Showcase', slides: [ { media: 'media_001', texts: [ { text: 'New Product Launch', fontSize: 16, x: 0, y: 43.13, width: 187, height: 36, color: '#ffffff', textAlign: 'center' } ] }, { media: 'media_002', duration: 3, texts: [ { text: 'Available Now!', x: 0, y: 43.13, width: 187, height: 36, fontSize: 36, color: '#00ff00' } ] } ] }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` Each slide requires a `media` field with the media ID from your media library. The media must already be uploaded to your account. ## Advanced Slideshow with Avatar and Music ```javascript theme={null} const createAdvancedSlideshow = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/tiktok-slideshow', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Summer Collection 2024', script: 'Check out our amazing summer collection. Three new styles, perfect for the season. Get yours today!', avatarId: 'avatar_id', musicId: 'upbeat_001', slides: [ { media: 'media_summer_001', duration: 4, texts: [ { text: 'Summer Collection 2024', x: 100, y: 150, width: 880, height: 100, fontSize: 52, fontWeight: "700", color: "#ffffff", backgroundColor: "#ff6b6b", textAlign: "center", borderRadius: 12, padding: 20, opacity: 0.95 }, { text: 'Fresh & Stylish', x: 200, y: 280, width: 680, height: 70, fontSize: 32, fontWeight: "600", color: "#ffed4e", backgroundColor: "transparent", textAlign: "center" } ] }, { media: 'media_summer_002', duration: 4, texts: [ { text: '100% Premium Cotton', x: 150, y: 1400, width: 780, height: 90, fontSize: 36, fontWeight: "600", color: "#ffffff", backgroundColor: "#000000", textAlign: "center", borderRadius: 10, padding: 12, opacity: 0.9 } ] }, { media: 'media_summer_003', duration: 4, texts: [ { text: 'Shop Now!', x: 200, y: 1650, width: 680, height: 120, fontSize: 56, fontWeight: '800', color: '#000000', backgroundColor: '#ffed4e', textAlign: 'center', borderRadius: 20, padding: 25, scale: 1.1 } ] } ], webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'summer-2024', collection: 'beachwear', variant: 'A' } }) }); return await response.json(); }; ``` ## Tips for TikTok Slideshows **Keep slides short**: 1-10 seconds per slide works best for engagement (default: 3 seconds) **Use high-contrast text**: Ensure text is readable on all backgrounds **Coordinate positioning**: For 9:16 format, canvas is 1080x1920 pixels **Add voiceover**: When using script, avatar, and voice together, they create engaging voiceover content **Layer text properly**: Use zIndex to control text stacking order **Media IDs only**: Each slide only needs the media ID - the media must already be uploaded to your account *** ## Canvas Dimensions by Aspect Ratio When using custom text positioning, remember these canvas sizes: | Aspect Ratio | Canvas Size | Best For | | - | - | - | | `ratio_9_16` | 1080 x 1920 | TikTok, Reels, Shorts | | `ratio_16_9` | 1920 x 1080 | YouTube, horizontal videos | | `ratio_1_1` | 1080 x 1080 | Instagram square posts | *** ## Text Positioning Guide ```javascript theme={null} // Example: Create a text box in the top-right corner (9:16 format) { x: 580, // X position from left y: 100, // Y position from top width: 400, // Width in pixels height: 200 // Height in pixels } // Example: Full-width text at bottom { x: 100, y: 1650, width: 880, height: 150 } // Example: Centered text box { x: 290, // Roughly centered horizontally y: 860, // Roughly centered vertically width: 500, height: 200 } ``` *** ## Styling Examples ### Bold Title Style ```javascript theme={null} { text: 'BOLD ANNOUNCEMENT', fontSize: 56, fontWeight: '800', color: '#ffffff', backgroundColor: '#000000', textAlign: 'center', borderRadius: 10, padding: 20, opacity: 0.95 } ``` ### Subtle Subtitle Style ```javascript theme={null} { text: 'Secondary information', fontSize: 28, fontWeight: '400', color: '#cccccc', backgroundColor: 'transparent', textAlign: 'center', opacity: 0.8 } ``` ### Call-to-Action Style ```javascript theme={null} { text: 'CLICK HERE', fontSize: 42, fontWeight: '700', color: '#000000', backgroundColor: '#00ff00', textAlign: 'center', borderRadius: 25, padding: 18, scale: 1.05 } ``` *** ## Common Use Cases * **Product Launches**: Showcase multiple product angles with descriptions * **Tutorials**: Step-by-step guides with visual aids * **Before/After**: Transformations with text explanations * **Lists**: "Top 5" or "Best of" content with images * **Stories**: Narrative content with sequential images * **Testimonials**: Customer reviews with photos *** ## Handling the Webhook Response ```javascript theme={null} // Express.js webhook handler app.post('/webhook', (req, res) => { const { data, status, message } = req.body; if (status === "COMPLETED") { const { videoId, status: videoStatus, url, shareUrl } = data; console.log('Slideshow completed!'); console.log('Video ID:', videoId); console.log('Download URL:', url); console.log('Share URL:', shareUrl); // Save to your database, notify team, etc. } res.status(200).send('OK'); }); ``` # UGC Ads Example Source: https://docs.hooked.so/examples/ugc-ads Create authentic-looking user-generated content ads ## Overview Create UGC-style advertising content that looks authentic and engaging - perfect for social media campaigns. UGC Ads combine AI avatars with natural-sounding scripts to create convincing spokesperson videos. ## Quick Example ```javascript theme={null} const response = await fetch('https://api.hooked.so/v1/project/create/ugc-ads', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ script: 'Hey everyone! I just got this amazing product and I had to share my experience with you.', avatarId: 'avatar_casual_01', media: ['product_demo'] }) }); const data = await response.json(); console.log('Video ID:', data.data.videoId); console.log('Project ID:', data.data.projectId); ``` ## Complete Example ```javascript theme={null} const createUGCAd = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/ugc-ads', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'UGC Demo', script: 'Stop scrolling! I need to tell you about something incredible. I was skeptical at first, but after trying this for just one week... I\'m never going back.', avatarId: 'avatar_influencer_01', adSettings: { bRollType: 'rounded-cover', removeAvatarBackground: true }, media: ['product_video', 'product_image'], musicId: 'trending_music_01', aspectRatio: 'ratio_9_16', caption: { preset: 'wrap1', alignment: 'top', disabled: false }, webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'q1-campaign', abTestVariant: 'B' } }) }); return await response.json(); }; ``` ## Example with Audio Settings ```javascript theme={null} const createUGCAdWithAudio = async () => { const response = await fetch('https://api.hooked.so/v1/project/create/ugc-ads', { method: 'POST', headers: { 'x-api-key': process.env.HOOKED_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Product Review', script: 'I\'ve been testing this product for weeks and I\'m blown away by the results. Let me show you exactly what makes it special.', avatarId: 'avatar_influencer_01', adSettings: { bRollType: 'rounded-cover', removeAvatarBackground: true }, media: ['product_demo', 'unboxing_video'], musicId: 'trending_music_01', aspectRatio: 'ratio_9_16', caption: { preset: 'tiktok', alignment: 'bottom', disabled: false }, webhook: 'https://yoursite.com/webhook', metadata: { campaignId: 'product-review-2024', productId: 'PROD-12345' } }) }); return await response.json(); }; ``` ## Tips for UGC Ads **Be authentic**: Write scripts that sound natural and conversational, not salesy **Hook in 2 seconds**: Start with attention-grabbing words like "Stop scrolling!" or "POV:" **Use 9:16 format**: Vertical videos perform better on TikTok, Reels, and Shorts **Keep it short**: 15-30 seconds works best for social media ads **Add B-roll**: Product shots and demos increase engagement and credibility ## Effective Script Formulas | Formula | Example | | - | - | | **Problem → Solution** | "I used to struggle with X... then I found this!" | | **Skeptic → Believer** | "I didn't believe the hype, but after trying it..." | | **Before → After** | "My mornings used to be chaos. Now look at this!" | | **Social Proof** | "Everyone's been asking about my secret. Here it is!" | | **Urgency** | "They're almost sold out! I had to share before it's gone" | ## B-Roll Display Types | Type | Description | | - | - | | `down` | Split screen down - Avatar on top half, B-roll on bottom half (Default) | | `up` | Split screen up - B-roll on top half, avatar on bottom half | | `left` | Split screen left - Avatar on right, B-roll on left - balanced layout | | `right` | Split screen right - Avatar on left, B-roll on right - balanced layout | | `rounded-cover` | Rounded - Rounded avatar overlay on B-roll background | | `full-width` | Full screen - B-roll covers entire screen, avatar hidden | ## Use Cases * **Product Reviews**: Authentic-looking testimonials * **Unboxing Videos**: Product reveal content * **Social Media Ads**: TikTok, Instagram Reels, Facebook ads * **Comparison Ads**: Side-by-side product comparisons * **Tutorial Teasers**: Quick how-to previews ## Handling the Webhook Response ```javascript theme={null} // Express.js webhook handler app.post('/webhook', (req, res) => { const { data, status, message } = req.body; if (status === "COMPLETED") { const { videoId, status: videoStatus, url, shareUrl, metadata } = data; console.log('Video completed!'); console.log('Video ID:', videoId); console.log('Download URL:', url); console.log('Share URL:', shareUrl); console.log('Campaign:', metadata.campaignId); // Save to your database, notify team, etc. } res.status(200).send('OK'); }); ``` # Best Practices Source: https://docs.hooked.so/guides/best-practices Production-ready patterns and optimization tips ## Overview Follow these best practices to build reliable, efficient, and scalable applications with Hooked's API. ## Authentication & Security ### Store API Keys Securely Never commit API keys to version control ```bash theme={null} # .env (add to .gitignore) HOOKED_API_KEY=your_secret_key_here ``` ```javascript theme={null} // ✅ Good - Use environment variables const apiKey = process.env.HOOKED_API_KEY; // ❌ Bad - Hardcoded const apiKey = 'sk_live_abc123...'; ``` ### Use Different Keys for Environments ```bash theme={null} # .env.development HOOKED_API_KEY=sk_dev_... # .env.production HOOKED_API_KEY=sk_live_... ``` ### Rotate Keys Regularly * Rotate API keys every 90 days * Immediately rotate if compromised * Use key rotation without downtime: ```javascript theme={null} const PRIMARY_KEY = process.env.HOOKED_API_KEY_PRIMARY; const BACKUP_KEY = process.env.HOOKED_API_KEY_BACKUP; async function makeRequestWithFailover(endpoint, options) { try { return await makeRequest(endpoint, { ...options, apiKey: PRIMARY_KEY }); } catch (error) { if (error.code === 'INVALID_API_KEY') { // Failover to backup key return await makeRequest(endpoint, { ...options, apiKey: BACKUP_KEY }); } throw error; } } ``` ## Error Handling ### Implement Comprehensive Error Handling ```javascript theme={null} class HookedAPIClient { async request(endpoint, options) { try { const response = await fetch(endpoint, options); const data = await response.json(); if (!response.ok) { throw new APIError(data.message, response.status, data.code); } return data; } catch (error) { if (error instanceof APIError) { // Handle known API errors this.handleAPIError(error); } else if (error.name === 'TypeError') { // Network error throw new Error('Network connection failed'); } else { // Unknown error this.logError(error); throw error; } } } handleAPIError(error) { switch (error.code) { case 'RATE_LIMIT_EXCEEDED': // Wait and retry break; case 'INVALID_API_KEY': // Alert admin break; case 'VALIDATION_ERROR': // Fix request data break; default: throw error; } } } ``` ### Use Retry Logic with Exponential Backoff ```javascript theme={null} async function retryWithBackoff(fn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { // Don't retry on client errors (4xx except 429) if (error.status >= 400 && error.status < 500 && error.status !== 429) { throw error; } if (i === maxRetries - 1) throw error; // Exponential backoff: 2^i * 1000ms const delay = Math.pow(2, i) * 1000; await new Promise(r => setTimeout(r, delay)); } } } // Usage const result = await retryWithBackoff(() => api.createVideo(videoData) ); ``` ## Performance Optimization ### Cache Resources ```javascript theme={null} class ResourceCache { constructor(ttl = 3600000) { // 1 hour this.cache = new Map(); this.ttl = ttl; } async get(key, fetchFn) { const cached = this.cache.get(key); if (cached && Date.now() - cached.timestamp < this.ttl) { return cached.data; } const data = await fetchFn(); this.cache.set(key, { data, timestamp: Date.now() }); return data; } } const cache = new ResourceCache(); // Cache avatars list const avatars = await cache.get('avatars', () => api.getAvatars() ); // Cache voices list const voices = await cache.get('voices', () => api.getVoices() ); ``` ### Batch Operations ```javascript theme={null} // ❌ Bad - Sequential requests for (const recipient of recipients) { await api.createVideo(recipient); } // ✅ Good - Batched with concurrency limit async function batchProcess(items, batchSize = 5) { for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); await Promise.all(batch.map(item => api.createVideo(item))); } } await batchProcess(recipients, 5); ``` ### Minimize API Calls ```javascript theme={null} // ❌ Bad - Multiple calls for same data const avatar1 = await api.getAvatar('avatar_123'); const avatar2 = await api.getAvatar('avatar_123'); // Duplicate! // ✅ Good - Fetch once, reuse const avatars = await api.getAvatars(); const avatar = avatars.find(a => a.id === 'avatar_123'); ``` ## Webhook Implementation ### Always Use Webhooks, Not Polling ```javascript theme={null} // ❌ Bad - Polling async function waitForVideo(projectId) { while (true) { const video = await api.getVideo(projectId); if (video.status === 'completed') return video; await new Promise(r => setTimeout(r, 10000)); // Wasteful! } } // ✅ Good - Webhook app.post('/webhook', (req, res) => { const { projectId, status, video } = req.body; if (status === 'completed') { handleCompletedVideo(projectId, video); } res.status(200).send('OK'); }); ``` ### Implement Idempotency ```javascript theme={null} const processedWebhooks = new Set(); app.post('/webhook', (req, res) => { const { projectId } = req.body; // Prevent duplicate processing if (processedWebhooks.has(projectId)) { return res.status(200).send('Already processed'); } processedWebhooks.add(projectId); // Process webhook processWebhook(req.body); res.status(200).send('OK'); }); ``` ### Return 200 Immediately ```javascript theme={null} // ✅ Good - Respond fast, process async app.post('/webhook', async (req, res) => { // Respond immediately res.status(200).send('OK'); // Process asynchronously setImmediate(async () => { try { await processWebhook(req.body); } catch (error) { console.error('Webhook processing error:', error); } }); }); ``` ## Rate Limiting ### Monitor Rate Limits ```javascript theme={null} class RateLimitTracker { checkHeaders(response) { const limit = response.headers.get('X-RateLimit-Limit'); const remaining = response.headers.get('X-RateLimit-Remaining'); const reset = response.headers.get('X-RateLimit-Reset'); console.log(`Rate limit: ${remaining}/${limit}, resets at ${new Date(reset * 1000)}`); if (remaining < 10) { console.warn('⚠️ Approaching rate limit!'); } } } ``` ### Implement Request Queuing ```javascript theme={null} import PQueue from 'p-queue'; const queue = new PQueue({ concurrency: 5, interval: 1000, intervalCap: 10 }); async function queuedRequest(endpoint, options) { return queue.add(() => fetch(endpoint, options)); } ``` ## Logging & Monitoring ### Structured Logging ```javascript theme={null} class APILogger { log(level, message, meta = {}) { console.log(JSON.stringify({ timestamp: new Date().toISOString(), level, message, ...meta })); } info(message, meta) { this.log('info', message, meta); } error(message, meta) { this.log('error', message, meta); } warn(message, meta) { this.log('warn', message, meta); } } const logger = new APILogger(); // Usage logger.info('Video created', { projectId: 'proj_123', duration: 1250, status: 'success' }); ``` ### Track Key Metrics ```javascript theme={null} class MetricsCollector { constructor() { this.metrics = { requests: 0, successes: 0, failures: 0, totalDuration: 0 }; } async trackRequest(fn) { this.metrics.requests++; const start = Date.now(); try { const result = await fn(); this.metrics.successes++; return result; } catch (error) { this.metrics.failures++; throw error; } finally { this.metrics.totalDuration += Date.now() - start; } } getStats() { return { ...this.metrics, successRate: (this.metrics.successes / this.metrics.requests) * 100, avgDuration: this.metrics.totalDuration / this.metrics.requests }; } } const metrics = new MetricsCollector(); // Track API calls await metrics.trackRequest(() => api.createVideo(data)); // Log stats console.log(metrics.getStats()); ``` ## Data Validation ### Validate Before Sending ```javascript theme={null} const videoSchema = { type: { required: true, enum: ['class', 'ugc_ads'] }, script: { required: true, minLength: 10, maxLength: 5000 }, avatarId: { required: true, pattern: /^avatar_/ }, voiceId: { required: true, pattern: /^voice_/ } }; function validateVideoData(data, schema) { const errors = []; Object.entries(schema).forEach(([field, rules]) => { const value = data[field]; if (rules.required && !value) { errors.push(`${field} is required`); } if (rules.enum && !rules.enum.includes(value)) { errors.push(`${field} must be one of: ${rules.enum.join(', ')}`); } if (rules.minLength && value.length < rules.minLength) { errors.push(`${field} must be at least ${rules.minLength} characters`); } if (rules.pattern && !rules.pattern.test(value)) { errors.push(`${field} has invalid format`); } }); return { valid: errors.length === 0, errors }; } // Validate before API call const validation = validateVideoData(videoData, videoSchema); if (!validation.valid) { throw new Error(`Validation failed: ${validation.errors.join(', ')}`); } ``` ## Testing ### Unit Test API Client ```javascript theme={null} // api-client.test.js import { describe, it, expect, vi } from 'vitest'; import { HookedAPIClient } from './api-client'; describe('HookedAPIClient', () => { it('should create video successfully', async () => { const client = new HookedAPIClient('test_key'); global.fetch = vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, data: { projectId: 'proj_123' } }) }) ); const result = await client.createVideo({ type: 'class', script: 'Test script' }); expect(result.projectId).toBe('proj_123'); }); it('should handle errors', async () => { const client = new HookedAPIClient('test_key'); global.fetch = vi.fn(() => Promise.resolve({ ok: false, status: 400, json: () => Promise.resolve({ success: false, message: 'Invalid request' }) }) ); await expect(client.createVideo({})).rejects.toThrow('Invalid request'); }); }); ``` ### Integration Testing ```javascript theme={null} // integration.test.js describe('Video Creation Flow', () => { it('should create and track video', async () => { const api = new HookedAPIClient(process.env.TEST_API_KEY); // 1. Get resources const avatars = await api.getAvatars(); expect(avatars.length).toBeGreaterThan(0); // 2. Create video const projectId = await api.createVideo({ type: 'class', script: 'Integration test video', avatarId: avatars[0].id }); expect(projectId).toBeTruthy(); // 3. Verify video exists const videos = await api.listVideos(); expect(videos.some(v => v.id === projectId)).toBe(true); }); }); ``` ## Code Organization ### Create Reusable SDK ```javascript theme={null} // hooked-sdk.js export class HookedSDK { constructor(apiKey, options = {}) { this.apiKey = apiKey; this.baseUrl = options.baseUrl || 'https://api.hooked.so/v1'; this.cache = new ResourceCache(); this.logger = new APILogger(); } // Resource methods async getAvatars(type = 'all') { /* ... */ } async getVoices(filters = {}) { /* ... */ } async getMusic() { /* ... */ } // Template methods async listTemplates() { /* ... */ } async getTemplate(id) { /* ... */ } // Video methods async createVideo(data) { /* ... */ } async listVideos(filters) { /* ... */ } // High-level helpers async createFromScratch(script, options) { /* ... */ } async generateFromTemplate(templateId, variables) { /* ... */ } async bulkGenerate(templateId, recipients) { /* ... */ } } // Usage import { HookedSDK } from './hooked-sdk'; const hooked = new HookedSDK(process.env.HOOKED_API_KEY); await hooked.createVideo({ /* ... */ }); ``` ## Deployment ### Environment Configuration ```javascript theme={null} // config.js const config = { development: { apiKey: process.env.HOOKED_DEV_KEY, baseUrl: 'https://dev.hooked.so/api/v1', webhookUrl: 'https://dev-tunnel.ngrok.io/webhook', logLevel: 'debug' }, staging: { apiKey: process.env.HOOKED_STAGING_KEY, baseUrl: 'https://staging.hooked.so/api/v1', webhookUrl: 'https://staging.myapp.com/webhook', logLevel: 'info' }, production: { apiKey: process.env.HOOKED_API_KEY, baseUrl: 'https://api.hooked.so/v1', webhookUrl: 'https://myapp.com/webhook', logLevel: 'error' } }; export default config[process.env.NODE_ENV || 'development']; ``` ### Health Checks ```javascript theme={null} async function checkAPIHealth() { try { const response = await fetch('https://api.hooked.so/v1/status'); if (!response.ok) { throw new Error(`API health check failed: ${response.status}`); } const data = await response.json(); return { status: data.status, timestamp: new Date() }; } catch (error) { return { status: 'unhealthy', error: error.message, timestamp: new Date() }; } } setInterval(async () => { const health = await checkAPIHealth(); console.log('API Health:', health); if (health.status !== 'operational') { alertOps('Hooked API is not operational', health); } }, 5 * 60 * 1000); ``` ## Production Checklist Before going to production: **Security** * [ ] API keys stored in environment variables * [ ] Different keys for dev/staging/production * [ ] Webhook endpoints use HTTPS * [ ] Webhook signature verification implemented * [ ] Rate limiting tracked and handled **Reliability** * [ ] Comprehensive error handling * [ ] Retry logic with exponential backoff * [ ] Webhook idempotency implemented * [ ] Webhook returns 200 immediately * [ ] Failed requests logged and alerted **Performance** * [ ] Resources cached appropriately * [ ] Batch operations used for bulk tasks * [ ] Unnecessary API calls eliminated * [ ] Request timeouts configured **Monitoring** * [ ] Structured logging implemented * [ ] Key metrics tracked * [ ] Health checks configured * [ ] Error alerts set up * [ ] Dashboard for monitoring **Testing** * [ ] Unit tests for API client * [ ] Integration tests pass * [ ] Webhook endpoint tested * [ ] Error scenarios tested * [ ] Load testing completed ## Quick Reference End-to-end implementation guide Handle errors gracefully Real-time notifications Scale to hundreds of videos # Bulk Generation Source: https://docs.hooked.so/guides/bulk-generation Generate hundreds of personalized videos at scale ## Overview Bulk generation allows you to create hundreds or thousands of personalized videos efficiently using templates and automation. **Use Case**: Perfect for personalized marketing campaigns, sales outreach, customer onboarding, and more. ## Prerequisites Before bulk generating videos: **You need:** * A template with defined variables * A list of recipients with their data * A webhook endpoint to receive completion notifications * Sufficient API credits/quota ## Basic Bulk Generation ### Step 1: Prepare Your Data ```javascript theme={null} // recipients.json const recipients = [ { name: "Sarah Johnson", company: "TechCorp", role: "CEO", metric: "30% increase in productivity" }, { name: "Mike Chen", company: "StartupXYZ", role: "CTO", metric: "50% faster deployment" }, { name: "Emma Davis", company: "BigCorp Inc", role: "VP of Sales", metric: "$2M in additional revenue" } // ... more recipients ]; ``` ### Step 2: Simple Bulk Generator ```javascript theme={null} async function bulkGenerate(templateId, recipients) { const results = []; for (const recipient of recipients) { try { const response = await fetch('https://api.hooked.so/v1/project/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.HOOKED_API_KEY }, body: JSON.stringify({ type: 'class', templateId: templateId, title: `Video for ${recipient.name}`, scenes: [{ script: template.scenes[0].script, variables: Object.entries(recipient).map(([key, value]) => ({ key, value: String(value) })) }], webhook: `https://yoursite.com/webhook?recipient=${recipient.email}` }) }); const result = await response.json(); if (result.success) { results.push({ recipient: recipient.name, projectId: result.data.projectId, status: 'queued' }); console.log(`✅ Queued video for ${recipient.name}`); } else { throw new Error(result.message); } // Rate limiting: wait 1 second between requests await new Promise(r => setTimeout(r, 1000)); } catch (error) { console.error(`❌ Failed for ${recipient.name}:`, error.message); results.push({ recipient: recipient.name, status: 'failed', error: error.message }); } } return results; } // Execute const results = await bulkGenerate('template_123', recipients); console.log(`Generated ${results.filter(r => r.status === 'queued').length}/${recipients.length} videos`); ``` ## Advanced Bulk Generation ### With Rate Limiting & Retries ```javascript theme={null} class BulkVideoGenerator { constructor(apiKey, options = {}) { this.apiKey = apiKey; this.baseUrl = 'https://api.hooked.so/v1'; this.concurrency = options.concurrency || 5; this.retryAttempts = options.retryAttempts || 3; this.retryDelay = options.retryDelay || 5000; } async generateOne(templateId, recipient, attempt = 1) { try { const response = await fetch(`${this.baseUrl}/project/create`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify({ type: 'class', templateId, title: `Video for ${recipient.name}`, scenes: [{ variables: Object.entries(recipient).map(([key, value]) => ({ key, value: String(value) })) }], webhook: recipient.webhook }) }); const result = await response.json(); if (!response.ok) { // Retry on rate limit if (response.status === 429 && attempt < this.retryAttempts) { const retryAfter = response.headers.get('Retry-After') || this.retryDelay / 1000; console.log(`Rate limited. Retrying in ${retryAfter}s...`); await new Promise(r => setTimeout(r, retryAfter * 1000)); return this.generateOne(templateId, recipient, attempt + 1); } throw new Error(result.message); } return { success: true, recipient: recipient.name, projectId: result.data.projectId }; } catch (error) { if (attempt < this.retryAttempts) { console.log(`Retry ${attempt}/${this.retryAttempts} for ${recipient.name}`); await new Promise(r => setTimeout(r, this.retryDelay)); return this.generateOne(templateId, recipient, attempt + 1); } return { success: false, recipient: recipient.name, error: error.message }; } } async generateBulk(templateId, recipients, onProgress) { const results = []; const batches = []; // Split into batches based on concurrency for (let i = 0; i < recipients.length; i += this.concurrency) { batches.push(recipients.slice(i, i + this.concurrency)); } let completed = 0; for (const batch of batches) { const batchResults = await Promise.all( batch.map(recipient => this.generateOne(templateId, recipient)) ); results.push(...batchResults); completed += batch.length; if (onProgress) { onProgress({ completed, total: recipients.length, percentage: Math.round((completed / recipients.length) * 100) }); } // Wait between batches to respect rate limits if (batches.indexOf(batch) < batches.length - 1) { await new Promise(r => setTimeout(r, 2000)); } } return results; } getStats(results) { return { total: results.length, successful: results.filter(r => r.success).length, failed: results.filter(r => !r.success).length, successRate: Math.round((results.filter(r => r.success).length / results.length) * 100) }; } } // Usage const generator = new BulkVideoGenerator(process.env.HOOKED_API_KEY, { concurrency: 5, retryAttempts: 3, retryDelay: 5000 }); const results = await generator.generateBulk( 'template_123', recipients, (progress) => { console.log(`Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`); } ); const stats = generator.getStats(results); console.log('Generation complete:', stats); ``` ## Processing CSV Files ### Read and Process CSV ```javascript theme={null} import fs from 'fs'; import csv from 'csv-parser'; async function processCSV(filePath, templateId) { const recipients = []; return new Promise((resolve, reject) => { fs.createReadStream(filePath) .pipe(csv()) .on('data', (row) => { // Validate required fields if (row.name && row.email) { recipients.push({ name: row.name, email: row.email, company: row.company || 'Unknown', role: row.role || 'Professional', // Add more fields as needed webhook: `https://yoursite.com/webhook?email=${row.email}` }); } }) .on('end', async () => { console.log(`Loaded ${recipients.length} recipients from CSV`); const generator = new BulkVideoGenerator(process.env.HOOKED_API_KEY); const results = await generator.generateBulk(templateId, recipients); resolve(results); }) .on('error', reject); }); } // Usage const results = await processCSV('./recipients.csv', 'template_123'); ``` ## Database Integration ### Generate from Database Records ```javascript theme={null} import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function generateFromDatabase(templateId, filters = {}) { // Fetch recipients from database const contacts = await prisma.contact.findMany({ where: { status: 'active', hasConsent: true, ...filters }, select: { id: true, name: true, email: true, company: true, customFields: true } }); console.log(`Found ${contacts.length} contacts in database`); const generator = new BulkVideoGenerator(process.env.HOOKED_API_KEY); const results = await generator.generateBulk( templateId, contacts.map(c => ({ ...c, ...c.customFields, webhook: `https://yoursite.com/webhook?contactId=${c.id}` })), async (progress) => { // Update progress in database await prisma.bulkJob.update({ where: { id: jobId }, data: { progress: progress.percentage, completed: progress.completed } }); } ); // Save results to database for (const result of results) { if (result.success) { await prisma.video.create({ data: { projectId: result.projectId, contactId: result.recipient.id, status: 'processing' } }); } } return results; } const results = await generateFromDatabase('template_123', { segment: 'enterprise' }); ``` ## Webhook Management for Bulk Jobs ### Track Completions ```javascript theme={null} // In-memory tracking (use Redis/Database for production) const bulkJobs = new Map(); // Start bulk job app.post('/api/bulk/start', async (req, res) => { const { templateId, recipients } = req.body; const jobId = `job_${Date.now()}`; bulkJobs.set(jobId, { id: jobId, total: recipients.length, completed: 0, videos: [], startedAt: new Date() }); // Start generation in background generateBulkAsync(jobId, templateId, recipients); res.json({ jobId, status: 'started' }); }); // Webhook handler app.post('/api/webhook', async (req, res) => { const { projectId, status, video } = req.body; const jobId = req.query.jobId; if (jobId && bulkJobs.has(jobId)) { const job = bulkJobs.get(jobId); job.completed++; if (status === 'completed') { job.videos.push({ projectId, url: video.url, completedAt: new Date() }); } // Check if job complete if (job.completed === job.total) { job.completedAt = new Date(); job.duration = job.completedAt - job.startedAt; console.log(`Bulk job ${jobId} completed!`); console.log(`Generated ${job.videos.length}/${job.total} videos in ${job.duration}ms`); // Notify user await notifyJobComplete(jobId, job); } bulkJobs.set(jobId, job); } res.status(200).send('OK'); }); // Check job status app.get('/api/bulk/:jobId', (req, res) => { const job = bulkJobs.get(req.params.jobId); if (!job) { return res.status(404).json({ error: 'Job not found' }); } res.json({ ...job, progress: Math.round((job.completed / job.total) * 100), status: job.completedAt ? 'completed' : 'processing' }); }); ``` ## Performance Optimization ### Tips for Large Batches Process multiple videos in parallel (5-10 concurrent requests) ```javascript theme={null} const concurrency = 5; ``` Use a job queue (Bull, BullMQ) for reliability ```javascript theme={null} import Queue from 'bull'; const videoQueue = new Queue('videos', { redis: { host: 'localhost', port: 6379 } }); videoQueue.process(async (job) => { return await generateVideo(job.data); }); ``` Fetch template details once and reuse ```javascript theme={null} const template = await getTemplate(templateId); // Reuse for all recipients ``` Track and respect API rate limits ```javascript theme={null} const rateLimiter = new RateLimiter(100, '1h'); ``` ## Error Handling Strategies ### Partial Failure Recovery ```javascript theme={null} async function generateWithRecovery(templateId, recipients) { const results = { successful: [], failed: [], pending: [...recipients] }; // Save state to disk/database const saveState = () => { fs.writeFileSync('bulk-state.json', JSON.stringify(results)); }; for (const recipient of recipients) { try { const projectId = await generateOne(templateId, recipient); results.successful.push({ recipient, projectId }); results.pending = results.pending.filter(r => r !== recipient); } catch (error) { results.failed.push({ recipient, error: error.message }); results.pending = results.pending.filter(r => r !== recipient); } // Save progress saveState(); } return results; } // Resume from saved state function resumeBulkGeneration() { const state = JSON.parse(fs.readFileSync('bulk-state.json')); return generateWithRecovery(templateId, state.pending); } ``` ## Best Practices Test with 5-10 videos before scaling to hundreds Never poll for bulk jobs - always use webhooks Track success rates and errors in real-time Handle transient failures with exponential backoff ## Example: Complete Bulk Campaign ```javascript theme={null} // complete-campaign.js import { BulkVideoGenerator } from './bulk-generator'; import { sendEmail } from './email-service'; async function runCampaign(campaignId) { // 1. Load recipients from database const recipients = await db.recipients.findMany({ where: { campaignId, status: 'pending' } }); // 2. Get template const campaign = await db.campaign.findUnique({ where: { id: campaignId }, include: { template: true } }); // 3. Generate videos const generator = new BulkVideoGenerator(process.env.HOOKED_API_KEY); const results = await generator.generateBulk( campaign.template.id, recipients.map(r => ({ ...r, webhook: `https://mysite.com/webhook?recipientId=${r.id}&campaignId=${campaignId}` })), async (progress) => { // Update campaign progress await db.campaign.update({ where: { id: campaignId }, data: { progress: progress.percentage } }); } ); // 4. Log results const stats = generator.getStats(results); await db.campaign.update({ where: { id: campaignId }, data: { status: 'completed', stats: stats, completedAt: new Date() } }); // 5. Notify admin await sendEmail({ to: campaign.owner.email, subject: `Campaign ${campaign.name} completed`, text: `Generated ${stats.successful}/${stats.total} videos (${stats.successRate}% success rate)` }); return results; } // Start campaign runCampaign('campaign_123'); ``` ## Next Steps Advanced personalization techniques Production-ready patterns # Complete Workflow Source: https://docs.hooked.so/guides/complete-workflow End-to-end guide from video creation to deployment ## Overview This guide walks through the complete process of creating and managing AI videos with Hooked's API, from initial setup to production deployment. ## Step-by-Step Workflow ### 1. Authentication Setup First, get your API key from the dashboard: Go to [https://hooked.so/settings/api](https://hooked.so/settings/api) Save your API key in environment variables ```bash theme={null} # .env HOOKED_API_KEY=your_api_key_here ``` ```bash theme={null} curl -H "x-api-key: $HOOKED_API_KEY" \ https://api.hooked.so/v1/avatar/list ``` ### 2. Choose Your Path Full control over every aspect **Best for:** Unique, one-off videos Fast, scalable personalization **Best for:** Bulk generation, personalized content *** ## Workflow: From Scratch ### Step 1: Fetch Available Resources ```javascript theme={null} const HOOKED_API_KEY = process.env.HOOKED_API_KEY; const BASE_URL = 'https://api.hooked.so/v1'; async function fetchResources() { // Get avatars const avatarsRes = await fetch(`${BASE_URL}/avatar/list`, { headers: { 'x-api-key': HOOKED_API_KEY } }); const { data: { avatars } } = await avatarsRes.json(); // Get voices const voicesRes = await fetch(`${BASE_URL}/voice/list`, { headers: { 'x-api-key': HOOKED_API_KEY } }); const { data: { voices } } = await voicesRes.json(); // Get music (optional) const musicRes = await fetch(`${BASE_URL}/music/list`, { headers: { 'x-api-key': HOOKED_API_KEY } }); const { data: { music } } = await musicRes.json(); return { avatars, voices, music }; } const resources = await fetchResources(); console.log(`Found ${resources.avatars.length} avatars`); console.log(`Found ${resources.voices.length} voices`); ``` ### Step 2: Select Resources ```javascript theme={null} // Filter for English female voices const englishFemaleVoices = resources.voices.filter(v => v.language === 'English' && v.gender === 'Female' ); // Pick a professional-looking avatar const professionalAvatars = resources.avatars.filter(a => a.situation === 'professional' ); const selectedAvatar = professionalAvatars[0]; const selectedVoice = englishFemaleVoices[0]; console.log(`Selected: ${selectedAvatar.name} (${selectedVoice.name})`); ``` ### Step 3: Create Video ```javascript theme={null} async function createVideoFromScratch() { const videoData = { type: 'class', title: 'Product Demo Video', script: `Hi! I'm excited to show you our amazing product. It's designed to make your life easier and more productive. Let me walk you through the key features.`, avatarId: selectedAvatar.id, voiceId: selectedVoice.id, aspectRatio: 'ratio_16_9', caption: { enabled: true, preset: 'wrap1', alignment: 'bottom' }, webhook: 'https://yoursite.com/api/webhook' }; const response = await fetch(`${BASE_URL}/project/create`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': HOOKED_API_KEY }, body: JSON.stringify(videoData) }); const result = await response.json(); if (result.success) { console.log('Video created:', result.data.projectId); return result.data.projectId; } else { throw new Error(result.message); } } const projectId = await createVideoFromScratch(); ``` ### Step 4: Monitor Progress **Option A: Using Webhooks (Recommended)** ```javascript theme={null} // Set up webhook endpoint (Express.js example) app.post('/api/webhook', async (req, res) => { const { event, projectId, status, video } = req.body; if (event === 'project.completed' && status === 'completed') { console.log('✅ Video ready:', video.url); // Save to database await saveVideo({ projectId, url: video.url, thumbnail: video.thumbnail, duration: video.duration }); // Notify user await notifyUser(projectId, video.url); } res.status(200).send('OK'); }); ``` **Option B: Polling (Not Recommended)** ```javascript theme={null} async function pollVideoStatus(projectId) { const maxAttempts = 60; // 5 minutes (60 * 5 seconds) for (let i = 0; i < maxAttempts; i++) { const response = await fetch(`${BASE_URL}/video/list`, { headers: { 'x-api-key': HOOKED_API_KEY } }); const { data: { videos } } = await response.json(); const video = videos.find(v => v.id === projectId); if (video.status === 'completed') { console.log('✅ Video ready!'); return video; } else if (video.status === 'failed') { throw new Error('Video generation failed'); } console.log(`Status: ${video.status} (${video.progress}%)`); await new Promise(r => setTimeout(r, 5000)); // Wait 5 seconds } throw new Error('Timeout waiting for video'); } ``` *** ## Workflow: With Templates ### Step 1: List Templates ```javascript theme={null} async function listTemplates() { const response = await fetch(`${BASE_URL}/template/list`, { headers: { 'x-api-key': HOOKED_API_KEY } }); const { data: { templates } } = await response.json(); console.log('Available templates:'); templates.forEach(t => { console.log(`- ${t.name} (${t.type}) - ${t.variables.length} variables`); }); return templates; } const templates = await listTemplates(); ``` ### Step 2: Get Template Details ```javascript theme={null} async function getTemplateDetails(templateId) { const response = await fetch(`${BASE_URL}/template/${templateId}`, { headers: { 'x-api-key': HOOKED_API_KEY } }); const { data: { template } } = await response.json(); console.log(`Template: ${template.name}`); console.log('Scenes:', template.scenes.length); console.log('Variables required:', template.variables); return template; } const selectedTemplate = templates[0]; const templateDetails = await getTemplateDetails(selectedTemplate.id); ``` ### Step 3: Prepare Variables ```javascript theme={null} // Extract variables from template const requiredVariables = templateDetails.variables; // Prepare personalized data const personalizationData = { name: 'Sarah Johnson', company: 'TechCorp Inc.', product: 'AI Assistant Pro', benefit: 'save 10 hours per week' }; // Validate all variables are provided const missingVars = requiredVariables.filter(v => !personalizationData[v]); if (missingVars.length > 0) { throw new Error(`Missing variables: ${missingVars.join(', ')}`); } ``` ### Step 4: Generate from Template ```javascript theme={null} async function generateFromTemplate(templateId, data) { const videoData = { type: 'class', templateId: templateId, title: `Personalized video for ${data.name}`, aspectRatio: 'ratio_16_9', caption: { enabled: true, preset: 'wrap1', alignment: 'bottom' }, scenes: templateDetails.scenes.map(scene => ({ script: scene.script, variables: Object.entries(data).map(([key, value]) => ({ key, value })) })), webhook: 'https://yoursite.com/api/webhook' }; const response = await fetch(`${BASE_URL}/project/create`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': HOOKED_API_KEY }, body: JSON.stringify(videoData) }); const result = await response.json(); return result.data.projectId; } const projectId = await generateFromTemplate(selectedTemplate.id, personalizationData); ``` ### Step 5: Bulk Generation ```javascript theme={null} async function bulkGenerate(templateId, recipients) { const projectIds = []; for (const recipient of recipients) { try { const projectId = await generateFromTemplate(templateId, recipient); projectIds.push({ recipient: recipient.name, projectId }); console.log(`✅ Created video for ${recipient.name}`); // Rate limiting: wait between requests await new Promise(r => setTimeout(r, 1000)); } catch (error) { console.error(`❌ Failed for ${recipient.name}:`, error.message); } } return projectIds; } // Generate personalized videos for multiple recipients const recipients = [ { name: 'Sarah Johnson', company: 'TechCorp', product: 'AI Assistant' }, { name: 'Mike Chen', company: 'StartupXYZ', product: 'Analytics Tool' }, { name: 'Emma Davis', company: 'BigCorp', product: 'Cloud Platform' }, ]; const projects = await bulkGenerate(selectedTemplate.id, recipients); console.log(`Generated ${projects.length} videos`); ``` *** ## Complete Example: Production-Ready Here's a complete, production-ready implementation: ```javascript theme={null} class HookedVideoAPI { constructor(apiKey, baseUrl = 'https://api.hooked.so/v1') { this.apiKey = apiKey; this.baseUrl = baseUrl; } async request(endpoint, options = {}) { const response = await fetch(`${this.baseUrl}${endpoint}`, { ...options, headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey, ...options.headers } }); const data = await response.json(); if (!response.ok) { throw new Error(`API Error: ${data.message}`); } return data; } // Resources async getAvatars(type = 'all') { const { data } = await this.request(`/avatar/list?type=${type}`); return data.avatars; } async getVoices(filters = {}) { const params = new URLSearchParams(filters); const { data } = await this.request(`/voice/list?${params}`); return data.voices; } async getMusic(category = null) { const params = category ? `?category=${category}` : ''; const { data } = await this.request(`/music/list${params}`); return data.music; } // Templates async listTemplates(type = null) { const params = type ? `?type=${type}` : ''; const { data } = await this.request(`/template/list${params}`); return data.templates; } async getTemplate(templateId) { const { data } = await this.request(`/template/${templateId}`); return data.template; } // Video Creation async createVideo(videoData) { const { data } = await this.request('/project/create', { method: 'POST', body: JSON.stringify(videoData) }); return data.projectId; } async listVideos(filters = {}) { const params = new URLSearchParams(filters); const { data } = await this.request(`/video/list?${params}`); return data.videos; } // High-level methods async createFromScratch(script, options = {}) { const videoData = { type: options.type || 'class', title: options.title || 'My Video', script, avatarId: options.avatarId, voiceId: options.voiceId, aspectRatio: options.aspectRatio || 'ratio_9_16', caption: options.caption || { enabled: true }, webhook: options.webhook }; return await this.createVideo(videoData); } async generateFromTemplate(templateId, variables, options = {}) { const template = await this.getTemplate(templateId); const videoData = { type: template.type, templateId, title: options.title || `Video from ${template.name}`, aspectRatio: options.aspectRatio || template.videoSettings?.aspectRatio, caption: options.caption, scenes: template.scenes.map(scene => ({ script: scene.script, variables: Object.entries(variables).map(([key, value]) => ({ key, value: String(value) })) })), webhook: options.webhook }; return await this.createVideo(videoData); } } // Usage const api = new HookedVideoAPI(process.env.HOOKED_API_KEY); // Create from scratch const projectId1 = await api.createFromScratch( 'Welcome to our platform! Let me show you around.', { avatarId: 'avatar_123', voiceId: 'voice_456', aspectRatio: 'ratio_16_9', webhook: 'https://mysite.com/webhook' } ); // Generate from template const projectId2 = await api.generateFromTemplate( 'template_789', { name: 'John Doe', company: 'Acme Corp' }, { webhook: 'https://mysite.com/webhook' } ); console.log('Videos created:', projectId1, projectId2); ``` ## Best Practices Summary Always use webhooks instead of polling for better efficiency Implement comprehensive error handling and retries Respect rate limits and implement backoff strategies Cache resources (avatars, voices) to reduce API calls ## Next Steps Learn to generate videos at scale Master video personalization techniques Production-ready patterns # Error Handling Source: https://docs.hooked.so/guides/error-handling Handle errors gracefully and debug issues effectively ## Error Response Format All API errors follow a consistent JSON format: ```json theme={null} { "success": false, "message": "Error description", "code": "ERROR_CODE", "details": { "field": "Additional context" } } ``` ## HTTP Status Codes | Status Code | Meaning | Common Causes | | - | - | - | | **400** | Bad Request | Invalid parameters, missing required fields | | **401** | Unauthorized | Invalid or missing API key | | **403** | Forbidden | Valid API key but insufficient permissions | | **404** | Not Found | Resource doesn't exist or you don't have access | | **429** | Too Many Requests | Rate limit exceeded | | **500** | Internal Server Error | Server-side error (rare) | ## Common Errors ### Authentication Errors **Error:** ```json theme={null} { "success": false, "message": "API key is required", "code": "MISSING_API_KEY" } ``` **Solution:** ```javascript theme={null} // ❌ Wrong - Missing header fetch('https://api.hooked.so/v1/avatar/list') // ✅ Correct - Include API key fetch('https://api.hooked.so/v1/avatar/list', { headers: { 'x-api-key': 'your_api_key_here' } }) ``` **Error:** ```json theme={null} { "success": false, "message": "Invalid API key", "code": "INVALID_API_KEY" } ``` **Solution:** * Verify your API key is correct * Check you're using the right environment (production vs development) * Regenerate your API key if needed at [https://hooked.so/settings/api](https://hooked.so/settings/api) **Error:** ```json theme={null} { "success": false, "message": "Active subscription required", "code": "SUBSCRIPTION_REQUIRED" } ``` **Solution:** * Upgrade your plan at [https://hooked.so/settings/billing](https://hooked.so/settings/billing) * Check your subscription status ### Validation Errors **Error:** ```json theme={null} { "success": false, "message": "Missing required fields", "code": "VALIDATION_ERROR", "details": { "missing": ["script", "avatarId"] } } ``` **Solution:** ```javascript theme={null} // ❌ Wrong - Missing required fields { "type": "class" } // ✅ Correct - All required fields { "type": "class", "script": "Your video script here", "avatarId": "avatar_123", "voiceId": "voice_456" } ``` **Error:** ```json theme={null} { "success": false, "message": "Invalid aspect ratio", "code": "INVALID_VALUE", "details": { "field": "aspectRatio", "value": "16:9", "allowed": ["ratio_9_16", "ratio_16_9", "ratio_1_1"] } } ``` **Solution:** ```javascript theme={null} // ❌ Wrong - Invalid format { "aspectRatio": "16:9" } // ✅ Correct - Use enum value { "aspectRatio": "ratio_16_9" } ``` **Error:** ```json theme={null} { "success": false, "message": "Script exceeds maximum length", "code": "SCRIPT_TOO_LONG", "details": { "maxLength": 5000, "currentLength": 6500 } } ``` **Solution:** Split your script into multiple videos or reduce the length ### Resource Errors **Error:** ```json theme={null} { "success": false, "message": "Avatar not found", "code": "AVATAR_NOT_FOUND" } ``` **Solution:** * List available avatars: `GET /api/v1/avatar/list` * Verify the avatar ID exists and you have access to it * Check if it's a custom avatar from another team **Error:** ```json theme={null} { "success": false, "message": "Template not found or unauthorized", "code": "TEMPLATE_NOT_FOUND" } ``` **Solution:** * List your templates: `GET /api/v1/template/list` * Verify the template ID * Ensure the template belongs to your team ### Rate Limiting **Error:** ```json theme={null} { "success": false, "message": "Rate limit exceeded", "code": "RATE_LIMIT_EXCEEDED", "details": { "limit": 100, "window": "1 hour", "retryAfter": 3600 } } ``` **Headers:** ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1705320000 Retry-After: 3600 ``` **Solution:** ```javascript theme={null} async function makeRequestWithRetry(url, options, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = response.headers.get('Retry-After'); const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 60000; console.log(`Rate limited. Waiting ${waitTime}ms before retry...`); await new Promise(resolve => setTimeout(resolve, waitTime)); continue; } return response; } throw new Error('Max retries exceeded'); } ``` ### Processing Errors **Webhook Payload:** ```json theme={null} { "event": "project.failed", "projectId": "proj_123", "status": "failed", "message": "Avatar voice synthesis failed", "code": "VOICE_SYNTHESIS_ERROR" } ``` **Common Causes:** * Script contains unsupported characters * Voice model temporarily unavailable * Audio processing timeout **Solution:** * Check script for special characters * Try a different voice * Retry the request * Contact support if persists **Error:** ```json theme={null} { "success": false, "message": "Template variables don't match", "code": "VARIABLE_MISMATCH", "details": { "expected": ["name", "company"], "provided": ["name"], "missing": ["company"] } } ``` **Solution:** ```javascript theme={null} // ❌ Wrong - Missing variable { "templateId": "tpl_123", "scenes": [{ "script": "Hi {{name}} from {{company}}", "variables": [ { "key": "name", "value": "John" } // Missing 'company' ] }] } // ✅ Correct - All variables provided { "templateId": "tpl_123", "scenes": [{ "script": "Hi {{name}} from {{company}}", "variables": [ { "key": "name", "value": "John" }, { "key": "company", "value": "Acme Corp" } ] }] } ``` ## Error Handling Patterns ### Basic Error Handling ```javascript theme={null} async function createVideo(data) { try { const response = await fetch('https://api.hooked.so/v1/project/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.HOOKED_API_KEY }, body: JSON.stringify(data) }); const result = await response.json(); if (!response.ok) { throw new Error(`API Error: ${result.message}`); } return result; } catch (error) { console.error('Failed to create video:', error); throw error; } } ``` ### Advanced Error Handling ```javascript theme={null} class HookedAPIError extends Error { constructor(message, code, statusCode, details) { super(message); this.name = 'HookedAPIError'; this.code = code; this.statusCode = statusCode; this.details = details; } } async function createVideo(data) { try { const response = await fetch('https://api.hooked.so/v1/project/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.HOOKED_API_KEY }, body: JSON.stringify(data) }); const result = await response.json(); if (!response.ok) { throw new HookedAPIError( result.message, result.code, response.status, result.details ); } return result; } catch (error) { if (error instanceof HookedAPIError) { // Handle API errors switch (error.code) { case 'INVALID_API_KEY': console.error('Please check your API key'); break; case 'RATE_LIMIT_EXCEEDED': console.error('Rate limit hit, waiting before retry...'); await new Promise(r => setTimeout(r, 60000)); return createVideo(data); // Retry case 'VALIDATION_ERROR': console.error('Validation failed:', error.details); break; default: console.error('API error:', error.message); } } else { // Handle network errors console.error('Network error:', error); } throw error; } } ``` ### Retry Logic with Exponential Backoff ```javascript theme={null} async function makeRequestWithBackoff(url, options, maxRetries = 3) { let lastError; for (let attempt = 0; attempt < maxRetries; attempt++) { try { const response = await fetch(url, options); const data = await response.json(); // Success if (response.ok) { return data; } // Don't retry client errors (4xx except 429) if (response.status >= 400 && response.status < 500 && response.status !== 429) { throw new HookedAPIError(data.message, data.code, response.status, data.details); } // Retry server errors (5xx) and rate limits (429) lastError = new HookedAPIError(data.message, data.code, response.status, data.details); // Calculate backoff: 2^attempt * 1000ms (1s, 2s, 4s, ...) const backoffMs = Math.pow(2, attempt) * 1000; console.log(`Request failed, retrying in ${backoffMs}ms... (attempt ${attempt + 1}/${maxRetries})`); await new Promise(resolve => setTimeout(resolve, backoffMs)); } catch (error) { if (error instanceof HookedAPIError && error.statusCode < 500) { // Don't retry client errors throw error; } lastError = error; } } throw lastError; } // Usage try { const result = await makeRequestWithBackoff( 'https://api.hooked.so/v1/project/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.HOOKED_API_KEY }, body: JSON.stringify(videoData) } ); console.log('Video created:', result); } catch (error) { console.error('Failed after retries:', error); } ``` ## Debugging Tips Verify your API key is correct and active ```bash theme={null} # Test authentication curl -H "x-api-key: your_key" \ https://api.hooked.so/v1/avatar/list ``` Use a JSON validator to ensure your payload is valid ```javascript theme={null} // Add logging console.log('Request body:', JSON.stringify(data, null, 2)); ``` Log the full response to see what went wrong ```javascript theme={null} const response = await fetch(url, options); console.log('Status:', response.status); console.log('Headers:', Object.fromEntries(response.headers)); const text = await response.text(); console.log('Body:', text); ``` Check rate limit headers in responses ```javascript theme={null} console.log('Rate Limit:', response.headers.get('X-RateLimit-Limit')); console.log('Remaining:', response.headers.get('X-RateLimit-Remaining')); ``` ## Error Prevention Checklist **Before making API calls:** * [ ] API key is set and valid * [ ] All required fields are included * [ ] Field values match expected types and formats * [ ] Avatar/Voice/Template IDs exist and are accessible * [ ] Script length is within limits (\< 5000 characters) * [ ] Rate limits are being tracked * [ ] Webhook URL is publicly accessible (if used) * [ ] Error handling is implemented * [ ] Retry logic for transient errors ## Getting Help Contact support for assistance Get help from the community Report bugs and issues ## Next Steps Set up webhooks for reliable notifications Learn production-ready patterns # MCP Server Source: https://docs.hooked.so/guides/mcp-server Let AI agents create videos using the Model Context Protocol ## Overview The Hooked MCP Server (`@hooked-so/mcp`) lets AI agents — Claude, Cursor, ChatGPT, and any MCP-compatible client — create videos, browse avatars, and check status directly from a conversation. **What is MCP?** The [Model Context Protocol](https://modelcontextprotocol.io) is an open standard that lets AI assistants interact with external tools and APIs natively. ## Quick Setup Go to [hooked.so](https://hooked.so) → Settings → API Keys and generate a key. Paste the JSON config into your AI client (see below). Ask your AI to create a video. It handles everything. ## Claude Desktop Add this to your `claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "hooked": { "command": "npx", "args": ["-y", "@hooked-so/mcp"], "env": { "HOOKED_API_KEY": "your_api_key_here" } } } } ``` On macOS, the config file is at `~/Library/Application Support/Claude/claude_desktop_config.json` ## Cursor Add this to `.cursor/mcp.json` in your project root: ```json theme={null} { "mcpServers": { "hooked": { "command": "npx", "args": ["-y", "@hooked-so/mcp"], "env": { "HOOKED_API_KEY": "your_api_key_here" } } } } ``` ## Available Tools The MCP server exposes 10 tools: ### Video Creation | Tool | Description | | - | - | | `create_script_to_video` | Create a video from a script with an AI avatar | | `create_prompt_to_video` | AI generates script, visuals, and narration from a prompt | | `create_tiktok_slideshow` | Create TikTok-style slideshow from images and text | | `create_ugc_ad` | Create UGC-style product ad with AI presenter | ### Resources | Tool | Description | | - | - | | `list_avatars` | Browse 50+ realistic AI avatars | | `list_voices` | Browse AI voices in 30+ languages | | `list_music` | Browse background music tracks | ### Video Management | Tool | Description | | - | - | | `get_video` | Check video status and get download URL | | `list_videos` | List all your generated videos | ## Example Conversations Once connected, you can interact naturally: **Create a video:** > "Create a 30-second product video for my fitness app. Use a young female avatar with an energetic tone." **Browse resources:** > "What AI avatars do you have? Show me the female ones." **Check status:** > "What's the status of my latest video?" **Batch creation:** > "Create 3 variations of a product ad with different hooks and avatars." ## Tool Details ### create\_script\_to\_video ```json theme={null} { "script": "Your video script here...", "avatarId": "avatar_sophia_01", "voiceId": "voice_en_female_01", "musicId": "music_upbeat_01", "captionStyle": "karaoke", "webhook": "https://your-app.com/webhook" } ``` | Parameter | Type | Required | Description | | - | - | - | - | | `script` | string | Yes | The script the avatar will speak | | `avatarId` | string | Yes | Avatar ID from `list_avatars` | | `voiceId` | string | No | Voice ID from `list_voices` | | `musicId` | string | No | Music ID from `list_music` | | `captionStyle` | string | No | Caption style (e.g. `karaoke`, `word-by-word`) | | `webhook` | string | No | Webhook URL for completion notification | ### create\_prompt\_to\_video ```json theme={null} { "prompt": "A 30-second video about productivity tips for remote workers", "avatarId": "avatar_sophia_01", "webhook": "https://your-app.com/webhook" } ``` | Parameter | Type | Required | Description | | - | - | - | - | | `prompt` | string | Yes | Describe the video you want | | `avatarId` | string | No | Avatar ID (AI picks one if omitted) | | `voiceId` | string | No | Voice ID | | `webhook` | string | No | Webhook URL | ## Environment Variables | Variable | Required | Description | | - | - | - | | `HOOKED_API_KEY` | Yes | Your Hooked API key | | `HOOKED_API_URL` | No | Custom API base URL (default: `https://api.hooked.so`) | ## Troubleshooting Ensure `npx` is available in your PATH. Try running `npx @hooked-so/mcp` manually in a terminal to check for errors. Verify your `HOOKED_API_KEY` is correct and active. Generate a new key at hooked.so → Settings → API Keys. Restart your AI client after adding the config. Some clients require a full restart to detect new MCP servers. ## Next Steps Download the Hooked skill for Cursor and Claude Receive notifications when videos are ready # Personalization Source: https://docs.hooked.so/guides/personalization Create highly personalized videos that resonate with each viewer ## Overview Personalization is the key to creating engaging videos that connect with your audience. Hooked's template system allows you to dynamically replace variables to create unique content for each recipient. ## Variable Basics ### Defining Variables in Templates Variables are placeholders in your script surrounded by double curly braces: ``` Hi {{name}}, welcome to {{company}}! ``` ### Variable Replacement When generating from a template, provide values for each variable: ```javascript theme={null} { "variables": [ { "key": "name", "value": "Sarah" }, { "key": "company", "value": "TechCorp" } ] } ``` **Result:** `"Hi Sarah, welcome to TechCorp!"` ## Types of Personalization ### 1. Basic Information ```javascript Name & Company theme={null} { "name": "John Smith", "company": "Acme Corp", "role": "CEO" } ``` ```javascript Location theme={null} { "city": "San Francisco", "country": "United States", "timezone": "PST" } ``` ```javascript Contact Info theme={null} { "email": "john@acme.com", "phone": "+1-555-0123" } ``` ### 2. Metrics & Data ```javascript theme={null} // Personalized metrics { "name": "Sarah", "metric_value": "30%", "metric_name": "productivity increase", "time_saved": "10 hours per week", "roi": "$50,000 annually" } ``` **Script:** ``` Hi {{name}}! Companies like yours see a {{metric_value}} {{metric_name}}, saving {{time_saved}} and generating {{roi}} in additional revenue. ``` ### 3. Conditional Content While Hooked doesn't support conditional logic in templates directly, you can prepare the script before sending: ```javascript theme={null} function prepareScript(template, data) { let script = template; // Replace variables Object.entries(data).forEach(([key, value]) => { script = script.replace(new RegExp(`{{${key}}}`, 'g'), value); }); return script; } // Different scripts based on user segment const templates = { enterprise: "Hi {{name}}! As an enterprise customer, you get {{feature}}...", startup: "Hi {{name}}! Perfect for startups like {{company}}...", individual: "Hi {{name}}! Great for individuals looking to {{goal}}..." }; const script = prepareScript( templates[user.segment], { name: user.name, company: user.company } ); ``` ### 4. Industry-Specific ```javascript theme={null} // SaaS Example { "name": "Mike", "product_name": "Analytics Pro", "feature_1": "real-time dashboards", "feature_2": "custom reports", "feature_3": "API access", "trial_days": "14" } // E-commerce Example { "name": "Emma", "product_name": "Wireless Headphones", "discount": "20%", "price": "$79", "shipping": "free", "delivery_date": "Friday" } // Real Estate Example { "name": "David", "property_address": "123 Oak Street", "bedrooms": "3", "bathrooms": "2", "price": "$450,000", "square_feet": "2,100" } ``` ## Advanced Personalization Strategies ### Multi-Scene Personalization Different variables for each scene: ```javascript theme={null} { "type": "class", "templateId": "template_123", "scenes": [ { "script": "Hi {{name}}, I'm {{agent_name}} from {{company}}.", "variables": [ { "key": "name", "value": "Sarah" }, { "key": "agent_name", "value": "Jennifer" }, { "key": "company", "value": "RealtyPro" } ] }, { "script": "I found {{property_count}} properties that match your criteria in {{location}}.", "variables": [ { "key": "property_count", "value": "5" }, { "key": "location", "value": "downtown" } ] }, { "script": "The top property at {{address}} is {{price}} with {{beds}} bedrooms.", "variables": [ { "key": "address", "value": "123 Main St" }, { "key": "price", "value": "$550,000" }, { "key": "beds", "value": "3" } ] } ] } ``` ### Dynamic Content Selection ```javascript theme={null} // Prepare personalized content based on user data function personalizeContent(user, products) { // Select relevant products const recommended = products .filter(p => p.category === user.interests[0]) .slice(0, 3); // Build variables const variables = { name: user.name, interest: user.interests[0], product_1: recommended[0].name, price_1: recommended[0].price, product_2: recommended[1].name, price_2: recommended[1].price, product_3: recommended[2].name, price_3: recommended[2].price }; return variables; } const content = personalizeContent(user, availableProducts); // Generate video await api.generateFromTemplate('product_showcase', content); ``` ### Integration with CRM Data ```javascript theme={null} import { CRMClient } from './crm'; async function generatePersonalizedVideo(contactId) { const crm = new CRMClient(process.env.CRM_API_KEY); // Fetch contact data const contact = await crm.getContact(contactId); const deals = await crm.getDeals(contactId); const activities = await crm.getActivities(contactId); // Calculate personalized metrics const totalValue = deals.reduce((sum, d) => sum + d.amount, 0); const lastInteraction = activities[0]?.date || 'never'; const engagement = activities.length; // Prepare variables const variables = { name: contact.firstName, company: contact.company, total_value: formatCurrency(totalValue), open_deals: deals.filter(d => d.status === 'open').length, last_interaction: formatDate(lastInteraction), engagement_level: engagement > 10 ? 'high' : 'moderate' }; // Generate video return await api.generateFromTemplate( 'sales_followup_template', variables, { webhook: `https://mysite.com/webhook?contactId=${contactId}` } ); } ``` ### A/B Testing with Personalization ```javascript theme={null} // Test different personalization strategies const variants = { A: { greeting: "Hi {{name}}", value_prop: "save {{time}} per week", cta: "Start your free trial" }, B: { greeting: "Hey {{name}}", value_prop: "boost productivity by {{percentage}}", cta: "Get started now" } }; async function generateWithVariant(recipient, variant) { const template = variant === 'A' ? variants.A : variants.B; const variables = { name: recipient.name, time: "5 hours", percentage: "30%" }; const script = Object.entries(template) .reduce((s, [key, tpl]) => { return s.replace(`{{${key}}}`, tpl); }, originalScript); return await api.createVideo({ script, ...variables, metadata: { variant, recipientId: recipient.id } }); } // Split recipients into A/B groups recipients.forEach((recipient, index) => { const variant = index % 2 === 0 ? 'A' : 'B'; generateWithVariant(recipient, variant); }); ``` ## Personalization Best Practices ### 1. Natural Language Make personalization feel natural, not robotic ```javascript theme={null} // ❌ Awkward "Hi {{name}}, you live in {{city}}, {{state}}, {{country}} and work at {{company}}." // ✅ Natural "Hi {{name}}! I noticed you're based in {{city}}. Many {{company}} teams in your area have seen great results with our platform." ``` ### 2. Balance Personalization Too much personalization can feel creepy ```javascript theme={null} // ❌ Too much (creepy) { name: "John", linkedin_url: "linkedin.com/in/john", last_visited: "January 15 at 3:42 PM", pages_viewed: "pricing page 3 times", ip_address: "192.168.1.1" } // ✅ Just right { name: "John", company: "TechCorp", role: "Engineering Manager", interest: "DevOps solutions" } ``` ### 3. Provide Defaults ```javascript theme={null} function safePersonalize(template, data, defaults = {}) { const variables = { ...defaults, ...data }; let result = template; Object.entries(variables).forEach(([key, value]) => { const placeholder = new RegExp(`{{${key}}}`, 'g'); result = result.replace(placeholder, value || defaults[key] || ''); }); return result; } // Usage with defaults const script = safePersonalize( "Hi {{name}} from {{company}}!", { name: "Sarah" }, // company is missing { company: "your company" } // default fallback ); // Result: "Hi Sarah from your company!" ``` ### 4. Validate Data Quality ```javascript theme={null} function validatePersonalizationData(data, required) { const errors = []; // Check required fields required.forEach(field => { if (!data[field]) { errors.push(`Missing required field: ${field}`); } }); // Check data quality if (data.name && data.name.length < 2) { errors.push('Name too short'); } if (data.email && !data.email.includes('@')) { errors.push('Invalid email format'); } // Check for placeholder text const placeholders = ['test', 'example', 'demo', 'lorem']; Object.values(data).forEach(value => { if (placeholders.some(p => String(value).toLowerCase().includes(p))) { errors.push(`Placeholder text detected: ${value}`); } }); return { valid: errors.length === 0, errors }; } // Before generating const validation = validatePersonalizationData(recipientData, ['name', 'company']); if (!validation.valid) { console.error('Invalid data:', validation.errors); // Handle error } ``` ## Real-World Examples ### Sales Outreach ```javascript theme={null} const salesTemplate = { name: "{{prospect_name}}", company: "{{company}}", mutual_connection: "{{connection_name}}", pain_point: "{{specific_challenge}}", solution: "{{your_solution}}", case_study: "{{similar_company}}", result: "{{quantified_result}}", cta: "{{call_to_action}}" }; // Example data const prospectData = { prospect_name: "Jennifer", company: "DataCorp", connection_name: "Mike Chen", specific_challenge: "scaling their data pipeline", your_solution: "automated ETL workflows", similar_company: "TechStart Inc", quantified_result: "reduced processing time by 60%", call_to_action: "Let's schedule a 15-minute demo" }; ``` ### Customer Onboarding ```javascript theme={null} const onboardingTemplate = { name: "{{customer_name}}", plan: "{{subscription_plan}}", feature_1: "{{key_feature_1}}", feature_2: "{{key_feature_2}}", feature_3: "{{key_feature_3}}", support_person: "{{account_manager}}", next_step: "{{onboarding_step}}" }; ``` ### Event Invitations ```javascript theme={null} const eventTemplate = { name: "{{attendee_name}}", event: "{{event_name}}", date: "{{event_date}}", time: "{{event_time}}", location: "{{venue}}", speaker: "{{keynote_speaker}}", topic: "{{session_topic}}", discount: "{{early_bird_code}}" }; ``` ## Personalization Checklist Before launching your personalized video campaign: **Data Quality** * [ ] All required variables are present * [ ] No placeholder or test data * [ ] Names are properly capitalized * [ ] Emails are valid * [ ] Phone numbers are formatted correctly **Content** * [ ] Variables sound natural in sentences * [ ] No obvious template text * [ ] Proper grammar and punctuation * [ ] Appropriate tone for audience * [ ] Clear call-to-action **Technical** * [ ] Template tested with sample data * [ ] Webhook configured correctly * [ ] Error handling in place * [ ] Logging for failed generations ## Next Steps Scale personalization to hundreds of recipients Production-ready tips and patterns # Scenes & Variables Source: https://docs.hooked.so/guides/scenes-and-variables Master the template system with scenes and variable replacement ## Overview Templates in Hooked use a powerful **scene-based** system that allows you to create personalized videos at scale. Understanding scenes and variables is key to leveraging templates effectively. ## What are Scenes? Scenes are individual segments of your video, each with its own script and customizable overlay elements. Think of them as slides or sections in your video presentation. ### Scene Structure ```json theme={null} { "script": "Hello! Welcome to our presentation.", "variables": [ { "key": "name", "value": "John Doe" }, { "key": "company", "value": "Acme Corp" }, { "key": "title", "value": "Marketing Director" } ] } ``` Each scene contains: * **script**: The narration text that the avatar will speak * **variables**: Array of key-value pairs for text overlays displayed on the slide (names, titles, company names, etc.) ## What are Variables? Variables are dynamic text elements that appear as overlays on your video slides. They allow you to personalize each video by replacing placeholder text with specific values. **Common uses:** * Names and titles * Company names * Product information * Dates and numbers * Custom messages ### Variable Names **✅ Valid Variable Names:** ``` name first_name customer_id subscription_plan trial_days product_name company title ``` **Rules:** * Lowercase letters * Use underscores for spaces * Descriptive names * No special characters **❌ Invalid Variable Names:** ``` Name // Capital letters first name // Spaces customer-id // Hyphens 1st_customer // Starts with number special!char // Special characters ``` ## Creating Multi-Scene Templates Templates can have multiple scenes, each with different overlay text: ```javascript theme={null} const scenes = [ { // Scene 1: Introduction slide script: "Hello! Thank you for joining our platform.", variables: [ { key: 'name', value: 'Sarah Johnson' }, { key: 'company', value: 'TechStart Inc' }, { key: 'title', value: 'Marketing Director' } ] }, { // Scene 2: Product information slide script: "Let me tell you about our product features and pricing.", variables: [ { key: 'plan_name', value: 'Professional Plan' }, { key: 'price', value: '$99/month' }, { key: 'features', value: 'Unlimited videos, Priority support' } ] }, { // Scene 3: Call to action slide script: "Ready to get started? Let's connect!", variables: [ { key: 'website', value: 'www.techstart.com' }, { key: 'email', value: 'sarah@techstart.com' }, { key: 'phone', value: '+1 (555) 123-4567' } ] } ]; ``` ## Variable Rules ### 1. Template Defines Required Variables The template creator defines which variables are needed for each scene. When generating a video, you must provide values for all required variables. ```javascript theme={null} // ❌ Will fail - template requires 'company' but it's missing { script: "Welcome to our presentation!", variables: [ { key: 'name', value: 'John' } // Missing 'company' variable that the template expects ] } // ✅ Correct - all required variables provided { script: "Welcome to our presentation!", variables: [ { key: 'name', value: 'John Doe' }, { key: 'company', value: 'Acme Corp' }, { key: 'title', value: 'Sales Director' } ] } ``` ### 2. Variables Are Case-Sensitive Variable names must match exactly: ```javascript theme={null} // ❌ Wrong - case mismatch { variables: [ { key: 'name', value: 'John' } // lowercase 'name' ] } // But template expects 'Name' (capital N) // ✅ Correct - exact match { variables: [ { key: 'Name', value: 'John' } // matches template's 'Name' ] } ``` ### 3. Variables Appear as Text Overlays Variables are displayed as text elements on the slide, not spoken in the narration: ```javascript theme={null} { // This is what the avatar says script: "Let me introduce our featured product.", // These appear as text on the slide variables: [ { key: 'product_name', value: 'Pro Analytics Dashboard' }, { key: 'price', value: '$99/month' }, { key: 'tagline', value: 'Data-driven decisions made easy' } ] } ``` ### 4. Empty Values Are Allowed Variables can have empty strings if the template allows it: ```javascript theme={null} { script: "Here's our contact information.", variables: [ { key: 'phone', value: '' }, // Empty if not available { key: 'email', value: 'contact@company.com' }, { key: 'website', value: 'www.company.com' } ] } ``` ## Complete Example Here's a full example of generating a video from a template with multiple scenes: ```javascript theme={null} const videoRequest = { type: "class", templateId: "template_abc123", title: "Onboarding Video for Sarah", aspectRatio: "ratio_16_9", caption: { enabled: true, preset: "wrap1", alignment: "bottom" }, scenes: [ { script: "Welcome to {{company}}, {{name}}!", variables: [ { key: "company", value: "TechStart" }, { key: "name", value: "Sarah" } ] }, { script: "You're on the {{plan}} plan with {{feature_count}} features.", variables: [ { key: "plan", value: "Professional" }, { key: "feature_count", value: "50+" } ] }, { script: "Your account manager is {{manager}}. Reach out at {{email}}!", variables: [ { key: "manager", value: "Mike Chen" }, { key: "email", value: "mike@techstart.com" } ] } ], webhook: "https://myapp.com/webhook?user=sarah" }; // Make API request const response = await fetch('https://api.hooked.so/v1/project/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.HOOKED_API_KEY }, body: JSON.stringify(videoRequest) }); const result = await response.json(); console.log('Project ID:', result.data.projectId); ``` ## Best Practices ### Use Descriptive Variable Names ```javascript theme={null} // ❌ Bad - unclear {{x}}, {{val}}, {{a}} // ✅ Good - self-documenting {{customer_name}}, {{product_price}}, {{discount_percentage}} ``` ### Keep Scripts Natural ```javascript theme={null} // ❌ Awkward - too many variables close together "Hi {{title}} {{first_name}} {{last_name}} from {{company_name}} in {{city}}, {{state}}!" // ✅ Natural - well-spaced "Hi {{name}}! I see you're from {{company}} in {{location}}." ``` ### Validate Before Sending Always check that: 1. All required variables are present 2. Variable names match exactly (case-sensitive) 3. Values are properly formatted (strings, numbers, etc.) 4. No extra variables are provided that don't exist in the script ```javascript theme={null} function validateScene(scene) { // Extract variables from script const scriptVars = [...scene.script.matchAll(/\{\{(\w+)\}\}/g)] .map(match => match[1]); // Get provided variable keys const providedKeys = scene.variables.map(v => v.key); // Check for missing variables const missing = scriptVars.filter(v => !providedKeys.includes(v)); if (missing.length > 0) { throw new Error(`Missing variables: ${missing.join(', ')}`); } return true; } // Usage try { validateScene(myScene); // Proceed with API call } catch (error) { console.error('Validation failed:', error.message); } ``` ## Common Patterns ### Personalized Greeting ```javascript theme={null} { script: "Hi {{first_name}}! Great to see you again.", variables: [ { key: "first_name", value: "Sarah" } ] } ``` ### Product Information ```javascript theme={null} { script: "The {{product_name}} costs {{price}} and includes {{features}}.", variables: [ { key: "product_name", value: "Pro Plan" }, { key: "price", value: "$99/month" }, { key: "features", value: "unlimited videos and priority support" } ] } ``` ### Call to Action ```javascript theme={null} { script: "Ready to get started? Visit {{website}} or call {{phone}}.", variables: [ { key: "website", value: "www.yoursite.com" }, { key: "phone", value: "1-800-555-0123" } ] } ``` ## Troubleshooting ### Error: "Variable not found in script" **Problem:** You provided a variable that doesn't exist in the script. **Solution:** Remove unused variables or add them to the script. ```javascript theme={null} // ❌ Error - 'age' variable not in script { script: "Hello {{name}}!", variables: [ { key: "name", value: "John" }, { key: "age", value: "30" } // Not used ] } // ✅ Fixed { script: "Hello {{name}}!", variables: [ { key: "name", value: "John" } ] } ``` ### Error: "Missing required variable" **Problem:** A `{{variable}}` in the script doesn't have a value. **Solution:** Provide all required variables. ```javascript theme={null} // ❌ Error - missing 'company' { script: "Hello {{name}} from {{company}}!", variables: [ { key: "name", value: "John" } ] } // ✅ Fixed { script: "Hello {{name}} from {{company}}!", variables: [ { key: "name", value: "John" }, { key: "company", value: "Acme Corp" } ] } ``` ## Next Steps See scenes and variables in action Advanced variable strategies Generate at scale with templates Complete code example # Skill File Source: https://docs.hooked.so/guides/skill-file Teach your AI agent to create videos with the Hooked skill ## Overview A Skill File is a markdown file that teaches AI agents (Claude, Cursor) how to use the Hooked Video API. Once installed, your agent knows every endpoint, authentication method, and best practice — no manual prompting required. **MCP vs Skill**: The [MCP Server](/guides/mcp-server) gives your agent direct API access (tool calls). The Skill File gives your agent knowledge about the API (documentation). For the best experience, use both together. ## Download Direct download from GitHub View source and contribute Or use curl: ```bash theme={null} curl -o SKILL.md https://raw.githubusercontent.com/hooked-so/hooked-skill/main/SKILL.md ``` ## Installation ### Cursor (Project-level) Place the skill in your project's `.cursor/skills/` directory: ```bash theme={null} mkdir -p .cursor/skills/hooked-video-api curl -o .cursor/skills/hooked-video-api/SKILL.md \ https://raw.githubusercontent.com/hooked-so/hooked-skill/main/SKILL.md ``` Cursor will automatically detect and use the skill when relevant. ### Cursor (Global) For all projects, place it in your home directory: ```bash theme={null} mkdir -p ~/.cursor/skills/hooked-video-api curl -o ~/.cursor/skills/hooked-video-api/SKILL.md \ https://raw.githubusercontent.com/hooked-so/hooked-skill/main/SKILL.md ``` ### Claude (via Project Knowledge) Upload `SKILL.md` as a project knowledge file in your Claude project settings. ## What the Skill Teaches The skill file contains: | Section | What the Agent Learns | | - | - | | **Authentication** | How to use `x-api-key` header | | **Base URL** | `https://api.hooked.so/v1` | | **Video Creation** | All POST endpoints for script-to-video, prompt-to-video, slideshows, UGC ads | | **Resources** | GET endpoints for avatars, voices, music | | **Video Status** | How to check and retrieve videos | | **Webhooks** | How webhook payloads work | | **Code Examples** | TypeScript examples for common operations | ## Example Prompts Once the skill is installed, try these: ``` "Create a 30-second UGC ad for my skincare product using avatar Sophia" ``` ``` "Make a TikTok slideshow about 5 productivity tips with AI-generated images" ``` ``` "What avatars are available? Show me female avatars for tech content" ``` ``` "Generate a product demo video from https://mystore.com/product" ``` ``` "Check the status of video vid_abc123 and download it when ready" ``` ## Skill + MCP Together For maximum capability, combine the Skill File with the [MCP Server](/guides/mcp-server): * **Skill File** gives your agent knowledge (understands the API, knows best practices) * **MCP Server** gives your agent tools (can actually make API calls) Together, your agent can reason about what video to create AND execute the creation — fully autonomous. ## Updating The skill is versioned with the Hooked repository. To update: ```bash theme={null} curl -o .cursor/skills/hooked-video-api/SKILL.md \ https://raw.githubusercontent.com/hooked-so/hooked-skill/main/SKILL.md ``` ## Next Steps Give your agent direct API access with MCP Create your first video in 5 minutes # Webhooks Source: https://docs.hooked.so/guides/webhooks Real-time notifications when your videos are ready ## Overview Webhooks allow you to receive real-time notifications when your video processing completes, instead of polling the API repeatedly. **Recommended**: Always use webhooks for production applications. Polling is inefficient and can hit rate limits. ## How Webhooks Work When creating a video, include your webhook URL ```javascript theme={null} { "type": "class", "script": "Your script...", "webhook": "https://yoursite.com/api/webhook" } ``` Hooked processes your video (typically 2-5 minutes) When complete, Hooked sends a POST request to your webhook URL with video details Your endpoint must return a 200 status code to confirm receipt ## Webhook Payload When your video is ready, you'll receive this payload: ```json theme={null} { "event": "project.completed", "projectId": "proj_abc123", "status": "completed", "video": { "id": "video_xyz789", "url": "https://cdn.hooked.so/videos/xyz789.mp4", "duration": 45, "thumbnail": "https://cdn.hooked.so/thumbnails/xyz789.jpg", "aspectRatio": "9:16", "resolution": "1080x1920" }, "createdAt": "2024-01-15T10:30:00Z", "completedAt": "2024-01-15T10:33:00Z" } ``` ### Payload Fields Event type: `project.completed` or `project.failed` The project/video ID Status: `completed` or `failed` Video details (only present if status is `completed`) Direct download URL for the video (expires in 7 days) Video duration in seconds URL to video thumbnail image Error message (only present if status is `failed`) ## Implementing a Webhook Endpoint ### Node.js (Express) ```javascript theme={null} const express = require('express'); const app = express(); app.use(express.json()); app.post('/api/webhook', async (req, res) => { const { event, projectId, status, video } = req.body; console.log(`Received webhook: ${event} for ${projectId}`); if (event === 'project.completed' && status === 'completed') { // Video is ready! console.log('Video URL:', video.url); console.log('Duration:', video.duration, 'seconds'); try { // Save to database await saveVideoToDatabase({ projectId, videoUrl: video.url, thumbnail: video.thumbnail, duration: video.duration }); // Notify user await notifyUser(projectId, video.url); // Download and store (optional) await downloadAndStore(video.url, projectId); } catch (error) { console.error('Error processing webhook:', error); // Still return 200 to prevent retries for application errors } } else if (event === 'project.failed') { console.error('Video failed:', req.body.message); await notifyUserOfFailure(projectId); } // IMPORTANT: Always return 200 res.status(200).send('OK'); }); app.listen(3000); ``` ### Python (FastAPI) ```python theme={null} from fastapi import FastAPI, Request import httpx app = FastAPI() @app.post("/api/webhook") async def webhook_handler(request: Request): payload = await request.json() event = payload.get("event") project_id = payload.get("projectId") status = payload.get("status") print(f"Received webhook: {event} for {project_id}") if event == "project.completed" and status == "completed": video = payload.get("video") # Save to database await save_video_to_db({ "project_id": project_id, "video_url": video["url"], "thumbnail": video["thumbnail"], "duration": video["duration"] }) # Notify user await notify_user(project_id, video["url"]) elif event == "project.failed": await notify_failure(project_id, payload.get("message")) # Always return 200 return {"status": "ok"} ``` ### Next.js API Route ```typescript theme={null} import { NextRequest, NextResponse } from 'next/server'; export async function POST(req: NextRequest) { try { const payload = await req.json(); const { event, projectId, status, video } = payload; if (event === 'project.completed' && status === 'completed') { // Process completed video await processCompletedVideo({ projectId, videoUrl: video.url, thumbnail: video.thumbnail, duration: video.duration }); } return NextResponse.json({ received: true }, { status: 200 }); } catch (error) { console.error('Webhook error:', error); // Still return 200 to prevent retries return NextResponse.json({ error: 'Internal error' }, { status: 200 }); } } ``` ## Webhook Security ### Verify Webhook Source Always verify webhooks are coming from Hooked. Check the request origin and consider implementing HMAC signature verification. ```javascript theme={null} // Verify request came from Hooked's IP ranges const allowedIPs = ['52.1.2.3', '54.2.3.4']; // Example IPs app.post('/api/webhook', (req, res) => { const clientIP = req.ip || req.headers['x-forwarded-for']; if (!allowedIPs.includes(clientIP)) { return res.status(403).send('Forbidden'); } // Process webhook... }); ``` ### Use HTTPS **Always use HTTPS** for your webhook endpoints to ensure data is encrypted in transit. ``` ✅ https://yoursite.com/webhook ❌ http://yoursite.com/webhook ``` ### Secret Tokens Include a secret token in your webhook URL for additional security: ```javascript theme={null} // When creating video { "webhook": "https://yoursite.com/webhook?token=your_secret_token" } // In your webhook handler app.post('/webhook', (req, res) => { const token = req.query.token; if (token !== process.env.WEBHOOK_SECRET) { return res.status(401).send('Unauthorized'); } // Process webhook... }); ``` ## Retry Logic If your endpoint doesn't return a 200 status code, Hooked will retry: * **1st retry**: After 1 minute * **2nd retry**: After 5 minutes * **3rd retry**: After 15 minutes After 3 failed attempts, no more retries will be made. ### Best Practices for Retries ```javascript theme={null} app.post('/webhook', async (req, res) => { // Return 200 FIRST res.status(200).send('OK'); // Then process asynchronously setImmediate(async () => { try { await processWebhook(req.body); } catch (error) { // Handle error without affecting the HTTP response console.error('Async processing error:', error); await logToErrorTracking(error); } }); }); ``` ## Testing Webhooks ### Local Development with ngrok ```bash theme={null} # Install ngrok npm install -g ngrok # Start your local server node server.js # Running on localhost:3000 # Create tunnel ngrok http 3000 # Use the ngrok URL as your webhook # https://abc123.ngrok.io/api/webhook ``` ### Webhook Testing Sites Get a temporary webhook URL for testing [https://webhook.site](https://webhook.site) Inspect webhook payloads [https://requestbin.com](https://requestbin.com) ### Test Webhook Endpoint ```bash theme={null} # Test your webhook manually curl -X POST https://yoursite.com/api/webhook \ -H "Content-Type: application/json" \ -d '{ "event": "project.completed", "projectId": "test_123", "status": "completed", "video": { "url": "https://example.com/test.mp4", "duration": 30, "thumbnail": "https://example.com/thumb.jpg" } }' ``` ## Common Issues **Possible causes:** * Webhook URL not publicly accessible * Firewall blocking Hooked's IP addresses * Endpoint returning non-200 status code **Solution:** Test with webhook.site first, then gradually move to your endpoint **Possible causes:** * Your endpoint took too long to respond * Endpoint returned non-200, triggering retry **Solution:** Implement idempotency using projectId ```javascript theme={null} const processedProjects = new Set(); app.post('/webhook', (req, res) => { const { projectId } = req.body; if (processedProjects.has(projectId)) { return res.status(200).send('Already processed'); } processedProjects.add(projectId); // Process webhook... }); ``` **Problem:** Your endpoint is slow, causing timeouts **Solution:** Return 200 immediately, process asynchronously ```javascript theme={null} app.post('/webhook', async (req, res) => { // Respond immediately res.status(200).send('OK'); // Process in background processWebhookAsync(req.body); }); ``` ## Webhook Events Currently supported events: | Event | Description | | - | - | | `project.completed` | Video successfully generated and ready to download | | `project.failed` | Video generation failed due to an error | ## Next Steps Learn how to handle errors gracefully See webhooks in action # Introduction Source: https://docs.hooked.so/introduction Welcome to the Hooked API - Create AI-powered videos programmatically with realistic avatars and natural voices Hooked API Hooked API ## Welcome to Hooked API The Hooked API allows you to create professional AI-powered videos programmatically. Generate UGC-style videos, educational content, and marketing materials with realistic avatars and natural-sounding voices. Create your first video in minutes Get your API key and start building Explore all available endpoints See real-world implementation examples ## Key Features Access our library of professional AI avatars or create custom ones. Each avatar speaks your script on camera, with voice, lip movement and gestures generated natively by the video model. Choose from hundreds of ElevenLabs voices in multiple languages and accents, or clone custom voices for your brand. Create different types of videos: educational content (Class), UGC ads, Script to Videos, hook + demo videos, and TikTok slideshows. Add background music, captions, b-roll footage, and customize every aspect of your video to match your brand. ## How It Works List available avatars, voices, music, and formats to build your video Send a project creation request with your script and selected resources Get notified when your video is ready (typically 2-5 minutes) Access your generated video via the provided URL ## Popular Use Cases Create UGC-style ads with authentic-looking presenters Generate tutorial and course videos at scale Produce consistent content for TikTok, Instagram, and YouTube Showcase products with engaging demonstrations Create clear, concise explainer content Generate authentic-looking customer testimonials ## API Endpoint All API requests should be made to: ``` https://api.hooked.so/v1 ``` ## Support Need help? We're here for you: * **Email**: [support@hooked.so](mailto:support@hooked.so) * **Dashboard**: [hooked.so](https://hooked.so) * **Discord**: Join our community Follow our quickstart guide to create your first video # Quickstart Source: https://docs.hooked.so/quickstart Create your first AI video in under 5 minutes ## Get Your API Key First, you'll need an API key to authenticate your requests. Go to [hooked.so](https://hooked.so) and create an account or log in Go to Settings → API Keys in your dashboard Click "Generate New API Key" and save it securely Keep your API key secret! Never commit it to version control or share it publicly. ## Your First Video Let's create a simple educational video using the Class project type. ### Step 1: List Available Avatars First, let's see which avatars are available: ```bash theme={null} curl -X GET "https://api.hooked.so/v1/avatar/list" \ -H "x-api-key: your_api_key_here" ``` ```json theme={null} { "success": true, "data": { "avatars": [ { "id": "avatar_sarah_01", "name": "Sarah", "gender": "Female", "age": "Young", "type": "Realistic", "thumbnail": "https://...", "isCustom": false } ], "total": 50 } } ``` ### Step 2: List Available Voices Now let's get the available voices: ```bash theme={null} curl -X GET "https://api.hooked.so/v1/voice/list?language=English&gender=Female" \ -H "x-api-key: your_api_key_here" ``` ```json theme={null} { "success": true, "data": { "voices": [ { "id": "1004", "voiceId": "tzX5paJ07p5hyWFcU3uG", "name": "Jude", "gender": "Male", "language": "English", "accent": "British" } ], "total": 150 } } ``` ### Step 3: Create Your Video Now that you have an avatar ID and voice ID, create your video: ```bash theme={null} curl -X POST "https://api.hooked.so/v1/project/create" \ -H "x-api-key: your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "type": "class", "name": "My First Tutorial", "script": "Hello! Welcome to this tutorial. Today, I will show you how to use our amazing product. It is very simple and easy to understand. Let me guide you through the key features.", "avatarId": "avatar_sarah_01", "voiceId": "tzX5paJ07p5hyWFcU3uG", "webhook": "https://your-domain.com/webhook" }' ``` ```json theme={null} { "success": true, "message": "Project created successfully", "data": { "projectId": "proj_abc123", "status": "processing", "estimatedTime": "3-5 minutes" } } ``` ### Step 4: Receive Webhook Notification When your video is ready, we'll send a POST request to your webhook URL: ```json theme={null} { "event": "project.completed", "projectId": "proj_abc123", "status": "completed", "video": { "id": "video_xyz789", "url": "https://cdn.hooked.so/videos/xyz789.mp4", "duration": 45, "thumbnail": "https://cdn.hooked.so/thumbnails/xyz789.jpg" }, "createdAt": "2024-01-15T10:30:00Z", "completedAt": "2024-01-15T10:33:00Z" } ``` Congratulations! You've just created your first AI video with Hooked. ## Next Steps Enhance your videos with background music and captions See complete code examples for different use cases Create personalized videos at scale with templates Learn the end-to-end video creation process ## Common Issues Make sure your API key is correct and included in the `x-api-key` header. API keys can be managed in your dashboard settings. Double-check that the IDs exist by listing resources first. IDs are case-sensitive. Scripts should be between 10 and 5000 characters. Very short scripts may not generate meaningful videos. Ensure your webhook URL is publicly accessible and returns a 200 status code. Test it with a tool like webhook.site first. ## Tips for Better Videos **Write natural scripts**: Write as if you're speaking to a friend. Avoid overly formal or robotic language. **Match voice to avatar**: Choose voices that match your avatar's age and gender for more authentic results. **Add pauses**: Use commas and periods naturally to create appropriate pauses in speech. **Test webhooks**: Use services like webhook.site to test your webhook integration before going live.