JavaScript and TypeScript
Use @humain-voice/sdk 0.18.0 from a server-side Node.js or Bun runtime.
This guide targets the exact javascript/v0.18.0 release tag. Its six programs
are compiled against that tag and render from the same tested source files.
Install and configure
Install the documented release:
npm install @humain-voice/sdk@0.18.0The package targets ES2021 and uses fetch, FormData, and Blob. It does not
declare a minimum Node.js or Bun version. The documentation fixtures are checked
with Node.js 24 and Bun 1.3.14; those are verification environments, not an SDK
support promise.
Set the values issued for your environment:
export API_URL="https://api.voice.humain.com"
export API_KEY="YOUR_API_KEY"
export API_VERSION="v1"Socket.IO clients require only api_url and api_key; release 0.18.0
defaults api_path to /socket.io. Pass a path only for a self-hosted or
proxied deployment that uses an override, or for the legacy
sautech.humain.com endpoint, which requires /realtime/socket.io. Keep
API_KEY in server-side configuration.
A successful example produces an application result, not only a connection: batch writes WebVTT, fast writes SRT, realtime writes finalized WebVTT, live diarization returns a reconciled timeline, and TTS writes a playable WAV file.
Choose a client
| Task | Client | Choose it when |
|---|---|---|
| Transcribe a complete recording | BatchTranscribeClient | The full file already exists, especially a longer meeting, podcast, call, or archive item |
| Transcribe a short complete audio unit with lower latency | FastTranscriptionClient | The complete payload is already available, such as one agentic conversation turn |
| Transcribe audio while it arrives | RealtimeClient | A microphone, call, or live source is still producing audio |
| Build a live speaker timeline | RealtimeDiarizationClient | You need evolving and finalized speaker segments |
| Generate speech | TTSClient | You need streamed PCM output from text |
Fast transcription is not the long-form path. Use batch for meetings, podcasts, and archive media; use fast for short, already-complete, latency-sensitive audio units.
Transcribe a complete recording
The tested batch program enables diarization, polls with a 300-second polling-loop threshold, prints the transcript, and writes finalized WebVTT. Submission and an in-flight request can extend wall time.
| Contract | Released 0.18.0 behavior |
|---|---|
| Constructor | new BatchTranscribeClient({ api_url, api_key, api_version="v1", maxRetries? }) |
| Accepted audio | ArrayBuffer, Uint8Array, Blob, or File |
| Operations | submit(), getResult(), transcribe(), close() |
| Options | submit: diarization, asr, itn, redact; getResult: saveResult; transcribe: those options plus pollInterval (2 s), timeout (300 s), onProgress, saveResult |
| Result and finality | submit() returns JobResponse; the helper succeeds on done, raises on failed, and times out while a job remains queued, processing, or cleared |
| Cleanup and errors | close() is public and currently a no-op. maxRetries is deprecated and ignored; batch failures use the typed hierarchy described in the retry section. |
import { readFile, writeFile } from 'node:fs/promises';
import {
BatchDiarization,
BatchTranscribeClient,
BatchTranscriptionModel,
Language,
Subtitles,
} from '@humain-voice/sdk';
function requiredEnv(name: 'API_KEY' | 'API_URL'): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
async function main(): Promise<void> {
const inputPath = process.argv[2] ?? 'meeting.wav';
const outputPath = process.argv[3] ?? 'meeting.vtt';
const client = new BatchTranscribeClient({
api_url: requiredEnv('API_URL'),
api_key: requiredEnv('API_KEY'),
api_version: process.env.API_VERSION ?? 'v1',
});
try {
const result = await client.transcribe(
await readFile(inputPath),
Language.ArEn,
{
asr: BatchTranscriptionModel.BayanArEn,
diarization: BatchDiarization.On,
saveResult: true,
pollInterval: 2,
timeout: 300,
onProgress: ({ status }) => console.info('status:', status),
},
);
console.info(result.results?.transcript ?? '');
await writeFile(outputPath, Subtitles.fromResponse(result).toVtt(), 'utf8');
} finally {
await client.close();
}
}
void main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
Use submit(audio, language, options) and
getResult(jobId, language, options) when a worker or queue owns polling. A
custom poller must stop on done, failed, and cleared explicitly.
The default saveResult=false can clear a done or failed result after the
response is built. Set saveResult: true before polling when terminal delivery
must survive a lost response; the API does not specify a retention duration.
Subtitles.fromResponse(result) reads result.results.offsets; use toSrt()
or toVtt(). Batch responses also expose diarization_segments when returned
by the legacy result route.
Transcribe a short complete audio unit
Fast transcription sends the complete audio payload once over Socket.IO. It is optimized for latency-sensitive short units such as an agentic conversation turn; it is not the long-meeting or podcast client.
| Contract | Released 0.18.0 behavior |
|---|---|
| Constructor | new FastTranscriptionClient({ api_url, api_key, api_path?, onConnect?, onFileUpload?, onError? }) |
| Accepted audio | ArrayBuffer, Uint8Array, or Blob containing the complete audio payload |
| Operations | connect(), transcribe(), close() |
| Call | transcribe(audio, language, model, { onResponse?, onFileUpload?, onError?, diarizationModel?, itnModel?, redactModel? }) |
| Result and finality | onFileUpload receives the upload acknowledgment; onResponse can receive partials before the final FtTranscribeResponse. The promise returns the final response or undefined. |
| Deadline, cleanup, and errors | There is no SDK timeout option. Apply an app deadline and close explicitly. A routed request error calls onError and then rejects with a generic message-only Error. |
import { readFile, writeFile } from 'node:fs/promises';
import {
FastTranscriptionClient,
FastTranscriptionModel,
Language,
Subtitles,
} from '@humain-voice/sdk';
function requiredEnv(name: 'API_KEY' | 'API_PATH' | 'API_URL'): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
async function withDeadline<T>(operation: Promise<T>, milliseconds: number): Promise<T> {
let timer: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
operation,
new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new Error('Fast transcription deadline exceeded')), milliseconds);
}),
]);
} finally {
if (timer) clearTimeout(timer);
}
}
async function main(): Promise<void> {
const inputPath = process.argv[2] ?? 'short-call.wav';
const outputPath = process.argv[3] ?? 'short-call.srt';
const client = new FastTranscriptionClient({
api_url: requiredEnv('API_URL'),
api_path: requiredEnv('API_PATH'),
api_key: requiredEnv('API_KEY'),
});
try {
await client.connect();
const result = await withDeadline(
client.transcribe(
await readFile(inputPath),
Language.Ar,
FastTranscriptionModel.BayanAr,
{
onFileUpload: (response) => console.info('uploaded:', response?.id),
onResponse: (response) => {
console.info(response.is_final ? 'final:' : 'partial:', response.transcription);
},
onError: (error) => console.error('server error:', error.code, error.message),
},
),
60_000,
);
if (!result) throw new Error('Fast transcription ended without a final result');
await writeFile(outputPath, Subtitles.fromResponse(result).toSrt(), 'utf8');
} finally {
await client.close();
}
}
void main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
The fixture rejects an absent final result and writes SRT only after finality.
Do not blindly resubmit after an ambiguous deadline: the API publishes no
idempotency-key contract. SDK 0.18.0 exposes diarizationModel, itnModel,
and redactModel for wire compatibility, but the validated public Fast service
does not apply them. Omit them; use Batch when those processing options are
required.
Transcribe audio while it arrives
Realtime input is PCM16 little-endian, 16 kHz, mono. The tested program sends 3,200-byte chunks, representing 100 ms of audio, and writes finalized WebVTT.
| Contract | Released 0.18.0 behavior |
|---|---|
| Constructor | new RealtimeClient({ api_url, api_key, api_path? }); the client also exposes handler properties |
| Operations | connect(), startStream(), disconnect() |
| Start | startStream(language, { onConnect?, onDisconnect?, onResponse?, onError?, subtitles? }) |
| Stream | send(audio, isLast=false), close(timeoutSeconds=1), stop() |
| Result and finality | RtTranscribeResponse carries seq, is_final, and is_speech_final; is_speech_final marks an utterance boundary, while only protocol-level is_final ends the stream |
| Cleanup and errors | close() sends the terminator and waits; stop() removes the stream without that wait. A routed error can remove the stream context, so always call client-level disconnect() in finally. |
import { readFile, writeFile } from 'node:fs/promises';
import {
type ErrorResponse,
Language,
RealtimeClient,
Subtitles,
type WordSegment,
} from '@humain-voice/sdk';
const CHUNK_BYTES = 3_200; // 100 ms of PCM16LE, 16 kHz, mono audio.
function requiredEnv(name: 'API_KEY' | 'API_PATH' | 'API_URL'): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
async function pause(milliseconds: number): Promise<void> {
await new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
}
async function main(): Promise<void> {
const inputPath = process.argv[2] ?? 'speech.pcm';
const outputPath = process.argv[3] ?? 'speech.vtt';
const client = new RealtimeClient({
api_url: requiredEnv('API_URL'),
api_path: requiredEnv('API_PATH'),
api_key: requiredEnv('API_KEY'),
});
const finalizedWords: WordSegment[] = [];
let serverError: ErrorResponse | undefined;
let protocolFinalObserved = false;
try {
const stream = await client.startStream(Language.ArEn, {
onResponse: (response) => {
const kind = response.is_final
? 'final'
: response.is_speech_final
? 'speech-final'
: 'partial';
console.info(`${kind}:`, response.transcription);
if (response.is_final) protocolFinalObserved = true;
if (response.is_final || response.is_speech_final) {
// The current public Realtime contract does not guarantee increasing
// seq values, so collect final words in arrival order instead of
// asking RealtimeSubtitles to deduplicate by id:seq.
finalizedWords.push(...response.words);
}
},
// The released SDK can invoke a stream handler more than once for one
// routed error, so keep this callback idempotent.
onError: (error) => {
serverError = error;
},
});
const pcm = await readFile(inputPath);
for (let offset = 0; offset < pcm.length; offset += CHUNK_BYTES) {
await stream.send(pcm.subarray(offset, offset + CHUNK_BYTES));
await pause(100);
}
// close() sends the last frame and waits for protocol is_final, a routed
// error, or this timeout. It resolves rather than throwing on timeout.
await stream.close(5);
if (serverError) {
throw new Error(serverError.message ?? serverError.code ?? 'Realtime stream failed');
}
if (!protocolFinalObserved) {
throw new Error('Realtime stream ended before protocol is_final');
}
await writeFile(outputPath, Subtitles.fromWords(finalizedWords).toVtt(), 'utf8');
} finally {
await client.disconnect();
}
}
void main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
Replace provisional text in observed arrival order until a speech boundary,
but keep the stream open until protocol-level is_final. The fixture collects final-event words itself and renders them with
Subtitles; it does not rely on seq, whose ordering and uniqueness are not
part of the current public wire contract. RealtimeSubtitles deduplicates by
id:seq and can collapse distinct final events under that contract.
stream.close(timeoutSeconds) waits for protocol-level is_final, a routed
error, or its timeout. It resolves rather than throws when that timeout expires;
is_speech_final does not release the wait.
Build a live speaker timeline
The SDK accumulates final-segment deltas and replaces the active tail to expose
one reconciled update.segments timeline.
| Contract | Released 0.18.0 behavior |
|---|---|
| Constructor | new RealtimeDiarizationClient({ api_url, api_key, api_path? }) |
| Operations | connect(), startStream(), disconnect() |
| Start options | language defaults to Language.Ar; connection, update, and error callbacks are optional |
| Stream | Exposes streamId, speakers, send(), close(5), and one async iterator |
| Result and finality | DiarizationUpdate contains reconciled segments, newlyFinalized, activeSegments, isFinal, and raw |
| Cleanup and errors | Consume updates while sending audio, then disconnect. Iterator failures are DiarizationStreamError; close(5) returns the best-known timeline if the final wait expires, but it does not end a waiting iterator on that timeout path. |
import { readFile, writeFile } from 'node:fs/promises';
import {
DIARIZATION_RECOMMENDED_CHUNK_BYTES,
RealtimeDiarizationClient,
type SpeakerSegment,
toRttm,
} from '@humain-voice/sdk';
function requiredEnv(name: 'API_KEY' | 'API_PATH' | 'API_URL'): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
async function pause(milliseconds: number): Promise<void> {
await new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
}
async function main(): Promise<void> {
const inputPath = process.argv[2] ?? 'meeting.pcm';
const outputPath = process.argv[3] ?? 'meeting.rttm';
const client = new RealtimeDiarizationClient({
api_url: requiredEnv('API_URL'),
api_path: requiredEnv('API_PATH'),
api_key: requiredEnv('API_KEY'),
});
try {
let finalObserved = false;
const stream = await client.startStream({
onError: (error) => console.error('server error:', error.code, error.message),
onUpdate: (update) => {
finalObserved ||= update.isFinal;
for (const segment of update.newlyFinalized) {
console.info(segment.speaker, segment.start_time, segment.end_time);
}
},
});
const pcm = await readFile(inputPath);
if (pcm.length === 0 || pcm.length % 2 !== 0) {
throw new Error('Input must be nonempty PCM16 with an even byte length');
}
for (
let offset = 0;
offset < pcm.length;
offset += DIARIZATION_RECOMMENDED_CHUNK_BYTES
) {
await stream.send(
pcm.subarray(offset, offset + DIARIZATION_RECOMMENDED_CHUNK_BYTES),
);
await pause(480);
}
// close() returns the best-known reconciled timeline after five seconds,
// even when no isFinal update arrived. A callback avoids leaving an async
// iterator waiting forever on that timeout path.
const timeline: SpeakerSegment[] = await stream.close(5);
const destination = finalObserved ? outputPath : `${outputPath}.partial`;
await writeFile(destination, toRttm(timeline, 'meeting'), 'utf8');
if (!finalObserved) {
console.warn(`Final result not observed; wrote incomplete output to ${destination}`);
}
} finally {
await client.disconnect();
}
}
void main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
DIARIZATION_RECOMMENDED_CHUNK_BYTES is 15,360 bytes, or 480 ms at the
required audio format. Consuming only after the feed finishes can stall the
workflow. The fixture uses onUpdate so timeout cleanup cannot leave an iterator
waiting; it writes a .partial RTTM file unless isFinal was observed.
Generate speech and write WAV
listVoices() returns multilingual { id, label, profile } entries. The
profile contains shared speaker metadata and an open-ended languages list;
pass its id as voice_id. Handle an empty list before synthesis. Socket.IO TTS returns raw
PCM16 little-endian, 24 kHz, mono bytes, not a WAV container.
For the current Arabic/English profiles, any Arabic-script letter in text
selects Arabic; otherwise English is selected. Physical variant IDs are
internal and rejected.
| Contract | Released 0.18.0 behavior |
|---|---|
| Constructor | new TTSClient({ api_url, api_key, api_path?, verbose?, onConnect?, onError? }); verbose is accepted but has no behavior |
| Operations | connect(), listVoices(), synthesize(), synthesizeStream(), close() |
| Inputs | Text containing at least one Unicode letter or number after trimming, and exactly one of voice_id or non-empty voice_references; for the public route, send one { text, audio } reference whose audio is standard-base64 RIFF/WAVE with non-empty mono PCM16 data |
| Defaults | Voice-list timeout 5 s; model=TtsModel.Nebula; timeoutSeconds=30 seconds of inactivity |
| Other options | onAudio on the buffered call only, onError, and request_id |
| Result, cleanup, and errors | TtsAudioResponse is { id, is_last, audio: Uint8Array }. Close explicitly. onError receives normalized structured data, while a rejected synthesis promise is a generic message-only Error. |
import { writeFile } from 'node:fs/promises';
import {
TTSClient,
TtsModel,
getSampleRate,
} from '@humain-voice/sdk';
function requiredEnv(name: 'API_KEY' | 'API_PATH' | 'API_URL'): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
function pcm16ToWav(pcm: Uint8Array, sampleRate: number): Buffer {
const header = Buffer.alloc(44);
header.write('RIFF', 0);
header.writeUInt32LE(36 + pcm.byteLength, 4);
header.write('WAVE', 8);
header.write('fmt ', 12);
header.writeUInt32LE(16, 16);
header.writeUInt16LE(1, 20); // Linear PCM.
header.writeUInt16LE(1, 22); // Mono.
header.writeUInt32LE(sampleRate, 24);
header.writeUInt32LE(sampleRate * 2, 28);
header.writeUInt16LE(2, 32);
header.writeUInt16LE(16, 34);
header.write('data', 36);
header.writeUInt32LE(pcm.byteLength, 40);
return Buffer.concat([
header,
Buffer.from(pcm.buffer, pcm.byteOffset, pcm.byteLength),
]);
}
async function main(): Promise<void> {
const outputPath = process.argv[2] ?? 'speech.wav';
const client = new TTSClient({
api_url: requiredEnv('API_URL'),
api_path: requiredEnv('API_PATH'),
api_key: requiredEnv('API_KEY'),
});
try {
const voices = await client.listVoices({ timeoutSeconds: 5 });
const voice = voices.find((candidate) => candidate.profile) ?? voices[0];
if (!voice) throw new Error('No TTS voices are available');
if (voice.profile) {
console.log(
'profile:',
voice.label,
voice.profile.speaker.dialect,
voice.profile.languages,
);
}
const model = TtsModel.Nebula;
const pcm = await client.synthesize('Hello from HUMAIN Voice', {
voice_id: voice.id,
model,
// This is an inactivity timeout applied while awaiting each audio chunk.
timeoutSeconds: 30,
onError: (error) => console.error('server error:', error.code, error.message),
});
await writeFile(outputPath, pcm16ToWav(pcm, getSampleRate(model)));
} finally {
await client.close();
}
}
void main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
Use synthesizeStream() to process response.audio as it arrives. A structured
error retains code and retryable; a legacy non-object payload normalizes to
{ message }. The fixture preserves the Uint8Array byte range and adds the
correct WAV header.
The service independently enforces a non-resetting 25-second overall synthesis
deadline and a 60-second inactivity watchdog. If the overall deadline wins,
TTS_DEADLINE_EXCEEDED is retryable and any audio already received is partial.
Public TTS helpers are TtsModel.Nebula, DEFAULT_SAMPLE_RATE,
MODEL_SAMPLE_RATES, getSampleRate(), and decodeTtsAudioFrame().
Retry a preserved batch read
SDK 0.18.0 makes one HTTP call per batch operation. This fixture retries a
result read with bounded exponential backoff and passes saveResult: true
before terminal retrieval. Without that option, a lost terminal response can
be followed by cleared, so the default read is not universally idempotent.
| Exception | Released fields |
|---|---|
BatchTranscribeError | statusCode, payload, code, retryable, jobId, detail, timestamp, capacity, rawBody |
BatchTranscribeAuthError | Authentication failure subtype |
BatchTranscribeTimeoutError | Adds elapsed |
BatchTranscribeJobFailedError | Adds error and errorCode |
BatchTranscribeRateLimitError | Adds retryAfter |
import {
BatchTranscribeClient,
BatchTranscribeError,
BatchTranscribeRateLimitError,
Language,
type TranscriptionResponse,
} from '@humain-voice/sdk';
function requiredEnv(name: 'API_KEY' | 'API_URL'): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
async function pause(milliseconds: number): Promise<void> {
await new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
}
async function getResultWithRetry(
client: BatchTranscribeClient,
jobId: string,
attempts = 5,
): Promise<TranscriptionResponse> {
for (let attempt = 1; attempt <= attempts; attempt += 1) {
try {
// saveResult prevents a terminal read from clearing the stored result
// before a retry. It does not establish a retention duration.
return await client.getResult(jobId, Language.ArEn, { saveResult: true });
} catch (error: unknown) {
if (!(error instanceof BatchTranscribeError)) throw error;
const rateLimited = error instanceof BatchTranscribeRateLimitError;
const retryable = rateLimited || error.retryable === true;
console.error({
statusCode: error.statusCode,
code: error.code,
capacity: error.capacity,
});
if (!retryable || attempt === attempts) throw error;
const serverDelayMs = rateLimited && error.retryAfter !== undefined
? error.retryAfter * 1_000
: 0;
const exponentialDelayMs = 500 * 2 ** (attempt - 1);
await pause(Math.max(serverDelayMs, exponentialDelayMs) + Math.random() * 250);
}
}
throw new Error('Retry loop exhausted');
}
async function main(): Promise<void> {
const jobId = process.argv[2];
if (!jobId) throw new Error('Pass a batch job ID as the first argument');
const client = new BatchTranscribeClient({
api_url: requiredEnv('API_URL'),
api_key: requiredEnv('API_KEY'),
});
try {
const result = await getResultWithRetry(client, jobId);
console.info(result.status, result.results?.transcript ?? '');
} finally {
await client.close();
}
}
void main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
maxRetries is deprecated and ignored. Do not apply this loop blindly to job
submission: after a timeout, the client may not know whether the upload created
a job.
Released response and helper reference
| Type or helper | Released fields / behavior |
|---|---|
JobResponse | jobId, status |
TranscriptionResponse | status; optional results, APIVersion, version, metadata, diarization_segments, error, errorCode. Results contain transcript and offsets; metadata contains sautechVersion, jobId, fileDuration. |
| Batch helpers | isComplete, isFailed, isPending, getJobId, getFileDuration; BatchDiarization, BatchRedact, and status/model constants are exported. |
FileUploadedResponse / FtTranscribeResponse | Upload: id, optional message. Fast result: id, seq, transcription, words, is_final. |
RtTranscribeResponse | Fast result fields plus is_speech_final. |
DiarizationUpdate | id, reconciled segments, newlyFinalized, activeSegments, isFinal, raw. |
SpeakerContext / VoiceProfile / VoiceInfo | { gender, dialect }; { speaker, languages }; { id, label, profile? }. The current API always supplies profile. |
VoiceReference / TtsAudioResponse | { text, audio }; { id, is_last, audio: Uint8Array }. |
ErrorResponse | Optional id, message, code, retryable, timestamp, retry_after_seconds, data, reason, and retry_scope; parseErrorResponse() normalizes legacy non-object payloads. |
An unrouteable socket error can reach only the global callback. Keep an
application deadline and always clean up. Fast request errors call onError
then reject with a generic Error; realtime signals its callback/final wait;
diarization iterators throw DiarizationStreamError; TTS callbacks retain
structured data but rejected synthesis promises keep only the message.
Low-level event and error exports
The top-level package exports generateUuid(), the live/fast frame encoders,
and flag constants.
| Event exports | Wire values |
|---|---|
EVENT_FT_ERROR, EVENT_FT_TRANSCRIBE_FILE, EVENT_FT_TRANSCRIBE_FILE_UPLOAD_SUCCESS, EVENT_FT_TRANSCRIBE_RESULT | error, audio_file, audio_file_upload_success, transcription_result |
EVENT_RT_AUDIO_STREAM, EVENT_RT_END_AUDIO_STREAM | audio_stream, end_audio_stream |
EVENT_DIARIZATION_STREAM, EVENT_DIARIZATION_RESULT | diarization_stream, diarization_result |
EVENT_TTS_REQUEST, EVENT_TTS_AUDIO, EVENT_TTS_ERROR | tts, tts_audio, error |
EVENT_TTS_VOICE_LIST_REQUEST, EVENT_TTS_VOICE_LIST_RESULT | tts_voice_list, tts_voice_list_result |
| Error-code group | Constants |
|---|---|
| Authentication | AUTH_UNAUTHORIZED, AUTH_KEY_INVALID, AUTH_FORBIDDEN |
| Validation | VALIDATION_INVALID_LANGUAGE, VALIDATION_INVALID_FORMAT, VALIDATION_REQUIRED_FIELD, VALIDATION_FILE_CORRUPT, VALIDATION_INVALID_PARAM, VALIDATION_INVALID_UUID |
| Limits and billing | RATE_LIMIT_EXCEEDED, RATE_LIMIT_SERVICE_BUSY, CONCURRENCY_LIMIT_EXCEEDED, CREDITS_EXHAUSTED, BILLING_AUTHORIZATION_UNAVAILABLE, PAYLOAD_TOO_LARGE, AUDIO_DURATION_EXCEEDED, FILE_COUNT_EXCEEDED, CHARACTER_COUNT_EXCEEDED, VOICE_REFERENCE_COUNT_EXCEEDED, and the exported SESSION_* codes |
| ASR | ASR_TRANSCRIPTION_FAILED, ASR_MODEL_NOT_FOUND, ASR_MODEL_UNAVAILABLE, ASR_STREAM_EXPIRED, ASR_UNSUPPORTED_CODEC, ASR_STREAM_NOT_FOUND |
| TTS | TTS_SYNTHESIS_FAILED, TTS_DEADLINE_EXCEEDED, TTS_MODEL_NOT_FOUND, TTS_VOICE_NOT_FOUND, TTS_VOICE_RESOLUTION_FAILED, TTS_VOICE_LIST_FAILED, TTS_INPUT_NOT_ALLOWED, TTS_MODERATION_UNAVAILABLE, TTS_MODEL_UNAVAILABLE, TTS_INVALID_INPUT |
| Speaker and diarization | SPEAKER_ID_FAILED, DIARIZATION_FAILED, DIARIZATION_MODEL_NOT_FOUND |
| Server, batch, and compatibility | SERVER_INTERNAL, SERVER_DEPENDENCY_FAILURE, METHOD_NOT_ALLOWED, TRANSCRIPTION_JOB_NOT_FOUND, RATE_LIMITED, VALIDATION_FAILED, INTERNAL_ERROR |
Workload-limit, billing, and TTS content-policy errors are routed to the active
request context in 0.18.0. Fast and TTS requests reject after their structured callbacks;
Realtime invokes onError and releases its final wait; diarization surfaces a
DiarizationStreamError from the iterator. Read data for limit evidence,
honor retry_after_seconds for retryable pressure, and open a fresh stream when
ASR_STREAM_EXPIRED carries retry_scope: "new_stream".
Use isAsrCode(), isTtsCode(), isRequestScopedCode(),
isRealtimeOwned(), isTtsOwned(), isDiarizationCode(), and
isDiarizationOwned() to route structured socket errors. These classifiers do
not replace a workflow deadline or cleanup for an unrouteable error.
Subtitle reference
| API | Contract |
|---|---|
Subtitles | SubtitleCue, SubtitleOptions, SubtitleRenderOptions, SubtitleError; constructor from cues; cues; fromWords, fromCues, fromResponse; toSrt, toVtt |
RealtimeSubtitles | words, cues, addResponse, subtitles, toSrt, toVtt; ignores partials and deduplicates finalized id:seq responses |
| Top-level helpers | wordsToCues, cuesToSrt, cuesToVtt, subtitles, toSrt, toVtt |
| Shaping defaults | maxDurationSeconds=6, maxGapSeconds=0.7, minDurationSeconds=0.5, maxCharsPerLine=42, maxLines=2, splitOnSpeakerChange=true, strict=false; SRT startIndex=1 |
Subtitle input accepts batch camel-case offsets and realtime snake-case words. Use strict mode when malformed or out-of-order timing must fail rather than be normalized or skipped.