Skip to main content

Upload transcription

POST https://api.kittenml.com/v1/audio/transcriptions

Already have the whole recording? Drop it here. Upload one audio file as multipart/form-data, then choose one complete response or a finite Server-Sent Events stream.

Send a bearer API key with the asr:transcribe permission.

Request fields

FieldTypeRequiredDefaultDescription
filefileYesNon-empty audio file, at most 25 MiB
modelstringNoemokittenasr-realtimeHosted ASR model ID
languagestringNoAutomatic detectionOptional language hint such as en
response_formatstringNojsonjson, verbose_json, text, srt, or vtt
streamboolean-like stringNofalseSet to true for finite SSE output

Supported containers include WAV, FLAC, MP3, M4A, MP4, OGG, and WebM. The service decodes the file and converts it to mono audio internally.

The plain transcript tells you what was said. JSON responses can also preserve how it was said through enriched_text, accent, and tags.

cURL

curl --fail-with-body -sS \
https://api.kittenml.com/v1/audio/transcriptions \
-H "Authorization: Bearer $KITTENML_API_KEY" \
-F "file=@speech.flac" \
-F "model=emokittenasr-realtime" \
-F "response_format=verbose_json"

In cURL, @ reads the file from disk. Relative paths start from your current directory. You can also use an absolute path:

-F "file=@/absolute/path/to/speech.flac"

--fail-with-body makes cURL return a nonzero exit code for HTTP errors while preserving the API error body. -sS hides progress but still prints errors.

Python with the OpenAI SDK

import os
from openai import OpenAI

client = OpenAI(
api_key=os.environ["KITTENML_API_KEY"],
base_url="https://api.kittenml.com/v1",
)

with open("speech.flac", "rb") as audio:
result = client.audio.transcriptions.create(
model="emokittenasr-realtime",
file=audio,
response_format="verbose_json",
)

print(result.text)
print(result.enriched_text)
print(result.request_id)

JavaScript with the OpenAI SDK

import fs from "node:fs";
import OpenAI from "openai";

const client = new OpenAI({
apiKey: process.env.KITTENML_API_KEY,
baseURL: "https://api.kittenml.com/v1",
});

const result = await client.audio.transcriptions.create({
model: "emokittenasr-realtime",
file: fs.createReadStream("speech.flac"),
response_format: "verbose_json",
});

console.log(result.text);
console.log(result.enriched_text);
console.log(result.request_id);

Non-streaming responses

Leave stream unset or set it to false for one response after transcription finishes.

response_formatContent typeResult
jsonapplication/jsonTranscript and KittenML metadata
verbose_jsonapplication/jsonJSON plus duration, language, and segments
texttext/plainClean transcript only
srtapplication/x-subripTimestamped SubRip subtitles
vtttext/vttTimestamped WebVTT subtitles

JSON

{
"text": "A cold, lucid indifference reigned in his soul.",
"enriched_text": "[low][slow]A (cold), [pause_short] (lucid) (indifference) (reigned) in his (soul).[/slow][/low]",
"accent": "General American",
"tags": [
{"type": "tag", "label": "low", "start": 0, "end": 5},
{"type": "tag", "label": "slow", "start": 5, "end": 11},
{"type": "tag", "label": "pause_short", "start": 21, "end": 34},
{"type": "tag", "label": "slow", "start": 82, "end": 89},
{"type": "tag", "label": "low", "start": 89, "end": 95},
{"type": "stress", "label": "stress", "start": 13, "end": 19},
{"type": "stress", "label": "stress", "start": 35, "end": 42},
{"type": "stress", "label": "stress", "start": 43, "end": 57},
{"type": "stress", "label": "stress", "start": 58, "end": 67},
{"type": "stress", "label": "stress", "start": 75, "end": 81}
],
"request_id": "<request-id>"
}

Verbose JSON

verbose_json adds task, language, duration, and segments:

{
"task": "transcribe",
"language": "English",
"duration": 4.275,
"text": "A cold, lucid indifference reigned in his soul.",
"enriched_text": "[low][slow]A (cold), [pause_short] (lucid) (indifference) (reigned) in his (soul).[/slow][/low]",
"accent": "General American",
"segments": [
{
"id": 0,
"start": 0.56,
"end": 4.192,
"text": "A cold, lucid indifference reigned in his soul.",
"enriched_text": "[low][slow]A (cold), [pause_short] (lucid) (indifference) (reigned) in his (soul).[/slow][/low]"
}
],
"request_id": "<request-id>"
}

The complete response also contains the same tags array shown in the JSON example.

Text

A cold, lucid indifference reigned in his soul.

SRT

1
00:00:00,560 --> 00:00:04,192
A cold, lucid indifference reigned in his soul.

VTT

WEBVTT

00:00:00.560 --> 00:00:04.192
A cold, lucid indifference reigned in his soul.

Model wording, enrichment labels, accent, and segment boundaries can vary.

Stream an uploaded file with SSE

Set stream=true and use cURL's -N option to display events immediately:

curl --fail-with-body -sS -N \
https://api.kittenml.com/v1/audio/transcriptions \
-H "Authorization: Bearer $KITTENML_API_KEY" \
-F "file=@speech.flac" \
-F "model=emokittenasr-realtime" \
-F "stream=true"

The response uses text/event-stream:

data: {"type":"transcript.text.delta","delta":"A cold, lucid","clean_text":"A cold, lucid","enriched_text":"[low]A (cold), lucid[/low]","request_id":"<request-id>"}

data: {"type":"transcript.text.done","text":"A cold, lucid indifference reigned in his soul.","enriched_text":"[low][slow]A (cold), lucid indifference reigned in his soul.[/slow][/low]","request_id":"<request-id>"}

data: [DONE]

There may be zero or more delta events. The terminal transcript.text.done event is authoritative and contains accent, tags, and segments. The server closes the finite response after [DONE]. The abridged events above omit those final metadata fields.

If transcription fails after streaming begins, the stream can emit a structured error event before [DONE].

With stream=true, every accepted response_format value produces this same SSE protocol. Use response_format to select an output representation only when stream=false.

See Response formats and SSE for a longer verified example with four timestamped segments and multiple delta events.

Response fields

FieldMeaning
textClean transcript
enriched_textTranscript with delivery, pause, and emphasis markup
accentBest-effort accent classification; may be empty
tagsHalf-open character ranges into enriched_text
durationDecoded input duration in seconds (verbose_json)
segmentsTimestamped speech segments; upload start and end are seconds (verbose_json and final SSE event)
request_idIdentifier for logs and support

Upload STT shares the organization's ASR concurrency limit with realtime sessions.