Speech & Sound
Text-to-speech and sound effect generation with the Kolbo API.
Convert text to speech or generate sound effects using ElevenLabs and other providers.
Model identifiers are Kolbo-specific — always fetch available models from GET /api/v1/models?type=text_to_speech (or type=text_to_sound) before specifying a model. The legacy aliases type=speech / type=sound also work. Omitting model uses the endpoint default (eleven_v3 for speech, elevenlabs-sound-effects-v1 for sound); passing auto, smart-select, smart_select, smartselect, or auto-select resolves to the same default. model must be a string — an array returns 400 INVALID_MODEL.
All generation endpoints accept an optional
project_idbody field that routes the output into a specific project. See Projects.
List Voices
Discover available voices before generating speech. Returns both platform preset voices and your custom cloned/designed voices.
Endpoint
GET /api/v1/voicesQuery Parameters
All three filters are case-insensitive exact matches (not substring matches), applied in memory.
| Parameter | Type | Required | Description |
|---|---|---|---|
provider | string | No | Filter by provider, e.g. elevenlabs, google. Applies to both preset and custom voices |
language | string | No | Filter by language name or language code, e.g. english or en-US. Preset voices only — custom voices are not filtered by language |
gender | string | No | Filter by gender, e.g. female, male. Preset voices only — custom voices always report gender: null and so are never excluded by this filter |
language and gender filter the preset catalog only. Your custom cloned voices are always appended to the result regardless of those two filters.
Example
curl "https://api.kolbo.ai/api/v1/voices?gender=female" \
-H "X-API-Key: kolbo_live_..."Response
{
"success": true,
"voices": [
{
"voice_id": "EXAVITQu4vr4xnSDxMaL",
"name": "Rachel",
"provider": "elevenlabs",
"language": "English",
"language_code": "en-US",
"gender": "female",
"accent": "American",
"preview_url": "https://...",
"thumbnail": "https://...",
"description": "Calm, conversational narrator",
"styles": ["conversational", "calm"],
"v3_optimized": true,
"custom": false
},
{
"voice_id": "custom_68f2c1a9b4e5d6f7a8b9c0d1",
"name": "My Cloned Voice",
"provider": "elevenlabs",
"language": "auto",
"language_code": null,
"gender": null,
"accent": null,
"preview_url": null,
"thumbnail": null,
"description": null,
"styles": [],
"v3_optimized": false,
"voice_type": "clone",
"voice_model_id": null,
"custom": true
}
],
"count": 152
}Custom voices are returned only when their status is ready and they have not been deleted. voice_type and voice_model_id appear on custom entries only.
Use the voice_id from this response as the voice parameter in the speech endpoint. You can also pass a voice name and the API resolves it automatically — see Voice resolution.
Text to Speech
Endpoint
POST /api/v1/generate/speechRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Text to convert to speech. Must be a non-empty string, otherwise 400 INVALID_PROMPT |
voice | string | No | Voice ID or display name from GET /api/v1/voices (default: "Rachel"). Required when model is cartesia_sonic_3_5 — omitting it returns 400 VOICE_REQUIRED |
model | string | No | TTS model identifier from GET /api/v1/models?type=text_to_speech (default: eleven_v3). Advisory — for catalog voices the synthesis model comes from the voice record, not from this field. See the callout below |
language | string | No | Language code, e.g. "en-US", "he-IL" (default: "en-US"). Send "auto" to detect the language from text server-side. Full language names are also accepted and mapped to locales: Hebrew, English, French, Spanish, German, Italian, Arabic — and Multilingual, which maps to auto |
style_instructions | string | No | Google / Gemini voices only. Free-form voice direction, e.g. "whisper conspiratorially". Truncated to the first 500 characters |
speaking_speed | number | No | Speech speed on a 0.5 (slow) – 2.0 (fast) scale, 1.0 = normal. Never rejected — each provider remaps it: ElevenLabs onto its own 0.7–1.2 speed, Cartesia clamps to 0.6–1.5, Google/Gemini turns it into a natural-language direction |
project_id | string | No | Route the output into a specific project. Omit for the default "API Generations" project |
model does not pick the synthesis engine for catalog voices. Every voice in GET /api/v1/voices carries its own model, and the TTS engine uses that one. What model actually does is: scope name → id resolution to that model's voices (step 3 below), gate the two Cartesia guards (VOICE_REQUIRED / VOICE_NOT_FOUND), and get echoed back as model in the "generation started" response. For a custom_<id> cloned voice it is only a starting point — the stored voice's provider overrides it. The model on the completed status response is the model that actually ran, and it can differ from the one you sent.
Voice resolution
voice is resolved server-side before the request reaches the TTS engine:
- Values that look like a raw provider id pass through untouched — 15+ alphanumerics (ElevenLabs style) or a UUID (Cartesia style).
- A
custom_<id>value (a cloned voice fromGET /v1/voices) also passes through untouched. - Anything else is treated as a display name and looked up case-insensitively: first in the global catalog scoped to the
modelyou passed, then in the global catalog unscoped, then among your own cloned voices (resolving tocustom_<id>). - If no match is found, the string is passed through unchanged. A
dna_<visualDnaId>value (a Visual DNA character voice) is resolved at this point by the TTS engine. Anything else that is not a real catalogvoice_idfails there with400 Voice "<value>" not found.
Cartesia is strict: with model: "cartesia_sonic_3_5", a name that does not match a Cartesia voice returns 400 VOICE_NOT_FOUND instead of silently falling back to an ElevenLabs voice. The check fires before the custom-voice lookup in step 3, so a cloned Cartesia voice cannot be referenced by name on this model — pass its custom_<id> from GET /v1/voices instead. Cartesia also caps text at 5,000 characters per request (TEXT_TOO_LONG), measured against the trimmed text of the voice's resolved model. Pass the model alongside the voice name whenever you rely on name resolution, so a name that exists on several providers resolves to the one you meant.
VOICE_REQUIRED and VOICE_NOT_FOUND are emitted before the SDK response wrapper is installed, so their bodies are { "status": false, "code": ..., "message": ... } — not the usual { "success": false, "error": ..., "code": ... }. Branch on the HTTP status and code, and read the text from message as well as error.
Expressive & style controls (optional, provider-specific)
Each voice belongs to a provider; the engine ignores any control that does not apply to the chosen voice's provider, so you can pass only what you need.
| Field | Type | Provider | Description |
|---|---|---|---|
selected_style | string | DeepDub / MiniMax | Preset delivery style. DeepDub accepts exactly reading, angry, breathy, panic, amused, sad, whisper, singing, shout, scream, mumbling — anything else silently falls back to reading. On MiniMax voices it is used as the emotion source when emotion is not sent |
emotion | string | MiniMax / Cartesia | MiniMax: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper — an unrecognised value falls back to auto-detect. Cartesia: neutral, calm, content, happy, excited, sad, angry, scared — anything else is rejected with INVALID_EMOTION |
similarity_boost | number | ElevenLabs | Voice similarity 0–1 (default 0.75). Outside the range returns 400 |
style | number | ElevenLabs | Style exaggeration 0–1 (default 0.5). Outside the range returns 400 |
variance | number | DeepDub | Voice variance 0–1. Outside the range returns 400 |
tempo | number | DeepDub | Tempo multiplier 0–2. Outside the range returns 400 |
promptBoost | boolean | DeepDub | Prompt-fidelity boost |
seed | number | DeepDub / Zonos | Reproducibility seed |
accentControl | object | DeepDub | Accent steering { accentBaseLocale, accentLocale, accentRatio }. Both locales are required when the object is sent, and accentRatio must be 0–1, otherwise 400 |
voiceTitle | string | DeepDub | Display title for a custom/cloned voice |
minimax_pitch | number | MiniMax | Pitch -12–12 (default 0) |
minimax_vol | number | MiniMax | Volume 0–10 (default 1) |
minimax_intensity | number | MiniMax | Voice intensity |
minimax_timbre | number | MiniMax | Voice timbre |
style_instructions_label | string | Google / Gemini | Metadata only — the label of the style preset the caller picked. Does not change the audio |
style_instructions_preset_id | string | Google / Gemini | Metadata only — the id of the style preset the caller picked. Does not change the audio |
The list above is an explicit allowlist: any other field in the request body is dropped before the TTS engine sees it.
Two exceptions worth knowing: use_speaker_boost is accepted by the request layer but has no effect — the ElevenLabs path hardcodes speaker boost on. And stability cannot be sent directly; it is derived from speaking_speed.
Examples
Simple (Recommended)
Omit model to use the server default. You can use a voice name directly:
curl -X POST https://api.kolbo.ai/api/v1/generate/speech \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"text": "Welcome to Kolbo AI, the all-in-one creative platform.",
"voice": "Rachel"
}'With Specific Model
To choose a specific model, first fetch identifiers from GET /api/v1/models?type=text_to_speech, then pass the identifier value:
curl -X POST https://api.kolbo.ai/api/v1/generate/speech \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"text": "Welcome to Kolbo AI, the all-in-one creative platform.",
"voice": "Rachel",
"model": "eleven_v3"
}'With Language
curl -X POST https://api.kolbo.ai/api/v1/generate/speech \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"text": "Welcome to Kolbo AI, the all-in-one creative platform.",
"voice": "Rachel",
"language": "en-US"
}'Response
POST /v1/generate/speech is asynchronous and fire-and-forget: it answers 202 Accepted as soon as the job is queued and never contains audio.
Polling is the only completion mechanism. Kolbo never calls you back: there is no webhook, no callback_url field on this request body, and no route delivers outbound notifications. Socket.IO events are the web app's internal transport and are not part of the API contract — API-key generations are registered so the shared progress emitter drops their events, and the few emit sites that bypass that check are undocumented, unversioned and unsafe to build on. After the POST, loop against GET /api/v1/generate/{generation_id}/status until state is completed, failed or cancelled. Full contract: Polling & Cancellation.
Generation Started
{
"success": true,
"generation_id": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "speech",
"model": "eleven_v3",
"credits_charged": 5,
"poll_url": "/v1/generate/68f2c1a9b4e5d6f7a8b9c0d1/status",
"poll_interval_hint": 8,
"session_id": "68f2c1a9b4e5d6f7a8b9c0d2",
"project_id": "68f2c1a9b4e5d6f7a8b9c0d3"
}| Field | Type | Notes |
|---|---|---|
generation_id | string | Mongo ObjectId. This is what you poll and cancel with. |
type | string | Always "speech" |
model | string | The identifier you sent, or the default eleven_v3 |
credits_charged | number | Speech is one of only two endpoints that return a real estimate here — the character-based pre-calculation. Nothing is charged yet: credits are deducted only after a successful synthesis, and the authoritative figure is credits_used on the completed status. |
poll_url | string | The status path without the /api prefix. Prepend https://api.kolbo.ai/api. |
poll_interval_hint | number | Suggested seconds between polls — 8 for speech |
session_id / project_id | string | Where the generation lives in the Kolbo app |
Completed Status
GET /api/v1/generate/{generation_id}/status
{
"success": true,
"generation_id": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "speech",
"state": "completed",
"progress": 100,
"result": {
"urls": ["https://media.kolbo.ai/tts/.../speech.mp3"],
"voice": "EXAVITQu4vr4xnSDxMaL",
"duration": null,
"model": "eleven_v3",
"created_at": "2026-07-20T14:20:00Z"
},
"credits_used": 6,
"credits_breakdown": [
{ "model": "eleven_v3", "amount": 6, "base": 6, "final": 6, "duration_multiplier": null, "pricing": null }
]
}The output lives in result.urls — always an array of strings with exactly one entry. Read urls[0].
result (and credits_used / credits_breakdown) appear only under state: "completed"; a processing body has exactly five keys and no result at all. There is no partial or streaming read.
result.voice is the resolved voice id, not the name you sent. result.model is the model that actually synthesised the audio — for a catalog voice this is the voice's own model, which may differ from the model echoed in the "generation started" response. result.duration is null in practice on this path: the TTS record does not persist an output length, so measure the file yourself if you need it.
Failure and cancellation
A failed synthesis is still an HTTP 200 with success: true — the failure is in state. Note that on this endpoint the failure writer stores no message, so error reads the generic "Generation failed" and every failure.* sub-field is null; the one exception is the 15-minute server timeout, which does report a real message. cancelled carries neither result nor error, so treat all three terminal states explicitly. Credits are deducted only on success, so a failed synthesis costs nothing. See Polling & Cancellation.
JavaScript Example
const KOLBO_API_KEY = "kolbo_live_..."; // Replace with your API key
const BASE_URL = "https://api.kolbo.ai/api";
async function generateSpeech() {
const response = await fetch(`${BASE_URL}/v1/generate/speech`, {
method: "POST",
headers: {
"X-API-Key": KOLBO_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Welcome to Kolbo AI, the all-in-one creative platform.",
voice: "Rachel",
}),
});
const started = await response.json();
if (!started.success) throw new Error(started.error);
const result = await pollUntilDone(started.generation_id, started.poll_interval_hint);
if (result.state !== "completed") throw new Error(result.error || result.state);
console.log("Audio URL:", result.result.urls[0]); // urls is always an array
return result.result;
}
const TERMINAL = new Set(["completed", "failed", "cancelled"]);
// Minimal loop. A production client should also handle transient HTTP errors and
// an overall timeout — see /docs/developer-api/polling-and-cancellation.
async function pollUntilDone(generationId, intervalSeconds) {
while (true) {
await new Promise((r) => setTimeout(r, intervalSeconds * 1000));
const status = await fetch(
`${BASE_URL}/v1/generate/${generationId}/status`,
{ headers: { "X-API-Key": KOLBO_API_KEY } }
).then((r) => r.json());
if (TERMINAL.has(status.state)) return status;
}
}
generateSpeech().catch(console.error);Python Example
import requests
import time
KOLBO_API_KEY = "kolbo_live_..." # Replace with your API key
BASE_URL = "https://api.kolbo.ai/api"
response = requests.post(
f"{BASE_URL}/v1/generate/speech",
headers={"X-API-Key": KOLBO_API_KEY},
json={
"text": "Welcome to Kolbo AI, the all-in-one creative platform.",
"voice": "Rachel",
},
)
started = response.json()
if not started.get("success"):
raise Exception(started.get("error", "Request failed"))
TERMINAL = {"completed", "failed", "cancelled"}
def poll_until_done(generation_id, interval_seconds):
"""Minimal loop. Add transient-error retries and an overall timeout for
production — see /docs/developer-api/polling-and-cancellation."""
while True:
time.sleep(interval_seconds)
status = requests.get(
f"{BASE_URL}/v1/generate/{generation_id}/status",
headers={"X-API-Key": KOLBO_API_KEY},
).json()
if status["state"] in TERMINAL:
return status
result = poll_until_done(started["generation_id"], started["poll_interval_hint"])
if result["state"] != "completed":
raise Exception(result.get("error", result["state"]))
print("Audio URL:", result["result"]["urls"][0]) # urls is always a listCredits
Speech credits are character-based on the trimmed text: ceil(text.trim().length / 100) x model.credit, where model.credit is the credit field from GET /api/v1/models?type=text_to_speech.
For example, a 250-character text with a model that costs 1 credit per 100 chars: ceil(250 / 100) x 1 = 3 credits.
Sound Effects
Endpoint
POST /api/v1/generate/soundRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Description of the sound effect. Must be a non-empty string, otherwise 400 INVALID_PROMPT |
model | string | No | Model identifier from GET /api/v1/models?type=text_to_sound (default: elevenlabs-sound-effects-v1) |
duration | number | No | Duration in seconds. Omit for automatic duration. Not clamped server-side — the value is forwarded to the provider as-is, so an out-of-range request fails at the provider. Read min_output_duration / max_output_duration / default_duration from the model entry in GET /api/v1/models?type=text_to_sound to stay inside the model's range. On per-second-billed models, omitting it makes the credit pre-check assume default_duration (falling back to 8s) |
prompt_influence | number | No | ElevenLabs: prompt guidance 0–1 (default 0.5). Higher = more literal |
project_id | string | No | Route the output into a specific project. Omit for the default "API Generations" project |
Advanced controls (optional, provider-specific)
The engine ignores any control that does not apply to the chosen model.
| Field | Type | Provider | Description |
|---|---|---|---|
cfg_strength | number | FAL Stable Audio / mmaudio | Classifier-free guidance strength |
sound_loop | boolean | Kie | Generate a seamlessly looping sound |
sound_tempo | number | Kie | Tempo control |
sound_key | string | Kie | Musical key / scale |
seed_voice | string | FAL Seed-Audio | Voice to use |
seed_speed | number | FAL Seed-Audio | Speed multiplier 0.5–2.0 |
seed_volume | number | FAL Seed-Audio | Volume 0–1 |
seed_pitch | number | FAL Seed-Audio | Pitch shift in semitones |
seed_reference_audio_urls | string[] | FAL Seed-Audio | Reference audio URLs. Only the first 3 are forwarded; the rest are dropped |
seed_reference_image_url | string | FAL Seed-Audio | Reference image URL |
audio_format | string | Sonilo | Output container/codec. The accepted set is fixed: wav | mp3 | aac | flac. An unrecognised value is not an error — it silently falls back to default_output_audio_format (or aac). Read entirely only on models that advertise a non-empty output_audio_formats; ignored on every other model |
Examples
Simple (Recommended)
curl -X POST https://api.kolbo.ai/api/v1/generate/sound \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{"prompt": "Thunder clap followed by heavy rain"}'With Specific Model
To choose a specific model, first fetch identifiers from GET /api/v1/models?type=text_to_sound, then pass the identifier value:
curl -X POST https://api.kolbo.ai/api/v1/generate/sound \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Thunder clap followed by heavy rain",
"model": "elevenlabs-sound-effects-v1"
}'With Duration
curl -X POST https://api.kolbo.ai/api/v1/generate/sound \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Gentle ocean waves on a sandy beach",
"duration": 10
}'Response
POST /v1/generate/sound is asynchronous and fire-and-forget: it answers 202 Accepted as soon as the job is queued and never contains audio.
Polling is the only completion mechanism. Kolbo never calls you back: there is no webhook, no callback_url field on this request body, and no route delivers outbound notifications. Socket.IO events are the web app's internal transport and are not part of the API contract — API-key generations are registered so the shared progress emitter drops their events, and the few emit sites that bypass that check are undocumented, unversioned and unsafe to build on. After the POST, loop against GET /api/v1/generate/{generation_id}/status until state is completed, failed or cancelled. Full contract: Polling & Cancellation.
Generation Started
{
"success": true,
"generation_id": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "sound",
"model": "elevenlabs-sound-effects-v1",
"credits_charged": 7,
"poll_url": "/v1/generate/68f2c1a9b4e5d6f7a8b9c0d1/status",
"poll_interval_hint": 5,
"session_id": "68f2c1a9b4e5d6f7a8b9c0d2",
"project_id": "68f2c1a9b4e5d6f7a8b9c0d3"
}| Field | Type | Notes |
|---|---|---|
generation_id | string | Mongo ObjectId. This is what you poll and cancel with. |
type | string | Always "sound" |
model | string | The identifier you sent, or the default elevenlabs-sound-effects-v1 |
credits_charged | number | Sound is one of only two endpoints that return a real estimate here. Nothing is charged yet: credits are deducted only after a successful generation, and the authoritative figure is credits_used on the completed status. |
poll_url | string | The status path without the /api prefix. Prepend https://api.kolbo.ai/api. |
poll_interval_hint | number | Suggested seconds between polls — 5 for sound, the only endpoint that uses this value |
session_id / project_id | string | Where the generation lives in the Kolbo app |
Completed Status
GET /api/v1/generate/{generation_id}/status
{
"success": true,
"generation_id": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "sound",
"state": "completed",
"progress": 100,
"result": {
"urls": ["https://media.kolbo.ai/sound/.../sfx.mp3"],
"duration": 8,
"prompt_used": "Thunder clap followed by heavy rain",
"model": "elevenlabs-sound-effects-v1",
"created_at": "2026-07-20T14:20:00Z"
},
"credits_used": 4,
"credits_breakdown": [
{ "model": "elevenlabs-sound-effects-v1", "amount": 4, "base": 4, "final": 4, "duration_multiplier": null, "pricing": null }
]
}The output lives in result.urls — always an array of strings with exactly one entry. Read urls[0].
result (and credits_used / credits_breakdown) appear only under state: "completed"; a processing body has exactly five keys and no result at all.
result.duration is the length you requested, not the measured length of the file — when you omit duration (auto mode) it is null. result.prompt_used is your original prompt, before any translation.
Failure and cancellation
A failed generation is still an HTTP 200 with success: true — the failure is in state, with the provider's message in error and failure.message. failure.code carries the provider error code or the literal GENERATION_ERROR (GENERATION_TIMEOUT on the 15-minute server deadline); failure.category, retryable, severity and provider are always null on this endpoint. cancelled carries neither result nor error, so treat all three terminal states explicitly. Credits are deducted only on success, so a failed generation costs nothing. See Polling & Cancellation.
JavaScript Example
const KOLBO_API_KEY = "kolbo_live_..."; // Replace with your API key
const BASE_URL = "https://api.kolbo.ai/api";
async function generateSound() {
const response = await fetch(`${BASE_URL}/v1/generate/sound`, {
method: "POST",
headers: {
"X-API-Key": KOLBO_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "Thunder clap followed by heavy rain",
}),
});
const started = await response.json();
if (!started.success) throw new Error(started.error);
// pollUntilDone() from the Text to Speech example above — same status route
// and state machine, and poll_interval_hint is 5 seconds here instead of 8.
const result = await pollUntilDone(started.generation_id, started.poll_interval_hint);
if (result.state !== "completed") throw new Error(result.error || result.state);
console.log("Sound URL:", result.result.urls[0]);
return result.result;
}
generateSound().catch(console.error);Python Example
import requests
KOLBO_API_KEY = "kolbo_live_..." # Replace with your API key
BASE_URL = "https://api.kolbo.ai/api"
started = requests.post(
f"{BASE_URL}/v1/generate/sound",
headers={"X-API-Key": KOLBO_API_KEY},
json={
"prompt": "Thunder clap followed by heavy rain",
},
).json()
if not started.get("success"):
raise Exception(started.get("error", "Request failed"))
# poll_until_done() from the Text to Speech example above — same status route
# and state machine, and poll_interval_hint is 5 seconds here instead of 8.
result = poll_until_done(started["generation_id"], started["poll_interval_hint"])
if result["state"] != "completed":
raise Exception(result.get("error", result["state"]))
print("Sound URL:", result["result"]["urls"][0])Voices
Clone, import, and delete your own custom voices. Cloned voices appear in GET /v1/voices with custom: true and can be used with the speech endpoint.
These field names are camelCase, unlike the snake_case body fields on the generation endpoints. They are passed through to the underlying voice controller unchanged.
Clone a Voice
POST /api/v1/voices/clonemultipart/form-data only — the handler reads uploaded file buffers.
| Field | Type | Required | Description |
|---|---|---|---|
voiceName | string | Yes | Name for the cloned voice. A blank/whitespace value returns 400 |
audioFile | file | Yes | The voice sample. Missing file returns 400. Route cap: 25 MB (multer memory storage) |
provider | string | No | One of deepdub, elevenlabs, minimax, zonos, cartesia. Default: deepdub. Any other value returns 400 |
language | string | No | Language hint. Auto-detected when omitted |
promptText | string | No | MiniMax clone_prompt text |
promptAudioFile | file | No | MiniMax clone_prompt reference audio |
avatarFile | file | No | Avatar image for the voice |
project_id | string | No | Project to associate the voice with. Omit for the default "API Generations" project |
Sample duration limits (validated after decode, before cloning):
| Provider | Min | Max | Notes |
|---|---|---|---|
elevenlabs | 2s | 180s | |
deepdub | 2s | 300s | The sample is trimmed to the first 6 seconds during processing |
zonos | 1s | 60s | Additional 10 MB file cap |
cartesia | 2s | 60s | Additional 10 MB file cap |
minimax | 2s | — | No provider-specific duration ceiling |
Files under 1 KB or with zero bytes are rejected as empty/corrupt; anything over 200 MB is rejected outright (the 25 MB route cap bites first on this endpoint). Cloning charges credits (provider-dependent).
curl -X POST https://api.kolbo.ai/api/v1/voices/clone \
-H "X-API-Key: kolbo_live_..." \
-F "[email protected]" \
-F "voiceName=Narrator" \
-F "provider=elevenlabs"{
"success": true,
"voice": {
"id": "",
"name": "Narrator",
"provider": "elevenlabs",
"language": "english",
"preview_audio_url": null
}
}The id and preview_audio_url fields in this response are currently always empty — the SDK response shaper reads keys the underlying voice controller does not return. To get the new voice's id, call GET /api/v1/voices after cloning and take the voice_id (already in custom_<id> form) of the entry whose name matches. You can also just pass the voice name to the speech endpoint.
Note that provider on the stored voice is the backend provider, not always what you sent: zonos is persisted as fal-zonos.
Import an ElevenLabs Voice
POST /api/v1/voices/import-elevenlabsCopies a voice from the ElevenLabs Voice Library into your Kolbo voices.
| Field | Type | Required | Description |
|---|---|---|---|
elevenLabsVoiceId | string | Yes | Presence is enforced by the SDK layer — omitting it returns 400 |
voiceId | string | Yes | The ElevenLabs voice ID, or its share URL. This is the field the import actually reads. shareUrl or original_voice_id work as equivalents |
voiceName | string | No | Name for the imported voice. Falls back to the library's own name, then to Imported Voice <timestamp> |
public_user_id | string | No | The upstream owner id. Resolved automatically from a share URL or a shared-voice lookup when omitted |
project_id | string | No | Project to associate the voice with |
elevenLabsVoiceId gates the request but is not the field the importer reads. Send the id in both elevenLabsVoiceId and voiceId (or shareUrl) — with elevenLabsVoiceId alone the request fails with 400 INVALID_SHARE_URL.
Failure modes, by HTTP status: 400 no id readable (INVALID_SHARE_URL) or the voice is private / removed / not a public-library voice (VOICE_NOT_IMPORTABLE); 404 no public voice matches the id (VOICE_NOT_FOUND); 409 you already imported this voice (DUPLICATE_IMPORT) or the ElevenLabs library slot limit was reached (ELEVENLABS_SLOT_LIMIT); 502 ElevenLabs unreachable or returned no id (IMPORT_FAILED / IMPORT_NO_ID).
The /v1/voices/* endpoints return errors as { "success": false, "error": "<message>" } — the internal codes named above are not included in the SDK response body. Branch on the HTTP status and the message text, not on a code field.
On success the response is { "success": true, "voice": { "id": "", "name": ..., "provider": "elevenlabs-import", "elevenlabs_voice_id": null } } — id and elevenlabs_voice_id are empty for the same shaper reason as the clone endpoint. Resolve the real id via GET /api/v1/voices.
curl -X POST https://api.kolbo.ai/api/v1/voices/import-elevenlabs \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"elevenLabsVoiceId": "21m00Tcm4TlvDq8ikWAM",
"voiceId": "21m00Tcm4TlvDq8ikWAM"
}'Delete a Voice
DELETE /api/v1/voices/:idSoft-deletes one of your own custom voices (owner-scoped; returns 404 for a voice you do not own). Preset catalog voices cannot be deleted.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string (path) | Yes | The raw Mongo ObjectId of the custom voice. Must be a valid ObjectId, otherwise 400 Invalid voice id. |
GET /v1/voices returns custom voices as custom_<id>. Strip the custom_ prefix before calling this endpoint — passing the prefixed value returns 400 Invalid voice id.
curl -X DELETE https://api.kolbo.ai/api/v1/voices/68f2c1a9b4e5d6f7a8b9c0d1 \
-H "X-API-Key: kolbo_live_..."Rate Limits
| Endpoint | Limit per account |
|---|---|
GET /v1/voices | No dedicated limiter |
POST /v1/voices/clone | 10 / minute |
POST /v1/voices/import-elevenlabs | 10 / minute |
DELETE /v1/voices/:id | 120 / minute |
MCP tools covering this surface: list_voices, clone_voice, import_elevenlabs_voice, delete_voice.
Tips
POST /v1/generate/speechandPOST /v1/generate/soundshare the standard SDK generation limit of 10 requests/minute per account- Poll at the cadence in
poll_interval_hint— 8 seconds for speech, 5 for sound - Both endpoints return audio URLs on
media.kolbo.aithat can be downloaded or streamed - Use
GET /api/v1/voicesto discover available voices before generating speech
Related
Music Generation
Generate songs and instrumentals, or score an existing video
Transcription
Turn audio and video into text and SRT subtitles
Lipsync
Drive a face with generated speech
Models and Pricing
The text_to_speech and text_to_sound catalogs and their credit costs
Polling and Cancellation
The state machine, the reference poll loop, failures and cancel
Asset Library License & Terms of Use
The license and terms of use governing assets (AI-generated music and SFX, and third-party licensed photos, videos, vectors, and 3D models) obtained from the Kolbo.AI Asset Library.
Transcription
Transcribe audio and video files to text with SRT subtitles using the Kolbo API.