curl -X POST "https://api.hooked.so/v1/voice/speech-to-speech" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"audioUrl": "https://cdn.example.com/recordings/take-3.m4a",
"audio": { "stability": 0.6 }
}'
const response = await fetch("https://api.hooked.so/v1/voice/speech-to-speech", {
method: "POST",
headers: { "x-api-key": process.env.HOOKED_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
voiceId: "tzX5paJ07p5hyWFcU3uG",
audioUrl: "https://cdn.example.com/recordings/take-3.m4a",
}),
});
const { data } = await response.json();
console.log(data.url, data.creditsUsed);
import os
import requests
response = requests.post(
"https://api.hooked.so/v1/voice/speech-to-speech",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
json={
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"audioUrl": "https://cdn.example.com/recordings/take-3.m4a",
},
)
data = response.json()["data"]
print(data["url"], data["creditsUsed"])
{
"success": true,
"message": "Speech generated",
"data": {
"voiceoverId": "cm4xa5f6g0003ab12cd34ef56",
"url": "https://files.hooked.so/team/public/media/voiceover/voiceover-1759660800000.mp3?X-Amz-Signature=...",
"urlExpiresInSeconds": 600,
"sourceDurationSeconds": 42.3,
"creditsUsed": 5
}
}
{
"success": false,
"message": "audioUrl: The audio is too long (412 s, at most 300 s)"
}
Speech to Speech
Re-voice a recording with another voice, keeping its words and timing
curl -X POST "https://api.hooked.so/v1/voice/speech-to-speech" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"audioUrl": "https://cdn.example.com/recordings/take-3.m4a",
"audio": { "stability": 0.6 }
}'
const response = await fetch("https://api.hooked.so/v1/voice/speech-to-speech", {
method: "POST",
headers: { "x-api-key": process.env.HOOKED_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
voiceId: "tzX5paJ07p5hyWFcU3uG",
audioUrl: "https://cdn.example.com/recordings/take-3.m4a",
}),
});
const { data } = await response.json();
console.log(data.url, data.creditsUsed);
import os
import requests
response = requests.post(
"https://api.hooked.so/v1/voice/speech-to-speech",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
json={
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"audioUrl": "https://cdn.example.com/recordings/take-3.m4a",
},
)
data = response.json()["data"]
print(data["url"], data["creditsUsed"])
{
"success": true,
"message": "Speech generated",
"data": {
"voiceoverId": "cm4xa5f6g0003ab12cd34ef56",
"url": "https://files.hooked.so/team/public/media/voiceover/voiceover-1759660800000.mp3?X-Amz-Signature=...",
"urlExpiresInSeconds": 600,
"sourceDurationSeconds": 42.3,
"creditsUsed": 5
}
}
{
"success": false,
"message": "audioUrl: The audio is too long (412 s, at most 300 s)"
}
Overview
Takes a recording and makes another voice say it: the words, pacing and intonation stay, the voice changes. It is Speech to speech in the dashboard editor, with the same settings and price. Record yourself reading a script, then re-voice it with a library voice or one of your own clones.- Send a public
httpslink to the recording inaudioUrl: MP3, WAV, M4A, AAC, OGG, FLAC or WEBM, up to 5 minutes and 10 MB. It is downloaded in the request; local, private-network and plainhttpaddresses are refused with400. voiceIdis any voice from List Voices.audiotakes the same settings as Generate Speech:speed,stability,similarityBoost.
url links to it for 10 minutes: download it right away.curl -X POST "https://api.hooked.so/v1/voice/speech-to-speech" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"audioUrl": "https://cdn.example.com/recordings/take-3.m4a",
"audio": { "stability": 0.6 }
}'
const response = await fetch("https://api.hooked.so/v1/voice/speech-to-speech", {
method: "POST",
headers: { "x-api-key": process.env.HOOKED_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
voiceId: "tzX5paJ07p5hyWFcU3uG",
audioUrl: "https://cdn.example.com/recordings/take-3.m4a",
}),
});
const { data } = await response.json();
console.log(data.url, data.creditsUsed);
import os
import requests
response = requests.post(
"https://api.hooked.so/v1/voice/speech-to-speech",
headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
json={
"voiceId": "tzX5paJ07p5hyWFcU3uG",
"audioUrl": "https://cdn.example.com/recordings/take-3.m4a",
},
)
data = response.json()["data"]
print(data["url"], data["creditsUsed"])
{
"success": true,
"message": "Speech generated",
"data": {
"voiceoverId": "cm4xa5f6g0003ab12cd34ef56",
"url": "https://files.hooked.so/team/public/media/voiceover/voiceover-1759660800000.mp3?X-Amz-Signature=...",
"urlExpiresInSeconds": 600,
"sourceDurationSeconds": 42.3,
"creditsUsed": 5
}
}
{
"success": false,
"message": "audioUrl: The audio is too long (412 s, at most 300 s)"
}
Errors
| Status | Message / code | Cause |
|---|---|---|
| 400 | Invalid JSON body | The body is not JSON. |
| 400 | audioUrl: Must be a public https URL | Not a URL, not https, or a local or private address. |
| 400 | audioUrl: Not an audio file (...) | The link is not an audio file. |
| 400 | audioUrl: The audio is too long / The file is over 10 MB | Longer than 5 minutes or larger than 10 MB. |
| 400 | voiceId: Voice "<id>" not found. | Not a library voice nor one of your team’s voices. |
| 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.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.
100Public https link to the recording file itself: MP3, WAV, M4A, AAC, OGG, FLAC or WEBM, up to 5 minutes and 10 MB.
2000Voice 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