Quickstart

Install SDK 0.18.0, complete a first batch transcription, then choose fast or realtime delivery.

The shortest path to a first result is batch transcription of a complete audio file. It accepts a supported audio container and does not require you to prepare realtime PCM chunks. Complete that path first, then choose fast or realtime delivery if your product needs it.

Before you start

Have these ready before running a command:

  • An API key and Socket.IO path obtained through your organization's access flow. The page renders the base URL configured for its environment.
  • A complete, supported audio file. The examples below use meeting.wav and write captions to meeting.vtt.
  • One supported SDK runtime:
    • JavaScript / TypeScript: a server-side Node.js or Bun runtime that supports ES2021, fetch, FormData, and Blob. The SDK does not publish a minimum Node.js or Bun version. The fixtures are checked with Node.js 24 and Bun 1.3.14; the direct node commands below assume that Node.js 24 verification environment.
    • Python 3.10 or newer.
  • ffmpeg only if you plan to try the optional realtime path.

Keep the API key in server-side configuration. Do not put it in browser or mobile code.

1. Install SDK 0.18.0

Choose one language. The page uses the same JavaScript and Python tab labels for every alternative.

npm install @humain-voice/sdk@0.18.0

Expected: the package manager completes successfully and records the exact SDK version 0.18.0.

2. Configure the environment

Run these exports in the same shell that will run the example. Replace the key with the value configured for your organization.

export API_URL="https://api.voice.humain.com"
export API_PATH="/socket.io"
export API_KEY="YOUR_API_KEY"
export API_VERSION="v1"

test -n "$API_URL" && test -n "$API_PATH" && test -n "$API_KEY" && echo "HUMAIN Voice environment ready"

Expected: the last command prints HUMAIN Voice environment ready.

This environment publishes Socket.IO at /socket.io. Release 0.18.0 defaults every Socket.IO client to /socket.io. Set API_PATH only when a self-hosted or proxied deployment requires an override. The legacy sautech.humain.com endpoint requires /realtime/socket.io. The batch client uses API_URL, API_KEY, and API_VERSION only.

3. Run a batch transcription

Use the code-block copy button and save the selected fixture under its displayed filename. It submits meeting.wav, polls with a five-minute deadline, enables diarization, prints the returned transcript, and writes WebVTT captions.

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;
});

Run the command for the file you saved:

  • JavaScript / TypeScript: node batch-transcription.ts meeting.wav meeting.vtt
  • Python: python batch_transcription.py meeting.wav meeting.vtt

Expected: on a successful job, the terminal prints one or more status: updates followed by the returned transcript, and meeting.vtt is created. Audio with no recognized speech can produce an empty transcript.

Your first HUMAIN Voice request is complete when the job reaches done and the caption file is written.

After the first result

  • The fixture polls every two seconds with a 300-second polling-loop threshold. Submission and an in-flight fetch can extend wall time. Choose deadlines for your own workload; a request timeout is not an overall workflow deadline.
  • SDK 0.18.0 returns on done and raises on failed or timeout. Its transcribe() helper does not stop specially on cleared, so a cleared job reaches the configured timeout. A direct poller must stop on done, failed, and cleared explicitly.
  • The fixture closes its client even when submission or polling fails. Preserve that cleanup pattern in production.

The batch recording recipe expands the polling, speaker-label, and subtitle patterns.

Choose the next delivery mode

ModeUse it whenAudio deliveryResult flow
Batch transcriptionA complete recording, including longer meetings, calls, or podcastsUpload oncePoll a job until done, failed, or cleared
Fast transcriptionA short, already-complete audio payload needs lower latency, such as one agentic conversation turnSend the complete payload once over Socket.IOReceive upload and transcription events, ending with a final result
Realtime transcriptionAudio is still arriving from a microphone, call, or live sourceSend PCM16 little-endian, 16 kHz, mono chunksReplace provisional text until a final or speech-final response arrives

Fast transcription is not the long-audio or podcast path. Use batch for those complete recordings; use fast when the complete payload is short and latency matters.

Optional: run realtime transcription

Realtime input must already be raw PCM16 little-endian, 16 kHz, mono. Convert a recording for this walkthrough:

ffmpeg -i input.wav -f s16le -acodec pcm_s16le -ar 16000 -ac 1 speech.pcm

Expected: ffmpeg exits successfully and creates speech.pcm. Raw PCM has no playable file header.

Save the selected fixture under its displayed filename:

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;
});

Run the command for the file you saved:

  • JavaScript / TypeScript: node realtime-transcription.ts speech.pcm speech.vtt
  • Python: python realtime_transcription.py speech.pcm speech.vtt

Expected: the terminal labels responses as partial:, final:, or speech-final:, and the successful stream writes finalized captions to speech.vtt.

After success, keep provisional UI text separate and replace it as result events arrive. The fixture collects words only from final or speech-final events, then uses Subtitles to render them; it does not rely on seq, whose ordering is not part of the current Realtime wire contract. It closes the stream, waits up to five seconds for protocol-level is_final, and disconnects the client in cleanup. A timeout is reported as incomplete instead of writing a normal caption file.

Next steps

Continue with the path that matches your product. Before production traffic, re-run it with representative inputs and exercise deadlines, terminal states, disconnects, retries, and cleanup regardless of delivery mode.

On this page