Batch transcription
Submit long complete recordings, poll every terminal state, and handle capacity safely.
Use Batch REST when a large or long recording already exists and asynchronous
completion is acceptable. It fits meetings, podcasts, archives, and similar
complete files: upload once, receive a jobId, and poll the job to a terminal
state.
Fast transcription also accepts complete audio, but choose it for one latency-sensitive conversational unit. If audio is still arriving, use a realtime workflow instead.
1. Prepare a trusted server
Direct batch requests need the following values and decisions:
| Requirement | What to prepare |
|---|---|
API_URL | Use the exact REST base URL issued for your environment; don't infer a host |
API_KEY | Send it as x-api-key from a trusted backend, never public client code |
| Complete audio | Upload one file in the file field of a multipart/form-data body |
| Deadlines | Set a timeout on each request and a finite deadline for the whole job |
Follow the authentication flow to obtain API_KEY. This
site renders the API_URL configured for its environment. API_PATH is a
Socket.IO setting and isn't used by these REST routes.
2. Submit a job
Choose the language path and processing selectors before uploading. This example requests Arabic–English code-switching, speaker diarization, and inverse text normalization:
curl --fail-with-body --connect-timeout 10 --max-time 120 \
-X POST "$API_URL/v1/transcribe/codeswitch?asr=bayan_cs_ar_en&diarization=1&itn=1&redact=0" \
-H "x-api-key: $API_KEY" \
-F "file=@meeting.wav"A successful job creation returns HTTP 200. The validated specification
includes this response:
{
"jobId": "7f51f2c2-e7bc-41c8-a850-f848df2ddfc8",
"status": "queued"
}Check the HTTP status before parsing the success shape, then persist jobId
with your application work item. It is the handle for subsequent result reads.
Processing selectors
| Selector | Location | Accepted values | Effect |
|---|---|---|---|
lang | Path | en, ar, codeswitch, auto | Selects the transcription language workflow |
asr | Query | Published model wire value | Overrides the model selected for the language |
diarization | Query | 0, 1, d1, d2 | Selects speaker diarization |
itn | Query | 0, 1 | Enables inverse text normalization |
redact | Query | 0, 1 | Enables redaction |
Use the model map for published model wire values. Do not invent a model suffix that the map does not expose.
3. Poll the V2 result route with a deadline
Read the direct V2 result with the returned job UUID:
export JOB_ID="<jobId returned by the submission request>"
curl --fail-with-body --connect-timeout 10 --max-time 15 \
"$API_URL/v1/transcribe/$JOB_ID?save_result=true" \
-H "x-api-key: $API_KEY"GET /v1/transcribe/{job_id} returns
{ "message": "success", "data": … }. Branch only on data.status:
| Status | Application action |
|---|---|
queued | Wait, then read again while the overall deadline remains |
processing | Keep waiting within the same deadline |
done | Stop polling and consume the completed result fields |
failed | Stop polling and surface the job failure |
cleared | Stop polling; the result is unavailable |
Use this bounded polling sequence:
- Set one overall deadline before the first result read.
- Give every GET its own shorter request timeout.
- Accept only
queuedandprocessingas reasons to wait and poll again. - Stop immediately for
done,failed, orcleared. - Stop locally when the overall deadline expires; don't infer a server-side terminal state from a client timeout.
Set save_result=true before the first poll when delivery of the terminal
response must be repeatable. With the default false, a done or failed
read can clear stored result fields after constructing its response. If that
response is lost, the next read can return cleared instead of the result.
The polling interval and deadline are application choices, not service guarantees. The recording recipe provides bounded JavaScript and Python loops.
4. Keep V1 and V2 separate
| Route | Intended use | Response shape | Optional result selectors |
|---|---|---|---|
GET /v1/transcribe/{job_id} | Direct V2 result read | Wrapper with data.final_result, data.final_word_segments, and data.diarization_segments | save_result, default false |
GET /v1/transcribe/{job_id}/{lang} | Legacy V1 and SDK 0.18.0 | metadata, results.transcript, results.offsets, and diarization_segments | save_result, default false; direct HTTP only: diarization_force_align, default true |
Both shapes use the same five job statuses, but their field names and nesting
differ. Do not mix V2 snake-case word segments with V1 offsets in one type.
On either route, the default save_result=false can make a terminal done or
failed response a single-consumption read. Set it to true before polling
when a lost response must be retrievable again. The contract doesn't specify a
result-retention duration, even with save_result=true; treat cleared as
terminal without assuming recovery is possible.
The V1 lang segment is retained for compatibility but the current result
handler does not use or validate it. SDK 0.18.0 sends the submission language;
new direct HTTP clients should use V2.
5. Reconcile diarization
V2 final_word_segments do not contain speaker. When diarization is enabled,
reconcile each word with diarization_segments using an application rule you
document, such as assigning the segment that contains the word midpoint. Keep
an unknown speaker when no segment overlaps unless your application explicitly
adopts nearest-segment assignment.
The V1 route can place speaker on word offsets. Its direct-only
diarization_force_align option changes those offset labels, not the raw
diarization_segments; SDK 0.18.0 does not expose it. With the default
true, a word whose startTime is outside every real segment uses the speaker
at the nearest segment boundary, measured from the word midpoint; ties use the
earlier segment. With false, it uses UNKNOWN_SPEAKER. With no real segments,
the speaker stays null.
Speaker labels describe relative turns in one result; they do not establish a real-world identity.
6. Handle capacity and ambiguous retries
Batch submission can return HTTP 429 with structured error fields and the
remaining capacity in audio seconds at data.capacity:
{
"error": "error.rate_limit",
"code": "RATE_LIMIT_EXCEEDED",
"detail": "error.rate_limit",
"retryable": true,
"timestamp": "2026-01-15T10:30:00Z",
"data": { "capacity": 120.5 }
}For an explicit 429, queue or slow producers and retry with bounded attempts,
backoff, and an overall deadline. Use capacity for admission decisions; the
contract does not define it as a sleep duration.
A connection failure or timeout after an upload is different: the first request
may already have created a job. The batch contract does not document an
idempotency-key header, so do not blindly resubmit. Record the ambiguous attempt
and retry only under an application policy that explicitly accepts the risk of
a duplicate job. Result GETs can use bounded retries only when
save_result=true was set before terminal retrieval; the default destructive
read can become cleared after a lost response. See
Errors and Rate Limits.
7. Continue to reference and production checks
Implement one representative recording first, then keep the generated Batch
API reference beside your code for exact schemas. Before launch, exercise
authentication failures, invalid audio, 429, polling deadlines, and all three
terminal outcomes.