Skip to content

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.

Open .md

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.

shell
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#

NameTypeDefaultDescription
audio_urlstringRequired. 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_frominteger | nullnullStart transcribing this many milliseconds into the recording. Non-negative integer.
audio_end_atinteger | nullnullStop transcribing at this millisecond offset. Non-negative integer.
multichannelboolean | nullnullTranscribe 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#

NameTypeDefaultDescription
language_codestring | nullnullTranscribe as this language. One of the supported codes. Setting it turns detection off.
language_detectionboolean | nullnullDetect the language automatically. Defaults to on when language_code is absent.
language_confidence_thresholdnumber | nullnullBetween 0 and 1. Fail the transcript when detection confidence falls below this. Requires detection.
language_detection_options.expected_languagesstring[] | ["all"] | nullnullRestrict detection to these codes.
language_detection_options.fallback_languagestring | "auto" | nullnullUse 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#

NameTypeDefaultDescription
punctuatebooleantrueAdd punctuation and sentence casing. false lowercases the display text and strips edge punctuation after alignment, so word timings and confidences survive.
format_textbooleantrueApply readability formatting — numbers, dates, currency written the way a person would.
disfluenciesbooleanfalseKeep 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_spellingarray | nullnullRewrite 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.

JSON
{
  "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:

ProblemResponse
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#

NameTypeDefaultDescription
speech_modelsstring[] | nullnullAccepted for AssemblyAI compatibility and echoed back. Provider model names such as universal-2 are fine.
speech_modelstring | nullnullThe 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#

NameTypeDefaultDescription
webhook_urlstring | nullnullPublicly routable http/https URL, at most 8192 characters, no credentials and no fragment.
webhook_auth_header_namestring | nullnull1–1000 characters from A-Z a-z 0-9 _ -, and not a reserved HTTP header.
webhook_auth_header_valuestring | nullnullAt 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.

HTTP
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 with speaker, channel, text, start, end, confidence and words.
  • speaker and channel on 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.

FieldTypeDescription
idstringThe transcript id, tr_...
statusstringqueued, processing, completed or error
audio_urlstringThe URL the audio was read from
textstring | nullThe full transcript; null until it completes
wordsWord[] | nullEvery word with its own timing — see below
utterancesUtterance[] | nullPer-channel spans; null unless multichannel
multichannelboolean | nulltrue when requested, otherwise null
audio_channelsnumber | nullPresent only when multichannel was requested
confidencenumber | nullOverall confidence, 0–1. 0 for a completed empty transcript
audio_durationnumber | nullMeasured length in seconds
audio_start_fromnumber | nullEchoed milliseconds; null when unset or 0
audio_end_atnumber | nullEchoed milliseconds; null when unset or 0
language_codestring | nullDetected or requested language
language_confidencenumber | null0–1, from genuine detection
speech_model_usedstring | nullThe model that actually ran
speech_modelsstring[] | nullThe model names you requested
punctuatebooleanEffective setting
format_textbooleanEffective setting
disfluenciesbooleanEffective setting
custom_spellingRule[] | nullNormalised rules, from lowercased
language_detectionbooleanWhether detection ran
speaker_labelsbooleanAlways false — diarization is not supported
webhook_urlstring | nullThe callback URL, if one was set
webhook_status_codenumber | nullHTTP status of the most recent delivery attempt
webhook_authbooleanWhether a custom auth header was configured
webhook_auth_header_namestring | nullThe header name, never its value
errorstring | nullWhy it failed, when status is error
createdstringISO 8601 timestamp

The word object#

JSON
{
  "text": "deploy",
  "start": 1100,
  "end": 1480,
  "confidence": 0.99,
  "speaker": null,
  "channel": null
}
FieldTypeDescription
textstringThe word, with the punctuation the model produced
startnumberStart offset in milliseconds
endnumberEnd offset in milliseconds
confidencenumber0–1, for this word alone
speakerstring | nullThe channel label under multichannel, else null
channelstring | nullThe 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:

shell
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}'
expected output
{
  "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.