# 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
## 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.