curl -X POST "https://api.hooked.so/v1/voice/speech" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"text": "Stop scrolling. This serum changed my morning routine in one week.",
"audio": { "speed": 1.05, "stability": 0.5, "similarityBoost": 0.75 },
"name": "Serum hook"
}'
const response = await fetch("https://api.hooked.so/v1/voice/speech", {
method: "POST",
headers: { "x-api-key": process.env.HOOKED_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
voiceId: "tzX5paJ07p5hyWFcU3uG",
text: "Stop scrolling. This serum changed my morning routine in one week.",
}),
});
const { data } = await response.json();
// The link works for 10 minutes
const audio = await fetch(data.url).then((res) => res.arrayBuffer());
import os
import requests
response = requests.post(
"https://api.hooked.so/v1/voice/speech",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
json={
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"text": "Stop scrolling. This serum changed my morning routine in one week.",
},
)
data = response.json()["data"]
with open("speech.mp3", "wb") as file:
file.write(requests.get(data["url"]).content)
{
"success": true,
"message": "Speech generated",
"data": {
"voiceoverId": "cm4xa3d4e0002ab12cd34ef56",
"url": "https://files.hooked.so/team/public/media/voiceover/voiceover-1759660800000.mp3?X-Amz-Signature=...",
"urlExpiresInSeconds": 600,
"durationSeconds": 4.6,
"characters": 66,
"creditsUsed": 5
}
}
{
"success": false,
"message": "voiceId: Voice \"abc123\" not found."
}
Generate Speech
Turn a text into a voiceover with any voice
curl -X POST "https://api.hooked.so/v1/voice/speech" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"text": "Stop scrolling. This serum changed my morning routine in one week.",
"audio": { "speed": 1.05, "stability": 0.5, "similarityBoost": 0.75 },
"name": "Serum hook"
}'
const response = await fetch("https://api.hooked.so/v1/voice/speech", {
method: "POST",
headers: { "x-api-key": process.env.HOOKED_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
voiceId: "tzX5paJ07p5hyWFcU3uG",
text: "Stop scrolling. This serum changed my morning routine in one week.",
}),
});
const { data } = await response.json();
// The link works for 10 minutes
const audio = await fetch(data.url).then((res) => res.arrayBuffer());
import os
import requests
response = requests.post(
"https://api.hooked.so/v1/voice/speech",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
json={
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"text": "Stop scrolling. This serum changed my morning routine in one week.",
},
)
data = response.json()["data"]
with open("speech.mp3", "wb") as file:
file.write(requests.get(data["url"]).content)
{
"success": true,
"message": "Speech generated",
"data": {
"voiceoverId": "cm4xa3d4e0002ab12cd34ef56",
"url": "https://files.hooked.so/team/public/media/voiceover/voiceover-1759660800000.mp3?X-Amz-Signature=...",
"urlExpiresInSeconds": 600,
"durationSeconds": 4.6,
"characters": 66,
"creditsUsed": 5
}
}
{
"success": false,
"message": "voiceId: Voice \"abc123\" not found."
}
Overview
Reads a text aloud with any voice from List Voices: a library voice or one of your own. It is the text-to-speech voiceover of the dashboard editor, with the same model, settings and price. Use it to preview a voice on your own script, or to get a voiceover file for your own editing.- Up to 1,200 characters per call, the editor’s limit.
audiotunes the voice:speed(0.7 to 1.2, default 1.0),stability(0 to 1, default 0.5) andsimilarityBoost(0 to 1, default 0.75). These are the same settings the create endpoints take asaudio.- The answer comes when the audio is ready, usually in a few seconds.
url links to it for 10 minutes: download it right away. There is no endpoint to fetch it again later.curl -X POST "https://api.hooked.so/v1/voice/speech" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"text": "Stop scrolling. This serum changed my morning routine in one week.",
"audio": { "speed": 1.05, "stability": 0.5, "similarityBoost": 0.75 },
"name": "Serum hook"
}'
const response = await fetch("https://api.hooked.so/v1/voice/speech", {
method: "POST",
headers: { "x-api-key": process.env.HOOKED_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
voiceId: "tzX5paJ07p5hyWFcU3uG",
text: "Stop scrolling. This serum changed my morning routine in one week.",
}),
});
const { data } = await response.json();
// The link works for 10 minutes
const audio = await fetch(data.url).then((res) => res.arrayBuffer());
import os
import requests
response = requests.post(
"https://api.hooked.so/v1/voice/speech",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
json={
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"text": "Stop scrolling. This serum changed my morning routine in one week.",
},
)
data = response.json()["data"]
with open("speech.mp3", "wb") as file:
file.write(requests.get(data["url"]).content)
{
"success": true,
"message": "Speech generated",
"data": {
"voiceoverId": "cm4xa3d4e0002ab12cd34ef56",
"url": "https://files.hooked.so/team/public/media/voiceover/voiceover-1759660800000.mp3?X-Amz-Signature=...",
"urlExpiresInSeconds": 600,
"durationSeconds": 4.6,
"characters": 66,
"creditsUsed": 5
}
}
{
"success": false,
"message": "voiceId: Voice \"abc123\" not found."
}
Errors
| Status | Message / code | Cause |
|---|---|---|
| 400 | Invalid JSON body | The body is not JSON. |
| 400 | text: Must be at most 1200 characters | The text is empty or too long. |
| 400 | audio.speed: ... | A setting out of range, or an unknown key in audio. |
| 400 | voiceId: Voice "<id>" not found. | Not a library voice nor one of your team’s voices. |
| 400 | voiceId: This voice ... | One of your clones made in the other key mode (it is listed with unavailable: true). |
| 401 | code: "not_authenticated" | Missing or invalid x-api-key. |
| 402 | errorCode: "INSUFFICIENT_CREDITS" | Not enough credits; creditsNeeded says how many. |
| 402 | code: "subscription_required" / "missing_credentials" | Managed team without a live plan, or BYOK team without an ElevenLabs key. |
| 403 | Insufficient storage... | Your plan’s storage is full. Nothing is charged. |
| 500 | Failed to generate the speech | The voice provider failed. The credits are given back. |
Idempotency-Key header to retry safely after a timeout: the same key answers with the first voiceover instead of generating (and charging) it again.Authorizations
Headers
Makes the request safe to retry. 1-255 printable ASCII characters, one per operation (your job id, or a UUID you store), reused on every retry. Within 24 hours the same key with the same body answers with the first response and the header Idempotent-Replayed: true, without running or charging again. Keys are scoped to your team and the endpoint. 5xx answers and refusals before anything ran (401, 402, 403, 409, 429) are not kept. See Idempotency.
1 - 255^[\x20-\x7E]+$Body
A voice from List Voices: its id (a library voice, or one of your team's own voices). Another team's voice, an unknown id, or one of your clones that is unavailable in your current key mode answers 400.
100What the voice says. Up to 1,200 characters (trimmed).
1 - 1200Voice settings, as in the editor. Each one is optional; a missing one takes its default. An unknown key answers 400.
Show child attributes
Show child attributes
Name of the voiceover in the editor. A generated file name when omitted.
100