API Guides

Realtime HTTP

Choose complete-audio, live framed, or text-to-speech HTTP operations and handle their streams safely.

The /realtime/http/* operations are direct platform APIs. SDK 0.18.0 doesn't wrap them. Use them only from a trusted runtime that can protect x-api-key, enforce deadlines, and implement the validated OpenAPI framing.

1. Choose the operation by input lifecycle

Input and outcomeChooseRequest unitCompletion signal
A long or large complete meeting, podcast, or archiveBatch REST, not a realtime HTTP operationOne complete file, then job pollingBatch done, failed, or cleared
One complete latency-sensitive conversational unitPOST /realtime/http/sttOne multipart/form-data fileNDJSON STTResponse with is_final: true
Audio still arriving when you need live textPOST /realtime/http/stt-streamOne 18-byte-header-plus-PCM body per requestAn observed is_speech_final marks a speech boundary; only an observed is_final completes the stream
Audio still arriving when you need a speaker timelinePOST /realtime/http/diarization-streamOne framed PCM body per serialized requestAn observed record with is_final: true; an active provisional tail can remain
Text must become speechSDK Socket.IO TTS for playable output; direct HTTP only for a protocol captureOne JSON requestSDK completion; direct HTTP has no generically detectable frame boundary

Fast transcription streams result lines, but its input is still one complete file. It isn't a live microphone transport.

2. Prepare authentication, identifiers, and deadlines

  • Obtain API_KEY through the authentication flow, and use the API_URL rendered for this environment.
  • Send x-api-key on every request from a trusted backend. Browser or mobile code can't keep this credential secret. The key also needs the provisioned capability: realtime ASR for Fast/live ASR, diarization for the speaker stream, or TTS for synthesis.
  • Generate a valid UUID for id. Fast STT carries it in the query, TTS carries it in JSON, and live frames carry its 16 raw bytes. Reuse one UUID only for the requests in the same live stream.
  • Set a connect timeout, a finite timeout for each request and read, and an overall operation deadline. Validate the HTTP status before parsing a success stream.
  • Buffer NDJSON across transport reads and split only on newline. One network read can contain part of a line or several lines.

API_PATH applies to Socket.IO clients and isn't used by these HTTP routes.

3. Send one complete unit for fast transcription

Supply a UUID in id and one complete file in the multipart file field. Fast applies only language and ASR model selection:

SelectorAccepted values
language or langen, ar, codeswitch, auto
asr or modelPublished model wire value

A nonempty language takes precedence over lang; a nonempty asr takes precedence over model. Omitted or auto language and an omitted model use defaults configured for the environment. Use Batch when diarization, ITN, or redaction is required.

export REQUEST_ID="7f51f2c2-e7bc-41c8-a850-f848df2ddfc8"
curl -N --fail-with-body --connect-timeout 10 --max-time 120 \
  "${API_URL%/}/realtime/http/stt?id=$REQUEST_ID&language=codeswitch&asr=bayan_cs_ar_en" \
  -H "x-api-key: $API_KEY" \
  -F "file=@turn.wav"

HTTP 200 returns application/x-ndjson. Each non-empty line is one complete JSON object; for example:

{"id":"7f51f2c2-e7bc-41c8-a850-f848df2ddfc8","seq":0,"transcription":"hello wor","words":[{"start_time":0.0,"end_time":0.45,"word":"hello"}],"is_final":false}
{"id":"7f51f2c2-e7bc-41c8-a850-f848df2ddfc8","seq":0,"transcription":"hello world","words":[{"start_time":0.0,"end_time":0.45,"word":"hello"},{"start_time":0.46,"end_time":0.9,"word":"world"}],"is_final":true}

Process records in observed arrival order and retain seq only for diagnostics; the current public Fast contract does not define ordering or uniqueness for it. Treat is_final: false as provisional and finish the request state only after is_final: true. Don't treat raw HTTP read chunks as records or assume that every provisional transcription is append-only.

After partial output has started, a later failure ends the partial HTTP 200 without appending an error JSON record. EOF, cancellation, or an application deadline without is_final: true is incomplete and ambiguous; the operation defines no replay contract, so do not blindly resubmit the audio.

4. Frame live ASR and diarization

Both live operations accept application/octet-stream. Every request body has this layout:

HTTP live input frame
UUID
bytes 0..15
The same 16 UUID bytes for every request in one stream.
Flags
byte 16
bit 0 start, bit 1 final.
Language
byte 17
0 ar, 1 en, 2 codeswitch, 255 auto.
PCM
bytes 18..end
Nonempty raw mono PCM16 little-endian, 16 kHz, even byte count, no WAV header.

Use one fresh nonzero UUID throughout. Set the start bit on the first request, neither flag on intermediate requests, and the final bit on the last request; set both for a one-chunk stream and keep reserved flag bits zero. Every request must carry audio. A framed file such as frame.bin can be sent with --data-binary; it isn't an audio file by itself because it includes the 18-byte control header.

Build a valid frame

These tested builders default to a one-chunk stream, so both boundary flags are set. For multiple chunks, reuse STREAM_ID, set IS_FINAL=0 on the first chunk, set both flags to 0 on intermediate chunks, and set only IS_FINAL=1 on the last chunk.

http-realtime-frame.ts
import { randomUUID } from 'node:crypto';
import { readFile, writeFile } from 'node:fs/promises';

const languageBytes = {
  ar: 0,
  en: 1,
  codeswitch: 2,
  auto: 255,
} as const;

function uuidBytes(id: string): Uint8Array {
  const hex = id.replaceAll('-', '');
  if (!/^[0-9a-f]{32}$/i.test(hex) || /^0{32}$/.test(hex)) {
    throw new Error('STREAM_ID must be a nonzero UUID');
  }
  return Uint8Array.from(hex.match(/.{2}/g)!, (byte) => Number.parseInt(byte, 16));
}

function frame(
  id: string,
  pcm16le: Uint8Array,
  options: { language: keyof typeof languageBytes; isStart: boolean; isFinal: boolean },
): Uint8Array {
  if (pcm16le.byteLength === 0 || pcm16le.byteLength % 2 !== 0) {
    throw new Error('PCM16 payload must be nonempty and contain an even number of bytes');
  }

  const output = new Uint8Array(18 + pcm16le.byteLength);
  output.set(uuidBytes(id), 0);
  output[16] = (options.isStart ? 1 : 0) | (options.isFinal ? 2 : 0);
  output[17] = languageBytes[options.language];
  output.set(pcm16le, 18);
  return output;
}

async function main(): Promise<void> {
  const inputPath = process.argv[2] ?? 'chunk.pcm';
  const outputPath = process.argv[3] ?? 'frame.bin';
  const streamId = process.env.STREAM_ID ?? randomUUID();
  const pcm = await readFile(inputPath);

  // Defaults build a valid one-chunk stream. For a longer stream, reuse
  // STREAM_ID and set only the boundary flags for each arriving PCM chunk.
  const body = frame(streamId, pcm, {
    language: 'codeswitch',
    isStart: process.env.IS_START !== '0',
    isFinal: process.env.IS_FINAL !== '0',
  });
  await writeFile(outputPath, body);
  console.info({ streamId, bytes: body.byteLength, outputPath });
}

void main().catch((error: unknown) => {
  console.error(error);
  process.exitCode = 1;
});

Live ASR

curl -N --fail-with-body --connect-timeout 10 --max-time 30 \
  -X POST "${API_URL%/}/realtime/http/stt-stream" \
  -H "x-api-key: $API_KEY" \
  -H "content-type: application/octet-stream" \
  --data-binary @frame.bin

Send one POST per framed audio chunk. Each HTTP 200 contains zero or more NDJSON records with id, seq, transcription, words, is_speech_final, and is_final. A later failure ends a partial 200 without appending an error record. Buffer across reads and parse complete lines. is_speech_final marks a detected speech-segment boundary; only an observed is_final: true completes the whole stream. The request final bit and an ending response, including an empty 200, do not. Treat seq as opaque and reconcile provisional text by id and observed arrival order as described in the realtime lifecycle guide.

The public contract does not define whether chunk POSTs should overlap or be serialized. Use only the coordination pattern provisioned for your environment; do not infer routing safety from ordinary HTTP concurrency.

A normal non-final response window ends after two seconds and preserves the session. Aborting a POST or timing out the final response cancels it. The session expires after 60 seconds without accepted client audio or an inference response.

POST /realtime/http/realtime-asr is a compatibility path. New clients should use the canonical POST /realtime/http/stt-stream operation.

Live diarization

curl -N --fail-with-body --connect-timeout 10 --max-time 30 \
  -X POST "$API_URL/realtime/http/diarization-stream" \
  -H "x-api-key: $API_KEY" \
  -H "content-type: application/octet-stream" \
  --data-binary @frame.bin

This operation has no query parameters, multipart form, or client model selector. The language byte must be 0, 1, 2, or 255, but the service discards it after validation. Mark the last real audio frame final because an empty terminator is invalid. A stream that never received a start frame returns HTTP 400 with VALIDATION_REQUIRED_FIELD.

Keep at most one POST in flight for each stream UUID. Capture audio concurrently into a bounded queue, but use one sender to drain it and close each response before sending the next frame. Concurrent same-UUID requests can overwrite response ownership; distinct UUID streams can run concurrently.

Each HTTP 200 contains zero or more NDJSON records with id, final_segments, active_segments, and is_final. A later failure ends a partial 200 without appending an error record. Accumulate unseen final_segments because each array is a per-record delta. Replace the prior active_segments snapshot, then sort the reconciled final-plus-active timeline by start_time. Speaker labels are relative to one stream, not identities, and segment times are relative to its start.

Only an observed is_final: true completes the stream. A final request bit, empty 200, EOF, or response deadline does not. A final record can retain a nonempty active tail; preserve it as provisional instead of silently marking it final. A normal non-final response window ends after two seconds and preserves the session. Aborting a POST or timing out the final response cancels it, and the session expires after 60 seconds without client or inference activity. There is no chunk replay, resume, or idempotency contract. After an ambiguous failure, close every response, mark the timeline incomplete, and recover with a fresh UUID rather than replaying an old chunk.

5. Request HTTP TTS with its output contract in mind

The JSON body requires a fresh id and text containing at least one Unicode letter or number after trimming. Optional fields are model, voice_id, and voice_references. For a predictable voice, send one UUID voice_id or one reference whose audio is standard-base64 RIFF/WAVE with non-empty mono PCM16 data and whose text is its transcript. The selectors are mutually exclusive. Obtain voice_id through SDK listVoices() or list_voices(); there is no HTTP voice-list operation. Set model to nebula explicitly instead of relying on the deployment-configured default, which falls back to nebula.

curl --fail-with-body --connect-timeout 10 --max-time 120 \
  -X POST "$API_URL/realtime/http/tts" \
  -H "x-api-key: $API_KEY" \
  -H "content-type: application/json" \
  --data '{"id":"7f51f2c2-e7bc-41c8-a850-f848df2ddfc8","text":"Hello from HUMAIN Voice","model":"nebula"}' \
  --output tts-frames.bin

HTTP 200 returns a continuous application/octet-stream body:

HTTP TTS service frame
UUID
bytes 0..15
The request UUID as 16 raw bytes.
Final
byte 16
1 for the final service frame; otherwise 0.
PCM
bytes 17..end
PCM16 little-endian, 16 kHz audio.

The service appends a final frame even when that frame has no additional PCM. The contract defines no frame-length field or delimiter. HTTP reader chunks are transport chunks and aren't guaranteed to match service-frame boundaries, so a generic client can't safely remove 17 bytes from every read. The example's tts-frames.bin is a protocol capture, not a playable PCM or WAV file.

If synthesis fails after bytes were committed, the partial binary 200 stream simply ends: no structured error JSON is appended. A missing boundary-aware final flag, premature EOF, or deadline therefore leaves an incomplete capture with no in-band explanation, and only a failure occurring before output was committed can be reported as structured JSON. Aborting the HTTP request cancels its in-flight synthesis. Direct HTTP model, capacity and inference failures are reported as retryable 500 TTS_SYNTHESIS_FAILED; a gateway can independently return 429. A content-policy rejection is non-retryable 400 TTS_INPUT_NOT_ALLOWED; change the text instead of resending it. If the moderation authority cannot decide, synthesis fails closed with retryable 503 TTS_MODERATION_UNAVAILABLE; do not misreport that infrastructure failure as prohibited content. A caller-supplied voice_id no longer collapses into TTS_SYNTHESIS_FAILED (SAU-2258): an unparseable voice_id is 400 VALIDATION_INVALID_UUID and a well-formed one that does not identify an available voice is 400 TTS_VOICE_NOT_FOUND (both non-retryable); a resolved voice whose stored data is incomplete or corrupt is 500 TTS_VOICE_RESOLUTION_FAILED (non-retryable); and a transient database/storage outage during voice resolution is 503 SERVER_DEPENDENCY_FAILURE (retryable).

Text and voice-reference problems no longer collapse that way. Over-limit text, an over-limit reference transcript, more than one reference, and a reference clip longer than the deployment's configured reference ceiling are non-retryable 422; a reference whose decoded size is over the ceiling is 413; and a malformed reference, supplying both voice selectors, or an explicit empty voice_references array are non-retryable 400. All are checked before any model lookup, admission or charge, and each limit rejection carries a data object naming the bound, its configured value and the observed value. See Errors and rate limits.

Until your direct client has an unambiguous frame-boundary mechanism, use SDK Socket.IO TTS and the TTS-to-WAV recipe for playable output. Don't assume the Socket.IO 24 kHz output format for this 16 kHz HTTP operation.

6. Bound failures and clean up every stream

  1. Validate the HTTP status before selecting the NDJSON or binary success parser. A deployment gateway can return 429; current backends can instead collapse capacity failures into retryable 500 with ASR_TRANSCRIPTION_FAILED, DIARIZATION_FAILED, or TTS_SYNTHESIS_FAILED. Treat each layer's actual status and body as evidence, and do not infer a numeric quota or reset window.
  2. For ErrorResponse, branch on code and retryable, not the wording of error, detail, or message.
  3. On a live-stream failure or deadline, stop sending frames, abort the request, close its response reader, and start recovery with a fresh UUID. Session resume after interruption isn't documented.
  4. A timeout after sending the complete multipart file is ambiguous. The contract doesn't define idempotent replay, so don't repeat it blindly.
  5. If a boundary-aware TTS client ends without a final frame, keep partial bytes separate from complete output and close the response. Don't infer completion from connection close alone.

Apply bounded retry only when the structured error permits it, the operation is safe under your application policy, and the overall deadline remains. See Errors and Rate Limits.

7. Continue with the contract and production checks

Start with the smallest representative request for the chosen operation and verify its documented final signal. Keep the generated OpenAPI reference beside your implementation for exact parameters, schemas, and errors, then exercise deadlines, malformed frames, disconnects, and cleanup before launch.

On this page