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_v4 for speech, elevenlabs-sound-effects-v1 for sound); passing an empty string, auto, smart-select, smart_select, smartselect, auto-select or kolbo_smart_select_router resolves to the same default. An identifier that does not match a catalog model is rejected with 400 MODEL_NOT_FOUND. Speech also accepts eleven_v4_turbo, which runs ElevenLabs V4 with turbo on. model must be a string — an array or object returns 400 INVALID_MODEL.
All generation endpoints accept an optional
project_idbody field that routes the output into a specific project, and an optionalsession_idthat drops the generation into an existing session you can write to (it wins overproject_id). 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 filters are case-insensitive and applied in memory. provider and gender are exact matches; language is a substring match.
| Parameter | Type | Required | Description |
|---|---|---|---|
provider | string | No | Exact match on provider, e.g. elevenlabs, google. Applies to both preset and custom voices. Custom voices are matched against their stored backend provider (e.g. fal-zonos, elevenlabs-import) |
language | string | No | Substring match against the language name or language code — he, hebrew and he-IL all match Hebrew voices. Only the first 60 characters are used. 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. Custom (custom_<id>) voices, and Visual DNA (dna_<id>) voices backed by a cloned or designed voice, accept at most 5,000 characters (trimmed) — longer returns 400 TEXT_TOO_LONG |
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_v4). Advisory — for catalog voices the synthesis model comes from the voice record, not from this field. See the callout below. eleven_v4_turbo is accepted as an alias for eleven_v4 with turbo: true |
turbo | boolean | No | ElevenLabs V4 Turbo. Must be the JSON boolean true (a string "true" is ignored). Only valid when the resolved voice is an ElevenLabs V4 voice — otherwise 400 TURBO_UNSUPPORTED. Billed at the model's turbo credit rate |
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". Trimmed and truncated to the first 500 characters. Ignored when style_instructions_preset_id names a preset (or is none) |
style_instructions_preset_id | string | No | Google / Gemini voices only. A named voice-direction preset that the server expands into the directive the engine receives: warm, dramatic, whisper, excited, calm, cheerful, serious, storyteller, sad, intimate, british, commercial — plus custom (use style_instructions, or style_instructions_label when that is empty) and none (default delivery, drops any style_instructions). Any other value returns 400 INVALID_STYLE_PRESET with the valid list |
style_instructions_label | string | No | Google / Gemini voices only. Display label stored with the generation (defaults to the preset's label or the directive). Truncated to 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 |
session_id | string | No | Put this generation into an existing text-to-speech session (e.g. the session_id from an earlier speech call) instead of a new one. Wins over project_id. Malformed → 400 SDK_SESSION_INVALID_ID; missing, or not a speech session → 404 SDK_SESSION_NOT_FOUND |
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 and the TTS engine resolves it: a
dna_<visualDnaId>value (a Visual DNA character voice) resolves to that character's voice (404, e.g.Character voice not found, when it cannot), and acustom_<id>that is not one of your non-deleted voices (or is a Fish Audio clone that is not ready yet) returns404 Custom voice is not available. Anything else that is not a real catalogvoice_idfails with400 Voice "<value>" not found.
Unless noted, these engine-level errors arrive in the standard { "success": false, "error": ..., "code": ... } envelope.
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, VOICE_NOT_FOUND and INVALID_STYLE_PRESET 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 |
The Google / Gemini direction fields (style_instructions, style_instructions_preset_id, style_instructions_label) are documented in the main table above.
The fields in both tables (plus turbo) are an explicit allowlist: any other field in the request body — including the app-only fish_settings — 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_v4"
}'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_v4",
"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 resolved identifier: what you sent, or the default eleven_v4 when you omitted it, sent an auto-select alias, or sent an identifier the catalog does not know. eleven_v4_turbo is echoed as eleven_v4 |
credits_charged | number | The character-based pre-calculation (see Credits). 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. Pass this session_id back on later speech calls to keep them in one session |
Completed Status
GET /api/v1/generate/{generation_id}/status
{
"success": true,
"generation_id": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "speech",
"state": "completed",
"progress": 100,
"session_id": "68f2c1a9b4e5d6f7a8b9c0d2",
"project_id": "68f2c1a9b4e5d6f7a8b9c0d3",
"result": {
"urls": ["https://media.kolbo.ai/tts/.../speech.mp3"],
"voice": "EXAVITQu4vr4xnSDxMaL",
"requested_voice_id": "EXAVITQu4vr4xnSDxMaL",
"duration": null,
"speaking_speed": null,
"language": "en-US",
"selected_style": null,
"style_instructions": null,
"style_instructions_label": null,
"style_instructions_preset_id": null,
"voice_settings": { "similarity_boost": 0.75, "style": 0.5, "stability": 1 },
"model": "eleven_v4",
"created_at": "2026-07-20T14:20:00Z"
},
"credits_used": 6,
"credits_breakdown": [
{ "model": "eleven_v4", "amount": 6 }
]
}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 carries success, generation_id, type, state, progress and (when known) session_id / project_id, with no result at all. There is no partial or streaming read. credits_breakdown entries carry only model and amount; both credit fields are omitted when no deduction row is found.
result field | Notes |
|---|---|
voice | The voice the provider actually spoke (resolved id), not the name you sent |
requested_voice_id | The voice id the request resolved to before synthesis. Differs from voice when the provider substitutes (e.g. a Google voice whose locale prefix is discarded) |
model | 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 |
model_name | Present only when the record stores a resolved display name |
duration | null in practice on this path: the TTS record does not persist an output length, so measure the file yourself if you need it |
speaking_speed, language, selected_style | The delivery settings stored with the generation (selected_style falls back to the stored emotion), so you can reuse them |
style_instructions, style_instructions_label, style_instructions_preset_id | The resolved Google / Gemini direction, or null |
voice_settings | { similarity_boost, style, stability } — present only when the record stores voice settings |
minimax | { pitch, vol, intensity, timbre } — present only for MiniMax generations that stored pitch or volume |
accentControl | The DeepDub accent object, when one was sent |
created_at | Creation timestamp |
Failure and cancellation
A failed synthesis is still an HTTP 200 with success: true — the failure is in state. error and failure.message carry the stored user-facing reason (provider names scrubbed). The other failure.* sub-fields (category, code, retryable, severity) are normally null on this endpoint, because the TTS failure record stores its cause under different keys. A job that never gets a provider response is failed by the 15-minute server deadline with a "timed out" 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 the model that actually runs (the voice's own model — see the callout above). Fish Audio models count in blocks of 10 characters instead of 100. turbo: true applies the model's turbo credit adjustment to that total.
For example, a 250-character text with a model that costs 1 credit per 100 chars: ceil(250 / 100) x 1 = 3 credits.
Before any job is created the SDK requires a balance of at least 2 credits (403 INSUFFICIENT_CREDITS, with balance, required and pricing_url); the engine then checks the full estimate and returns 403 INSUFFICIENT_CREDITS if it is not covered. In a shared project the project owner's credits are checked.
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. Non-English prompts are translated to English server-side before generation (except on Seed-Audio, which receives your original text) |
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 the model's 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 |
session_id | string | No | Put this generation into an existing text-to-sound session (e.g. the session_id from an earlier sound call). Wins over project_id. Malformed → 400 SDK_SESSION_INVALID_ID; missing, or not a sound session → 404 SDK_SESSION_NOT_FOUND |
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 | ElevenLabs / Kie | Generate a seamlessly looping sound |
sound_tempo | number | Kie | Tempo control |
sound_key | string | Kie | Musical key / scale. Kie models do not receive duration |
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 — up to 3. Must be a JSON array; only the first 3 entries are forwarded (empty or non-string entries among them are then discarded) and the rest are silently dropped. URLs pointing at internal/private hosts fail the generation |
seed_reference_image_url | string | FAL Seed-Audio | One reference image URL (same internal-host rule) |
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 resolved identifier: what you sent, or the default elevenlabs-sound-effects-v1 when you omitted it, sent an auto-select alias, or sent an identifier the catalog does not know |
credits_charged | number | The pre-calculated estimate (see Sound credits). 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 |
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 }
]
}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 carries success, generation_id, type, state, progress and (when known) session_id / project_id, with 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. result.model is the model that ran and result.created_at the creation timestamp.
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 stored error code when there is one; failure.category, retryable and severity are normally null on this endpoint. A job that never gets a provider response is failed by the 15-minute server deadline with a "timed out" message. 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);Sound credits
A sound model charges a flat model.credit per generation unless it is billed per second. Per-second models charge ceil(model.credit x seconds): the estimate uses your duration (or the model's default duration, falling back to 8s), and the final charge uses the measured length of the output, rounded up. Credits are checked against your balance (or the project owner's in a shared project) before the job is created — 403 when not covered.
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, 1–100 characters after trimming. Blank or longer 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, fish-audio. 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. You need owner, full or edit permission on it, otherwise 403 |
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 |
fish-audio | 10s | 120s | Additional 20 MB file cap |
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.
If the voice was created but its billing is still being finalized, the endpoint answers HTTP 202 with { "success": false, "error": "Your voice is being finalized..." }. That is not a failure: check GET /api/v1/voices again shortly before retrying, or you may clone twice.
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 | 30 / minute, shared with the SDK generation bucket |
POST /v1/voices/import-elevenlabs | 30 / minute, shared with the SDK generation bucket |
DELETE /v1/voices/:id | 120 / minute, shared with the SDK media bucket |
Requests that end in an error status (4xx/5xx) do not count against a limit.
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 30 requests/minute per account (one bucket across the generation endpoints that use it; requests that end in an error status are not counted)- 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.