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.wavand write captions tomeeting.vtt. - One supported SDK runtime:
- JavaScript / TypeScript: a server-side Node.js or Bun runtime that supports
ES2021,
fetch,FormData, andBlob. 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 directnodecommands below assume that Node.js 24 verification environment. - Python 3.10 or newer.
- JavaScript / TypeScript: a server-side Node.js or Bun runtime that supports
ES2021,
ffmpegonly 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.0Expected: 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.
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.0returns ondoneand raises onfailedor timeout. Itstranscribe()helper does not stop specially oncleared, so a cleared job reaches the configured timeout. A direct poller must stop ondone,failed, andclearedexplicitly. - 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
| Mode | Use it when | Audio delivery | Result flow |
|---|---|---|---|
| Batch transcription | A complete recording, including longer meetings, calls, or podcasts | Upload once | Poll a job until done, failed, or cleared |
| Fast transcription | A short, already-complete audio payload needs lower latency, such as one agentic conversation turn | Send the complete payload once over Socket.IO | Receive upload and transcription events, ending with a final result |
| Realtime transcription | Audio is still arriving from a microphone, call, or live source | Send PCM16 little-endian, 16 kHz, mono chunks | Replace 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.pcmExpected: ffmpeg exits successfully and creates speech.pcm. Raw PCM has
no playable file header.
Save the selected fixture under its displayed filename:
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.