Transcription
Transcribe audio and video files to text with SRT subtitles using the Kolbo API.
Transcribe audio and video files to text with word-level SRT subtitles. Supports both URL input and direct file upload.
No model selection — transcription always uses the built-in STT engine. There is no model parameter.
All generation endpoints accept an optional
project_idbody field that routes the output into a specific project. See Projects.
Endpoint
POST /api/v1/transcribeAccepts both application/json (for URL-based input) and multipart/form-data (for file uploads).
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
audio_url | string | No* | URL of an audio file to transcribe |
video_url | string | No* | URL of a video file (audio is extracted automatically). Takes precedence over audio_url when both are sent |
file_url | string | No* | URL of any audio or video file. Lowest precedence — used only when neither video_url nor audio_url is sent, and otherwise handled identically. One difference: if it points at a video stored under a Kolbo /temp/ path, that source object is deleted after the transcription finishes |
file | file | No* | Audio or video file upload (multipart). Max 500 MB |
language | string | No | Output language for the subtitles, e.g. "en", "he", "es". It is not a source-language hint: the engine always auto-detects the spoken language, and this value is applied afterwards as a translation target on the main SRT only (so it needs generate_srt on, and does nothing when it equals the detected language). Omit — or send "auto-detect" — to keep the detected language |
diarize | boolean | No | Detect and label distinct speakers (default false) |
tag_audio_events | boolean | No | Tag non-speech events (laughter, applause, music) (default false) |
remove_punctuation | boolean | No | Strip punctuation from the transcript and word chunks (default false) |
generate_srt | boolean | No | Produce SRT + word-by-word SRT files (default true). Only an explicit false (boolean or the string "false") disables it |
words_per_line | number | No | SRT: max words per line. Clamped 1–18 (default 12) |
lines_per_subtitle | number | No | SRT: max lines per cue. Clamped 1–4 (default 2) |
stretch_captions | boolean | No | SRT: extend a cue's end to the next cue's start so subtitles stay on screen continuously. Only small gaps are closed — a real pause is left intact. Default true; only an explicit false / "false" disables it. The same value is applied to the word-by-word SRT |
srt_options | object | string | No | JSON object (or JSON string) of { wordsPerLine, linesPerSubtitle }. Takes precedence over the flat words_per_line / lines_per_subtitle fields. A malformed JSON string is ignored silently |
project_id | string | No | Route the transcription into a specific project. Omit for the default "API Generations" project |
*At least one of audio_url, video_url, file_url, or file must be provided — otherwise the request returns 400 Provide audio_url, video_url, or upload a file. For multipart/form-data uploads, send the options above as additional form fields; boolean flags are accepted as the strings "true" / "false".
POST /v1/transcribe has a raised rate limit of 100 requests/minute per account (vs. 10/min for the other generation endpoints) so you can fire a whole folder of files at once. Server-side concurrency is still bounded internally.
Examples
From Audio URL
curl -X POST https://api.kolbo.ai/api/v1/transcribe \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{"audio_url": "https://example.com/podcast-episode.mp3"}'From Video URL
curl -X POST https://api.kolbo.ai/api/v1/transcribe \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{"video_url": "https://example.com/interview.mp4"}'File Upload
curl -X POST https://api.kolbo.ai/api/v1/transcribe \
-H "X-API-Key: kolbo_live_..." \
-F "[email protected]"With Subtitle Formatting
curl -X POST https://api.kolbo.ai/api/v1/transcribe \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"audio_url": "https://example.com/interview.mp3",
"diarize": true,
"words_per_line": 6,
"lines_per_subtitle": 2,
"stretch_captions": false
}'Translated Subtitles
language translates the generated SRT into that language when it differs from the detected speech. The transcript text and the word-by-word SRT stay in the original language.
curl -X POST https://api.kolbo.ai/api/v1/transcribe \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"video_url": "https://example.com/hebrew-interview.mp4",
"language": "en"
}'Response
This endpoint is asynchronous and fire-and-forget: the POST returns as soon as the job is queued and never contains a transcript.
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 — the transcript record carries a cancelled status of its own even though this endpoint has no cancel route. Full contract: Polling & Cancellation.
Generation Started
{
"success": true,
"generation_id": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "transcription",
"model": "auto",
"credits_charged": null,
"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 with. |
type | string | Always "transcription" |
model | string | Always the literal "auto" — this endpoint takes no model parameter |
credits_charged | null | Always null on this endpoint — transcription is billed per minute of audio once the job finishes |
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 here |
session_id / project_id | string | Where the transcription lives in the Kolbo app |
Completed Status
GET /api/v1/generate/{generation_id}/status
{
"success": true,
"generation_id": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "transcription",
"state": "completed",
"progress": 100,
"result": {
"text": "Hello and welcome to today's episode...",
"srt_url": "https://media.kolbo.ai/stt/.../subtitles.srt",
"txt_url": "https://media.kolbo.ai/stt/.../transcript.txt",
"word_by_word_srt_url": "https://media.kolbo.ai/stt/.../words.srt",
"srt_content": "1\n00:00:00,000 --> 00:00:03,500\nHello and welcome to today's episode\n\n",
"duration": 245.8,
"audio_url": "https://media.kolbo.ai/uploaded-audio/.../episode-42.mp3",
"model": null,
"created_at": "2026-07-20T14:20:00Z"
}
}The output is result.text — this family has no urls array. The transcript is also available as downloadable files:
| Result field | Type | Notes |
|---|---|---|
text | string | The full transcript |
srt_url | string | null | null when generate_srt was disabled |
txt_url | string | null | Plain-text download |
word_by_word_srt_url | string | null | Word-level timing. null when generate_srt was disabled. |
srt_content | string | null | The complete SRT inline, so you can process subtitles without a download |
duration | number | null | Source length in seconds |
audio_url | string | null | The stored copy of your source file |
model | null | Always null for transcription — the record does not persist a model identifier |
created_at | string (ISO 8601) |
result appears only under state: "completed". There is no partial read: the pipeline holds progress at 99 with the transcript already written internally and only flips to completed once the files are stored, so progress climbs 5 → 80 → 95 → 99 → 100 with no payload until the end.
credits_used and credits_breakdown are not returned for /v1/transcribe. Unlike the other generation endpoints, this pipeline's credit ledger rows are not linked to the generation id you poll with, so the completed status carries neither field. Transcription is still billed — per minute of audio, at the credit rate of elevenlabs/scribe-v2 in GET /api/v1/models?type=stt (or elevenlabs/scribe-v2-srt when generate_srt is on). To measure actual spend, diff your balance from GET /api/v1/account/credits.
POST /v1/generate/{id}/cancel does not work for transcription — it always returns 404. Transcription jobs are not in the cancellable registry. Let the job finish or ignore the result.
Failure
A failed transcription is still an HTTP 200 with success: true — the failure is in state, with the real message in error (e.g. the provider's rejection). Every failure.* sub-field except message is null on this endpoint. Credits are deducted only after a transcript exists, so a failed job costs nothing. See Polling & Cancellation.
JavaScript Example — URL Input
const KOLBO_API_KEY = "kolbo_live_..."; // Replace with your API key
const BASE_URL = "https://api.kolbo.ai";
async function transcribe(body) {
const response = await fetch(`${BASE_URL}/api/v1/transcribe`, {
method: "POST",
headers: {
"X-API-Key": KOLBO_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const started = await response.json();
if (!started.success) throw new Error(started.error);
const status = await pollUntilDone(started.generation_id, started.poll_interval_hint);
if (status.state !== "completed") throw new Error(status.error || status.state);
return status.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}/api/v1/generate/${generationId}/status`,
{ headers: { "X-API-Key": KOLBO_API_KEY } }
).then((r) => r.json());
if (TERMINAL.has(status.state)) return status;
}
}
async function main() {
// Transcribe from audio URL
const result = await transcribe({
audio_url: "https://example.com/podcast.mp3",
});
console.log("Full text:", result.text); // the transcript — no urls array here
console.log("Duration:", result.duration, "seconds");
console.log("SRT file:", result.srt_url);
console.log("Word-level SRT:", result.word_by_word_srt_url);
}
main().catch(console.error);JavaScript Example — File Upload
const KOLBO_API_KEY = "kolbo_live_..."; // Replace with your API key
const BASE_URL = "https://api.kolbo.ai";
async function transcribeFile(filePath) {
// Node.js: use fs to read the file
const fs = await import("fs");
const path = await import("path");
const formData = new FormData();
formData.append("file", new Blob([fs.readFileSync(filePath)]), path.basename(filePath));
const response = await fetch(`${BASE_URL}/api/v1/transcribe`, {
method: "POST",
headers: { "X-API-Key": KOLBO_API_KEY },
body: formData,
});
const started = await response.json();
if (!started.success) throw new Error(started.error);
// pollUntilDone() from the URL-input example above — a file upload returns the
// same envelope and is polled the same way.
const status = await pollUntilDone(started.generation_id, started.poll_interval_hint);
if (status.state !== "completed") throw new Error(status.error || status.state);
return status.result;
}
async function main() {
const result = await transcribeFile("./recording.mp3");
console.log("Transcript:", result.text);
console.log("SRT download:", result.srt_url);
}
main().catch(console.error);Python Example — URL Input
import requests
import time
KOLBO_API_KEY = "kolbo_live_..." # Replace with your API key
BASE_URL = "https://api.kolbo.ai"
HEADERS = {"X-API-Key": KOLBO_API_KEY}
# Transcribe from audio URL
response = requests.post(
f"{BASE_URL}/api/v1/transcribe",
headers=HEADERS,
json={"audio_url": "https://example.com/podcast.mp3"},
)
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}/api/v1/generate/{generation_id}/status",
headers=HEADERS,
).json()
if status["state"] in TERMINAL:
return status
status = poll_until_done(started["generation_id"], started["poll_interval_hint"])
if status["state"] != "completed":
raise Exception(status.get("error", status["state"]))
result = status["result"]
print("Full text:", result["text"]) # the transcript — no urls array here
print("Duration:", result["duration"], "seconds")
print("SRT file:", result["srt_url"])
print("Word-level SRT:", result["word_by_word_srt_url"])Python Example — File Upload
import requests
KOLBO_API_KEY = "kolbo_live_..." # Replace with your API key
BASE_URL = "https://api.kolbo.ai"
HEADERS = {"X-API-Key": KOLBO_API_KEY}
# Upload a local file
with open("recording.mp3", "rb") as f:
started = requests.post(
f"{BASE_URL}/api/v1/transcribe",
headers=HEADERS,
files={"file": ("recording.mp3", f)},
).json()
if not started.get("success"):
raise Exception(started.get("error", "Request failed"))
# poll_until_done() from the URL-input example above — a file upload returns the
# same envelope and is polled the same way.
status = poll_until_done(started["generation_id"], started["poll_interval_hint"])
if status["state"] != "completed":
raise Exception(status.get("error", status["state"]))
result = status["result"]
print("Transcript:", result["text"])
print("SRT download:", result["srt_url"])
print("Text download:", result["txt_url"])Tips
- Audio is automatically extracted from video files -- no preprocessing needed
- The
srt_contentfield contains the raw SRT text inline, useful if you want to process subtitles without downloading the file - The
word_by_word_srt_urlprovides word-level timing, ideal for karaoke-style subtitles or precise editing - Maximum file size for uploads is 500 MB
- Poll at the cadence in
poll_interval_hint(8 seconds) - Batching is expected: the endpoint allows 100 requests/minute per account
Related
Speech and Sound
Text-to-speech, sound effects, and custom voices
Media Library
Upload a local file first and transcribe it by URL
Projects
Route transcriptions into a project with project_id
Errors and Limits
Error codes, rate limits, and retry guidance
Polling and Cancellation
The state machine, the reference poll loop, and failures