Kolbo.AIKolbo.AI Docs
Developer API

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_id body field that routes the output into a specific project. See Projects.

Endpoint

POST /api/v1/transcribe

Accepts both application/json (for URL-based input) and multipart/form-data (for file uploads).

Request Body

FieldTypeRequiredDescription
audio_urlstringNo*URL of an audio file to transcribe
video_urlstringNo*URL of a video file (audio is extracted automatically). Takes precedence over audio_url when both are sent
file_urlstringNo*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
filefileNo*Audio or video file upload (multipart). Max 500 MB
languagestringNoOutput 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
diarizebooleanNoDetect and label distinct speakers (default false)
tag_audio_eventsbooleanNoTag non-speech events (laughter, applause, music) (default false)
remove_punctuationbooleanNoStrip punctuation from the transcript and word chunks (default false)
generate_srtbooleanNoProduce SRT + word-by-word SRT files (default true). Only an explicit false (boolean or the string "false") disables it
words_per_linenumberNoSRT: max words per line. Clamped 118 (default 12)
lines_per_subtitlenumberNoSRT: max lines per cue. Clamped 14 (default 2)
stretch_captionsbooleanNoSRT: 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_optionsobject | stringNoJSON 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_idstringNoRoute 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"
}
FieldTypeNotes
generation_idstringMongo ObjectId. This is what you poll with.
typestringAlways "transcription"
modelstringAlways the literal "auto" — this endpoint takes no model parameter
credits_chargednullAlways null on this endpoint — transcription is billed per minute of audio once the job finishes
poll_urlstringThe status path without the /api prefix. Prepend https://api.kolbo.ai/api.
poll_interval_hintnumberSuggested seconds between polls — 8 here
session_id / project_idstringWhere 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 fieldTypeNotes
textstringThe full transcript
srt_urlstring | nullnull when generate_srt was disabled
txt_urlstring | nullPlain-text download
word_by_word_srt_urlstring | nullWord-level timing. null when generate_srt was disabled.
srt_contentstring | nullThe complete SRT inline, so you can process subtitles without a download
durationnumber | nullSource length in seconds
audio_urlstring | nullThe stored copy of your source file
modelnullAlways null for transcription — the record does not persist a model identifier
created_atstring (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_content field contains the raw SRT text inline, useful if you want to process subtitles without downloading the file
  • The word_by_word_srt_url provides 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