curl -X POST "https://api.hooked.so/v1/avatar/cm7a1v9t20003ab12cd34ef56/reaction" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "reaction": "surprise" }'
const avatarId = "cm7a1v9t20003ab12cd34ef56";
const response = await fetch(`https://api.hooked.so/v1/avatar/${avatarId}/reaction`, {
method: "POST",
headers: { "x-api-key": process.env.HOOKED_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ reaction: "surprise" }),
});
const { data } = await response.json();
// Poll GET /v1/avatar/{avatarId} until this reaction is COMPLETED, then use data.id as hook-demo avatarId
console.log(data.id, data.status);
import os
import requests
avatar_id = "cm7a1v9t20003ab12cd34ef56"
response = requests.post(
f"https://api.hooked.so/v1/avatar/{avatar_id}/reaction",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
json={"reaction": "surprise"},
)
data = response.json()["data"]
print(data["id"], data["status"])
{
"success": true,
"message": "Reaction generation started",
"data": {
"id": "cm7a2r4k80011ab12gh78ij90",
"reaction": "surprise",
"status": "PROCESSING",
"videoUrl": null,
"thumbnailUrl": null,
"createdAt": "2026-10-05T09:20:03.000Z",
"avatarId": "cm7a1v9t20003ab12cd34ef56",
"usedCredits": 52
}
}
{
"success": false,
"message": "customPrompt: Required for the custom reaction"
}
Create Reaction
Make a Hook + Demo reaction clip of one of your avatars
curl -X POST "https://api.hooked.so/v1/avatar/cm7a1v9t20003ab12cd34ef56/reaction" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "reaction": "surprise" }'
const avatarId = "cm7a1v9t20003ab12cd34ef56";
const response = await fetch(`https://api.hooked.so/v1/avatar/${avatarId}/reaction`, {
method: "POST",
headers: { "x-api-key": process.env.HOOKED_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ reaction: "surprise" }),
});
const { data } = await response.json();
// Poll GET /v1/avatar/{avatarId} until this reaction is COMPLETED, then use data.id as hook-demo avatarId
console.log(data.id, data.status);
import os
import requests
avatar_id = "cm7a1v9t20003ab12cd34ef56"
response = requests.post(
f"https://api.hooked.so/v1/avatar/{avatar_id}/reaction",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
json={"reaction": "surprise"},
)
data = response.json()["data"]
print(data["id"], data["status"])
{
"success": true,
"message": "Reaction generation started",
"data": {
"id": "cm7a2r4k80011ab12gh78ij90",
"reaction": "surprise",
"status": "PROCESSING",
"videoUrl": null,
"thumbnailUrl": null,
"createdAt": "2026-10-05T09:20:03.000Z",
"avatarId": "cm7a1v9t20003ab12cd34ef56",
"usedCredits": 52
}
}
{
"success": false,
"message": "customPrompt: Required for the custom reaction"
}
Overview
Makes a reaction of one of your team’s own avatars, the same way Studio → Actors → Reactions does in the dashboard: a still of the avatar making the face, animated into a short silent clip (5-6 seconds). The call answers202 at once; the reaction takes about 1-3 minutes. Follow it in Get Avatar under reactions.
Once the reaction is COMPLETED, its id is an avatarId for Create Hook Demo, and it is listed in GET /v1/catalog/reactions next to the library reactions. A reaction that is not finished is refused by Hook Demo with 400.
| Field | Values |
|---|---|
reaction | surprise, shock, excitement, smirk, confusion, disgust, happy, laughing, whispers, nervous, frustrated, crying, turn_reveal, side_look, double_take, lean_in, or custom with your own customPrompt |
model | Image model for the still: an id of GET /v1/catalog/image-models. Default gpt_image_2 |
videoModel | grok_imagine_video (default, cheapest), seedance_2_0_fast, seedance_2_0_mini, seedance_2_0, kling_3_0, wan_2_7_i2v |
COMPLETED. Library avatars already have their reactions in the catalog; this endpoint is for your own avatars.
curl -X POST "https://api.hooked.so/v1/avatar/cm7a1v9t20003ab12cd34ef56/reaction" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "reaction": "surprise" }'
const avatarId = "cm7a1v9t20003ab12cd34ef56";
const response = await fetch(`https://api.hooked.so/v1/avatar/${avatarId}/reaction`, {
method: "POST",
headers: { "x-api-key": process.env.HOOKED_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ reaction: "surprise" }),
});
const { data } = await response.json();
// Poll GET /v1/avatar/{avatarId} until this reaction is COMPLETED, then use data.id as hook-demo avatarId
console.log(data.id, data.status);
import os
import requests
avatar_id = "cm7a1v9t20003ab12cd34ef56"
response = requests.post(
f"https://api.hooked.so/v1/avatar/{avatar_id}/reaction",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
json={"reaction": "surprise"},
)
data = response.json()["data"]
print(data["id"], data["status"])
{
"success": true,
"message": "Reaction generation started",
"data": {
"id": "cm7a2r4k80011ab12gh78ij90",
"reaction": "surprise",
"status": "PROCESSING",
"videoUrl": null,
"thumbnailUrl": null,
"createdAt": "2026-10-05T09:20:03.000Z",
"avatarId": "cm7a1v9t20003ab12cd34ef56",
"usedCredits": 52
}
}
{
"success": false,
"message": "customPrompt: Required for the custom reaction"
}
Errors
| Status | When |
|---|---|
| 400 | Invalid JSON, reaction: Required, an unknown reaction, custom without customPrompt (up to 1,000 characters), an invalid model or videoModel, or Avatar is not ready (status …) |
| 401 | Missing or invalid API key |
| 402 | BYOK without its keys, no live plan, or not enough credits. Nothing was created or charged |
| 403 | The team does not have the Hooked app product |
| 404 | Avatar not found: not one of your team’s own avatars |
Authorizations
Headers
Makes the request safe to retry. 1-255 printable ASCII characters, one per operation (your job id, or a UUID you store), reused on every retry. Within 24 hours the same key with the same body answers with the first response and the header Idempotent-Replayed: true, without running or charging again. Keys are scoped to your team and the endpoint. 5xx answers and refusals before anything ran (401, 402, 403, 409, 429) are not kept. See Idempotency.
1 - 255^[\x20-\x7E]+$Path Parameters
The id of one of your team's own avatars (from List Avatars with type=custom, or a create call). Library avatars are not accepted here.
Body
The face to make. custom takes your own customPrompt.
surprise, shock, excitement, smirk, confusion, disgust, happy, laughing, whispers, nervous, frustrated, crying, turn_reveal, side_look, double_take, lean_in, custom What the avatar does, for reaction: custom (required then). Ignored for the other reactions.
1000Image model for the still: an id of GET /v1/catalog/image-models.
Video model for the clip, cheapest first.
grok_imagine_video, seedance_2_0_fast, seedance_2_0_mini, seedance_2_0, kling_3_0, wan_2_7_i2v