SDKs

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.0

The 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

TaskClientChoose it when
Transcribe a complete recordingBatchTranscribeClientThe full file already exists, especially a longer meeting, podcast, call, or archive item
Transcribe a short complete audio unit with lower latencyFastTranscriptionClientThe complete payload is already available, such as one agentic conversation turn
Transcribe audio while it arrivesRealtimeClientA microphone, call, or live source is still producing audio
Build a live speaker timelineRealtimeDiarizationClientYou need evolving and finalized speaker segments
Generate speechTTSClientYou 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.

ContractReleased 0.18.0 behavior
Constructornew BatchTranscribeClient({ api_url, api_key, api_version="v1", maxRetries? })
Accepted audioArrayBuffer, Uint8Array, Blob, or File
Operationssubmit(), getResult(), transcribe(), close()
Optionssubmit: diarization, asr, itn, redact; getResult: saveResult; transcribe: those options plus pollInterval (2 s), timeout (300 s), onProgress, saveResult
Result and finalitysubmit() returns JobResponse; the helper succeeds on done, raises on failed, and times out while a job remains queued, processing, or cleared
Cleanup and errorsclose() is public and currently a no-op. maxRetries is deprecated and ignored; batch failures use the typed hierarchy described in the retry section.
batch-transcription.ts
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.

ContractReleased 0.18.0 behavior
Constructornew FastTranscriptionClient({ api_url, api_key, api_path?, onConnect?, onFileUpload?, onError? })
Accepted audioArrayBuffer, Uint8Array, or Blob containing the complete audio payload
Operationsconnect(), transcribe(), close()
Calltranscribe(audio, language, model, { onResponse?, onFileUpload?, onError?, diarizationModel?, itnModel?, redactModel? })
Result and finalityonFileUpload receives the upload acknowledgment; onResponse can receive partials before the final FtTranscribeResponse. The promise returns the final response or undefined.
Deadline, cleanup, and errorsThere 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.
fast-transcription.ts
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.

ContractReleased 0.18.0 behavior
Constructornew RealtimeClient({ api_url, api_key, api_path? }); the client also exposes handler properties
Operationsconnect(), startStream(), disconnect()
StartstartStream(language, { onConnect?, onDisconnect?, onResponse?, onError?, subtitles? })
Streamsend(audio, isLast=false), close(timeoutSeconds=1), stop()
Result and finalityRtTranscribeResponse 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 errorsclose() 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.
realtime-transcription.ts
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.

ContractReleased 0.18.0 behavior
Constructornew RealtimeDiarizationClient({ api_url, api_key, api_path? })
Operationsconnect(), startStream(), disconnect()
Start optionslanguage defaults to Language.Ar; connection, update, and error callbacks are optional
StreamExposes streamId, speakers, send(), close(5), and one async iterator
Result and finalityDiarizationUpdate contains reconciled segments, newlyFinalized, activeSegments, isFinal, and raw
Cleanup and errorsConsume 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.
realtime-diarization.ts
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.

ContractReleased 0.18.0 behavior
Constructornew TTSClient({ api_url, api_key, api_path?, verbose?, onConnect?, onError? }); verbose is accepted but has no behavior
Operationsconnect(), listVoices(), synthesize(), synthesizeStream(), close()
InputsText 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
DefaultsVoice-list timeout 5 s; model=TtsModel.Nebula; timeoutSeconds=30 seconds of inactivity
Other optionsonAudio on the buffered call only, onError, and request_id
Result, cleanup, and errorsTtsAudioResponse is { id, is_last, audio: Uint8Array }. Close explicitly. onError receives normalized structured data, while a rejected synthesis promise is a generic message-only Error.
tts-to-wav.ts
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.

ExceptionReleased fields
BatchTranscribeErrorstatusCode, payload, code, retryable, jobId, detail, timestamp, capacity, rawBody
BatchTranscribeAuthErrorAuthentication failure subtype
BatchTranscribeTimeoutErrorAdds elapsed
BatchTranscribeJobFailedErrorAdds error and errorCode
BatchTranscribeRateLimitErrorAdds retryAfter
batch-error-retry.ts
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 helperReleased fields / behavior
JobResponsejobId, status
TranscriptionResponsestatus; optional results, APIVersion, version, metadata, diarization_segments, error, errorCode. Results contain transcript and offsets; metadata contains sautechVersion, jobId, fileDuration.
Batch helpersisComplete, isFailed, isPending, getJobId, getFileDuration; BatchDiarization, BatchRedact, and status/model constants are exported.
FileUploadedResponse / FtTranscribeResponseUpload: id, optional message. Fast result: id, seq, transcription, words, is_final.
RtTranscribeResponseFast result fields plus is_speech_final.
DiarizationUpdateid, 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 }.
ErrorResponseOptional 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 exportsWire values
EVENT_FT_ERROR, EVENT_FT_TRANSCRIBE_FILE, EVENT_FT_TRANSCRIBE_FILE_UPLOAD_SUCCESS, EVENT_FT_TRANSCRIBE_RESULTerror, audio_file, audio_file_upload_success, transcription_result
EVENT_RT_AUDIO_STREAM, EVENT_RT_END_AUDIO_STREAMaudio_stream, end_audio_stream
EVENT_DIARIZATION_STREAM, EVENT_DIARIZATION_RESULTdiarization_stream, diarization_result
EVENT_TTS_REQUEST, EVENT_TTS_AUDIO, EVENT_TTS_ERRORtts, tts_audio, error
EVENT_TTS_VOICE_LIST_REQUEST, EVENT_TTS_VOICE_LIST_RESULTtts_voice_list, tts_voice_list_result
Error-code groupConstants
AuthenticationAUTH_UNAUTHORIZED, AUTH_KEY_INVALID, AUTH_FORBIDDEN
ValidationVALIDATION_INVALID_LANGUAGE, VALIDATION_INVALID_FORMAT, VALIDATION_REQUIRED_FIELD, VALIDATION_FILE_CORRUPT, VALIDATION_INVALID_PARAM, VALIDATION_INVALID_UUID
Limits and billingRATE_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
ASRASR_TRANSCRIPTION_FAILED, ASR_MODEL_NOT_FOUND, ASR_MODEL_UNAVAILABLE, ASR_STREAM_EXPIRED, ASR_UNSUPPORTED_CODEC, ASR_STREAM_NOT_FOUND
TTSTTS_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 diarizationSPEAKER_ID_FAILED, DIARIZATION_FAILED, DIARIZATION_MODEL_NOT_FOUND
Server, batch, and compatibilitySERVER_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

APIContract
SubtitlesSubtitleCue, SubtitleOptions, SubtitleRenderOptions, SubtitleError; constructor from cues; cues; fromWords, fromCues, fromResponse; toSrt, toVtt
RealtimeSubtitleswords, cues, addResponse, subtitles, toSrt, toVtt; ignores partials and deduplicates finalized id:seq responses
Top-level helperswordsToCues, cuesToSrt, cuesToVtt, subtitles, toSrt, toVtt
Shaping defaultsmaxDurationSeconds=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.

Next steps

On this page