Speech
Transcription
Turn speech into text — POST /v1/stt for short audio, streamed segments for long recordings, POST /v1/transcriptions for large files.
There are two ways to transcribe, both on the stt scope:
POST /v1/stt |
POST /v1/transcriptions |
|
|---|---|---|
| Answer | the text, in the response | a generation id, then poll |
| Input | the audio in the request body | an audio file in your asset library |
| Size | up to 25 MB | up to the library’s audio limit (Limits) |
| Streaming | stream=true: segments as they are transcribed |
no |
| Stored | nothing | the full result, as JSON, in your library |
Short audio — a voice note, a spoken command — goes to /v1/stt. Anything long, or anything you want to keep, goes to /v1/transcriptions.
Transcribe in one request
Section titled “Transcribe in one request”Send the audio as a file part with the options as form fields:
curl https://api.lumavox.ai/v1/stt \ -H "Authorization: Bearer $API_KEY" \ -F file=@meeting.mp3 \ -F language=enOr send the audio itself as the body, with the options as query parameters:
curl "https://api.lumavox.ai/v1/stt?language=en×tamps=true" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: audio/mpeg" \ --data-binary @meeting.mp3{ "text": "Thanks everyone for joining. Let's start with the roadmap.", "language_code": "eng", "language_probability": 0.98, "duration_seconds": 4.2, "model": "scribe_v2", "words": null}Any audio or video container the provider reads is accepted, and nothing is converted on the way. The content type must be audio/*, video/* or application/octet-stream; anything else is a 415.
Options
Section titled “Options”The same options work as form fields or query parameters.
| Option | Default | What it does |
|---|---|---|
model |
scribe_v2 |
The transcription model. scribe_v2 is the one you can name. |
language |
detected | An ISO 639-1 or 639-3 code, such as en or fa. Set it when you know it: detection is good, and a wrong guess costs the whole transcript. |
diarize |
false |
Label who is speaking. Each word gains a speaker_id. |
num_speakers |
provider decides | The most speakers to look for, 1 to 32, when diarize is on. |
tag_audio_events |
false |
Mark laughter, applause and the like, as entries with type: audio_event. |
timestamps |
false |
Return each word with its start_seconds and end_seconds. |
stream |
false |
Answer as newline-delimited JSON — see streaming. |
language_code in the answer is not what you sent. It is always ISO 639-3, so en comes back as eng.
Word timings are off by default because the list is far larger than the text — an hour of speech is about ten thousand words. Without them words is null; with timestamps=true it is a list of:
{"text": "Thanks", "start_seconds": 0.12, "end_seconds": 0.46, "type": "word", "speaker_id": null}start_seconds and end_seconds count from the start of the audio. type is word, the spacing between words, or an audio_event with tag_audio_events; speaker_id is set with diarize.
Charged per minute of audio, on duration_seconds — the provider’s own measurement, which the response shows you so you can check the charge. A request the provider fails is not charged.
Streamed segments
Section titled “Streamed segments”With stream=true the answer is application/x-ndjson: one JSON object per line. The audio is cut at pauses, the pieces are transcribed several at a time — the platform’s speech.stt.stream_concurrency (Limits) — and each piece’s text arrives as soon as it is its turn — so a long recording shows its first lines in seconds rather than at the end.
curl "https://api.lumavox.ai/v1/stt?stream=true&language=en" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: audio/wav" \ --data-binary @lecture.wav \ --no-buffer{"type":"segment","index":0,"duration_seconds":28.4,"reason":"silence"}{"type":"segment","index":1,"duration_seconds":30.0,"reason":"max"}{"type":"segment","index":2,"duration_seconds":6.1,"reason":"end"}{"type":"transcript","index":0,"text":"Good morning. Today we look at…","duration_seconds":28.4,"reason":"silence","language_code":"eng","language_probability":0.99,"words":null}{"type":"transcript","index":1,"text":"…which is why the second law…","duration_seconds":30.0,"reason":"max","language_code":"eng","language_probability":0.99,"words":null}{"type":"error","index":2,"code":"internal","message":"That segment could not be transcribed (TimeoutError)."}{"type":"done","text":"Good morning. Today we look at… …which is why the second law…","segments":3,"failed":1,"duration_seconds":58.4,"language_code":"eng"}The four kinds of line:
type |
Fields | Meaning |
|---|---|---|
segment |
index, duration_seconds, reason |
A piece was cut. reason is silence (cut at a pause), max (cut at 30 seconds, at the quietest moment) or end (the last piece). |
transcript |
index, text, duration_seconds, reason, language_code, language_probability, words |
One piece’s text. words is null without timestamps=true. |
error |
index, code, message |
One piece failed; the rest carry on. code is an error code: provider_failed when the provider refused it, internal otherwise. |
done |
text, segments, failed, duration_seconds, language_code |
The end: the whole transcript joined, how many pieces failed, and how long the transcribed pieces were together. |
- Transcripts arrive in
indexorder, not in the order they finish. A transcript out of order is not a transcript. - Every line has every field of its kind,
nullwhere there is nothing:failedis0when no piece failed. - The
doneline carries the whole text, so a client may ignore every line before it. - A piece with no speech in it is dropped, so silence costs nothing.
- No
doneline means the stream broke. The status was already 200 by then; treat a stream withoutdoneas incomplete.
Streaming needs WAV or raw PCM. Finding a pause means reading samples, and the API never decodes audio. Send:
- a WAV of 16-bit mono integer PCM, at any rate from 8 to 192 kHz — the rate is read from the header; or
- raw 16-bit little-endian mono PCM, as
audio/pcm,audio/l16oraudio/x-pcm, which is read as 24 kHz.
Anything else with stream=true is a 415 that says what it found. Send it without stream, or convert it first.
Queued transcription
Section titled “Queued transcription”For a long recording, or one you want to keep with its word timings, upload it to your library and queue the transcription.
1. Upload the file — ask for an upload URL, PUT the file to it, then confirm:
curl https://api.lumavox.ai/v1/assets/upload-url \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"content_type": "audio/mpeg"}'# → {"asset_id": "ast_…", "url": "https://…", "expires_at": "2026-10-24T09:45:00.000Z"}
curl -X PUT "<url>" -H "Content-Type: audio/mpeg" --data-binary @interview.mp3
curl https://api.lumavox.ai/v1/assets/uploaded \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"asset_id": "ast_…", "content_type": "audio/mpeg", "name": "interview.mp3"}'Wait until GET /v1/assets/{asset_id} says ready. Assets has the details.
2. Queue the transcription:
curl https://api.lumavox.ai/v1/transcriptions \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"asset_id": "ast_a1B2c3D4e5F6g7H8", "language": "en", "diarize": true, "timestamps": true}'It answers 202 with a generation (gen_…). The body takes asset_id, folder_id (where the result is filed) and the same model, language, diarize, num_speakers, tag_audio_events and timestamps as /v1/stt, as JSON.
3. Poll GET /v1/transcriptions/{generation_id} every couple of seconds until status is succeeded or failed:
curl https://api.lumavox.ai/v1/transcriptions/gen_a1B2c3D4e5F6g7H8 \ -H "Authorization: Bearer $API_KEY"{ "id": "gen_a1B2c3D4e5F6g7H8", "status": "succeeded", "capability": "stt", "transcript": { "text": "Thanks for having me. So, where do I start…", "language_code": "eng", "language_probability": 0.99, "duration_seconds": 1834.6, "word_count": 5120, "asset_id": "ast_q1W2e3R4t5Y6u7I8", "content_type": "application/json", "size_bytes": 912345, "url": "https://…" }}transcript.textis the whole transcript, inline.transcript.urlis a short-lived link to the provider’s full response as JSON — word timings and speaker labels included, where you asked for them.- That JSON is kept in your library as
transcript.asset_id. Fetch it from there later; theurlexpires.
The recording stays in your library, so transcribing it again with other options costs no second upload.
Charged when accepted, as an estimate: the asset’s measured length, or an estimate from its size. When the job finishes, the charge is corrected to the provider’s own measurement — transcript.duration_seconds is the figure you paid for. A transcription that fails is refunded in full. See Queued jobs and Credits and usage.
Errors
Section titled “Errors”The common envelope is described in Errors and limits. The ones specific to transcription:
| Status | Endpoint | When |
|---|---|---|
| 422 | /v1/transcriptions |
The asset is not audio, or its upload failed; or folder_id is not one of your folders. |
| 400 | /v1/stt |
The multipart body could not be read. |
| 402 | both | The organisation is out of credits — or, for /v1/transcriptions, the job’s estimate would take its balance past the grace buffer. Nothing is charged. |
| 404 | /v1/transcriptions |
The asset or the generation is unknown or not yours. |
| 409 | /v1/transcriptions |
The asset is still being processed. Poll it until it is ready. |
| 413 | /v1/stt |
The audio is over 25 MB. Use /v1/transcriptions, whose limit is the library’s for an audio upload (limits.uploads.audio_bytes on Limits). |
| 415 | /v1/stt |
Not audio; or, with stream=true, not WAV or raw PCM, or a WAV that is not 16-bit mono. |
| 422 | both | An option failed validation, or one the endpoint does not know was sent — or, on /v1/stt, no audio was sent, a multipart body had no file part, or a WAV header could not be read. issues names the field. |
| 429 | both | Over your organisation’s rate limit. |
| 502 | /v1/stt |
The provider failed. Nothing is charged; safe to retry. |
| 503 | both | Transcription, or the storage it reads from, is not set up on this deployment. |
Related
Section titled “Related”- Assets — uploading files.
- Queued jobs — polling, statuses, refunds.
- The WebSocket — transcription inside an agent conversation, including transcribing without answering.
- Reference: transcription