Transcript options
Every field you can send when creating a transcript, what each one does, and every field on the transcript and word objects that come back.
One JSON body creates a transcript, at POST /v2/transcript or
POST /api/v1/transcripts. Only audio_url is required; everything else has a
sensible default. This page is the complete field reference for both directions.
curl -sS https://stt-api.sandchest.com/v2/transcript \
-H "Authorization: $SANDCHEST_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: import-2026-08-30-meeting-141" \
-d '{
"audio_url": "https://example.com/meeting.mp3",
"language_detection": true,
"disfluencies": true,
"format_text": true
}'The audio#
| Name | Type | Default | Description |
|---|---|---|---|
audio_url | string | — | Required. Either an upload_url from POST /v2/upload, or a public HTTP(S) URL. Credentials in the URL, private and loopback addresses are refused; in production the URL must be HTTPS. |
audio_start_from | integer | null | null | Start transcribing this many milliseconds into the recording. Non-negative integer. |
audio_end_at | integer | null | null | Stop transcribing at this millisecond offset. Non-negative integer. |
multichannel | boolean | null | null | Transcribe each physical input channel independently. See Multichannel. |
An uploaded asset expires after the deployment's retention window (24 hours on the
hosted service). Referring to an expired or deleted upload returns
{"error":"The uploaded audio asset was not found or has expired."}.
Language#
| Name | Type | Default | Description |
|---|---|---|---|
language_code | string | null | null | Transcribe as this language. One of the supported codes. Setting it turns detection off. |
language_detection | boolean | null | null | Detect the language automatically. Defaults to on when language_code is absent. |
language_confidence_threshold | number | null | null | Between 0 and 1. Fail the transcript when detection confidence falls below this. Requires detection. |
language_detection_options.expected_languages | string[] | ["all"] | null | null | Restrict detection to these codes. |
language_detection_options.fallback_language | string | "auto" | null | null | Use this language when detection is not confident. |
Rules the API enforces, with the exact error each one produces, are in
Languages. The short version: send language_code, or
send language_detection: true, or send neither and get detection.
fallback_language must appear in expected_languages, or be "auto", or
expected_languages must be ["all"] — otherwise the request is rejected with
{"error":"fallback_language must be in expected_languages or auto."}. When
comparing, en_au, en_uk and en_us all fold to en; de_ch stays distinct from
de.
Text formatting#
| Name | Type | Default | Description |
|---|---|---|---|
punctuate | boolean | true | Add punctuation and sentence casing. false lowercases the display text and strips edge punctuation after alignment, so word timings and confidences survive. |
format_text | boolean | true | Apply readability formatting — numbers, dates, currency written the way a person would. |
disfluencies | boolean | false | Keep filled pauses ("um", "uh"). The default removes them from English audio after alignment; retained words keep their original timings and confidence. Other languages keep the model's own output. |
custom_spelling | array | null | null | Rewrite recognised words to a preferred spelling. See below. |
custom_spelling#
An array of { "from": ..., "to": ... } rules. from is a spoken word or phrase, or
an array of them, matched case-insensitively. to is the single word to display.
{
"audio_url": "https://example.com/meeting.mp3",
"custom_spelling": [
{ "from": ["sand chest", "sandchest"], "to": "Sandchest" },
{ "from": "assembly ai", "to": "AssemblyAI" }
]
}Rules apply after recognition and alignment. They do not bias the acoustic model or
improve accuracy — they rewrite what was already heard. When a multi-word from
collapses into one word, the merged word keeps the first word's start, the last
word's end, and the mean confidence.
Constraints, and the exact error for each:
| Problem | Response |
|---|---|
| not an array | {"error":"custom_spelling must be an array of objects"} |
an entry with keys other than from and to | {"error":"custom_spelling must be an array of objects with only 'to' and 'from' keys"} |
a missing or empty from | {"error":"custom_spelling 'from' fields cannot be empty or null"} |
an empty string inside from | {"error":"custom_spelling 'from' values cannot be empty"} |
a to that is not exactly one word | {"error":"custom_spelling 'to' fields must contain only one word"} |
| a wrong type anywhere | {"error":"Invalid endpoint schema, please refer to documentation for examples."} |
null, false, 0, "", [] and {} all mean "no rules" and are accepted.
Models#
| Name | Type | Default | Description |
|---|---|---|---|
speech_models | string[] | null | null | Accepted for AssemblyAI compatibility and echoed back. Provider model names such as universal-2 are fine. |
speech_model | string | null | null | The singular form, treated the same way. |
Sandchest routes to its own configured models — English audio to Parakeet TDT v2,
automatic and non-English requests to the multilingual Parakeet TDT v3. The response
field speech_model_used reports the model that genuinely produced the transcript,
so you always know what ran. See Languages.
Webhooks#
| Name | Type | Default | Description |
|---|---|---|---|
webhook_url | string | null | null | Publicly routable http/https URL, at most 8192 characters, no credentials and no fragment. |
webhook_auth_header_name | string | null | null | 1–1000 characters from A-Z a-z 0-9 _ -, and not a reserved HTTP header. |
webhook_auth_header_value | string | null | null | At most 1000 characters. |
Both auth fields must be sent together with a webhook_url, or all three omitted.
Anything else returns {"error":"Invalid webhook URL or authentication parameters."}.
Full behaviour in Webhooks.
Idempotency#
Idempotency-Key is a request header, not a body field. At most 200 characters.
POST /v2/transcript HTTP/1.1
Authorization: sc_live_...
Idempotency-Key: import-2026-08-30-meeting-141
Content-Type: application/json- Same key, same request → the original transcript, not a second one, and no second charge.
- Same key, a genuinely different request → HTTP 409 and
{"error":"This Idempotency-Key was already used for a different transcription request."}. - Same key, but the transcript was deleted → HTTP 409 and
{"error":"This Idempotency-Key belongs to a deleted transcript."}. Keys stay reserved after deletion; they never produce a fresh job. - Longer than 200 characters → HTTP 400 and
{"error":"Idempotency-Key must contain at most 200 characters."}.
Keys are scoped to your workspace and do not expire. Re-uploading the identical bytes and retrying with the same key still counts as the same request: Sandchest compares the stored SHA-256 of the audio, not the URL.
Multichannel#
multichannel: true recognises each physical input channel on its own, so a
two-track call recording gives you both sides with independent timings. Up to 32
channels.
When it is on, the completed transcript adds:
audio_channels— how many channels were decoded.utterances— per-channel spans, each withspeaker,channel,text,start,end,confidenceandwords.speakerandchannelon every word, both set to the channel label:"1","2", and so on.
Channel labels identify input channels. They are not inferred speaker identities —
Sandchest does not do diarization, and speaker_labels: true is rejected.
The transcript object#
Returned by create, get, list-item lookups and delete.
| Field | Type | Description |
|---|---|---|
id | string | The transcript id, tr_... |
status | string | queued, processing, completed or error |
audio_url | string | The URL the audio was read from |
text | string | null | The full transcript; null until it completes |
words | Word[] | null | Every word with its own timing — see below |
utterances | Utterance[] | null | Per-channel spans; null unless multichannel |
multichannel | boolean | null | true when requested, otherwise null |
audio_channels | number | null | Present only when multichannel was requested |
confidence | number | null | Overall confidence, 0–1. 0 for a completed empty transcript |
audio_duration | number | null | Measured length in seconds |
audio_start_from | number | null | Echoed milliseconds; null when unset or 0 |
audio_end_at | number | null | Echoed milliseconds; null when unset or 0 |
language_code | string | null | Detected or requested language |
language_confidence | number | null | 0–1, from genuine detection |
speech_model_used | string | null | The model that actually ran |
speech_models | string[] | null | The model names you requested |
punctuate | boolean | Effective setting |
format_text | boolean | Effective setting |
disfluencies | boolean | Effective setting |
custom_spelling | Rule[] | null | Normalised rules, from lowercased |
language_detection | boolean | Whether detection ran |
speaker_labels | boolean | Always false — diarization is not supported |
webhook_url | string | null | The callback URL, if one was set |
webhook_status_code | number | null | HTTP status of the most recent delivery attempt |
webhook_auth | boolean | Whether a custom auth header was configured |
webhook_auth_header_name | string | null | The header name, never its value |
error | string | null | Why it failed, when status is error |
created | string | ISO 8601 timestamp |
The word object#
{
"text": "deploy",
"start": 1100,
"end": 1480,
"confidence": 0.99,
"speaker": null,
"channel": null
}| Field | Type | Description |
|---|---|---|
text | string | The word, with the punctuation the model produced |
start | number | Start offset in milliseconds |
end | number | End offset in milliseconds |
confidence | number | 0–1, for this word alone |
speaker | string | null | The channel label under multichannel, else null |
channel | string | null | The channel label under multichannel, else null |
Timings are acoustic, from the model's own alignment. Every derived resource — sentences, paragraphs, SRT, VTT, word search — is built from these same word boundaries, so a caption never disagrees with a word.
Verify#
Create a transcript with a couple of options and check they round-trip:
curl -sS https://stt-api.sandchest.com/v2/transcript \
-H "Authorization: $SANDCHEST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"audio_url":"'"$UPLOAD_URL"'","disfluencies":true,"language_code":"en"}' \
| jq '{status, disfluencies, language_detection, language_code}'{
"status": "queued",
"disfluencies": true,
"language_detection": false,
"language_code": null
}language_code is null until the transcript completes — the response echoes what
was detected, not what was requested. Poll the same id and it fills in.
Next#
Languages — automatic detection, the supported codes, and which model handles which.