Troubleshooting

Diagnose SDK 0.18.0 connection, mode, audio, finality, result, TTS, rate-limit, and cleanup failures.

This page targets @humain-voice/sdk@0.18.0 and humain-voice==0.18.0. Diagnose from evidence: capture the status, event, identifier, and final signal before changing configuration or retrying.

Diagnose in this order

  1. Confirm the installed package is exactly 0.18.0.
  2. Confirm API_URL and API_KEY are present in the server process.
  3. Choose the processing mode from the input you actually have.
  4. Reproduce with one small, known input and one request. Turn off concurrent retries while isolating the failure.
  5. Record structured error fields and whether cleanup completed.

Run only the package-version command relevant to your app. The shell loop reports presence without printing the secret; do not replace it with env or another command that exposes API_KEY.

npm ls @humain-voice/sdk --depth=0
python -c 'from importlib.metadata import version; print(version("humain-voice"))'

for name in API_URL API_KEY; do
  if [ -n "$(printenv "$name")" ]; then
    printf "%s=set\n" "$name"
  else
    printf "%s=missing\n" "$name"
  fi
done

Choose the correct processing mode

SymptomLikely causeEvidence or checkFix
A long meeting, podcast, or archive file stalls in fast transcriptionFast transcription was used for long-form mediaThe entire recording existed before the request and is long-formUse BatchTranscribeClient; poll under a finite deadline
A bounded conversational turn has unnecessary streaming complexityRealtime was used although the complete turn already existsNo audio arrives after the request startsUse FastTranscriptionClient for the already-complete, latency-sensitive unit
A microphone or call is repeatedly uploaded as completed filesBatch or fast mode was used while audio is still arrivingProcessing must begin before recording endsUse RealtimeClient or RealtimeDiarizationClient and send framed PCM as it arrives
A complete file is sent as realtime PCM, or PCM is uploaded as a fileContainer and stream inputs were confusedCompare the input bytes with the selected operation's contractSend an encoded file to batch or fast; send headerless PCM16 LE to realtime

Mode selection does not promise a particular latency. It selects the lifecycle and input contract that match the task.

Connection and authentication symptoms

SymptomLikely causeEvidence or checkFix
REST returns 401 or 403The key is missing, invalid, or lacks access to the operationRecord the HTTP status and structured code; confirm only that API_KEY is setSend the provisioned value as x-api-key or api_key; resolve invalid or access-denied credentials through your organization's approved access flow
A Socket.IO constructor says URL or key is requiredapi_url or api_key is emptyLog the option names and presence, never the key valueSupply the provisioned API_URL and API_KEY; api_path defaults to /socket.io
Socket.IO raises connect_error or never calls the connection handlerThe host or path is wrong, the WebSocket upgrade is blocked, or the handshake is rejectedCompare API_URL and the effective /socket.io path with the issued values; in Python, reproduce once with verbose=True and retain the handshake errorUse the default path unless your deployment documents an override, keep WebSocket transport enabled, and configure the proxy to preserve the upgrade
REST works but every Socket.IO capability failsThe Socket.IO route or WebSocket upgrade is blockedA protected REST call succeeds while the Socket.IO handshake fails before any app eventTest /socket.io from the same server network; set API_PATH only for a documented override
A Socket.IO handshake is rejected before any app eventIts required Origin header is missing or does not match the service originCompare the sanitized handshake headers; the body can be a gateway page rather than a structured platform errorSet Origin to the scheme and host of API_URL. A direct Socket.IO client must set it; SDK 0.18.0 derives it from api_url.
A hand-built Socket.IO client connects differently from the SDKThe path, API-key header, or transport differsInspect the sanitized handshake: path, transport, and x-api-key presenceSend x-api-key, select transports: ["websocket"], and register handlers before connecting

Release 0.18.0 defaults Socket.IO to /socket.io; use API_PATH only for a documented override. The legacy sautech.humain.com endpoint requires /realtime/socket.io. Keep credentials in a server-side process; moving a key into a browser or mobile bundle is not a connection fix.

Audio and framing symptoms

Inspect an encoded source, then create the exact raw realtime input when needed:

ffprobe -v error -select_streams a:0 \
  -show_entries stream=codec_name,sample_rate,channels,sample_fmt \
  -of default=noprint_wrappers=1 input.wav

ffmpeg -i input.wav -ar 16000 -ac 1 -c:a pcm_s16le \
  -f s16le realtime.pcm
SymptomLikely causeEvidence or checkFix
Batch returns 422 with VALIDATION_FILE_CORRUPTThe uploaded file is corrupt or unsupportedRun ffprobe; retain the status, code, and safe file metadataDecode or transcode to a valid encoded audio file, then retry once as a new submission
Fast transcription accepts an upload but produces no useful final resultThe complete payload uses an unsupported container or malformed MP4Confirm it is AAC, FLAC, MP3, MP4, or WAV; inspect MP4 layoutSend one complete supported file; place the MP4 moov atom at the front
Realtime text is empty, garbled, too fast, or too slowA WAV/MP3 container, big-endian samples, wrong sample rate, or wrong channel count was sent as PCMffprobe the source and inspect the conversion command; PCM payload length must be evenSend headerless PCM16 little-endian, 16 kHz, mono bytes
A direct realtime client receives nothingThe 18-byte application header, UUID, flags, or language byte is wrongInspect bytes 0..17; verify one UUID is reused and audio starts at byte 18For audio_stream, send flags 1 once, 0 between, and 2 once at the end; use the documented language byte
Direct live diarization never finalizesdiarization_stream framing or the final flag is missingVerify the same 18-byte header, one UUID, start bit, and final bitSend PCM16 LE at 16 kHz mono and exactly one final frame; keep the other flag bits zero
Updates arrive in an uneven cadencePayload sizes differ substantially from the tested helpersCount audio bytes after the 18-byte headerStart with 3,200 audio bytes per realtime ASR frame; the SDK recommends 15,360 bytes per live diarization feed

The frame sizes are tested cadences, not throughput or latency guarantees. The SDK constructs headers; inspect them only for a direct wire implementation.

Language and model symptoms

SymptomLikely causeEvidence or checkFix
Arabic-English speech is recognized as one languageLanguage and model do not describe code-switchingLog the exact enum values, not only their labelsUse Language.ArEn with BatchTranscriptionModel.BayanArEn or FastTranscriptionModel.BayanArEn
Fast transcription is empty with an 8 kHz telephony modelA batch-only model was forced into the fast pipelineNidaArTelephony is absent from FastTranscriptionModel in 0.18.0Use BatchTranscriptionModel.NidaArTelephony with batch; do not pass its wire string to fast
An unexpected language behaves as ArabicAn unrecognized string reached the protocol converterLog the exact value passed to the SDK; unknown strings map to protocol ID 0 in 0.18.0Pass Language.Ar, Language.En, or Language.ArEn instead of a free-form label
Realtime configuration includes a batch or fast ASR modelRealtime was treated like a file pipelineType-check the call; RealtimeClient.startStream() / start_stream() selects a language, not an ASR modelRemove the model option and pass the correct Language value
Direct HTTP TTS fails when no model is specifiedOnly the direct route leaves model selection to the deployment, whose configured default can differ or be unavailable. The SDK always sends nebula when you omit model, so this cannot occur through TTSClientRecord the structured error event and the request payload without text if it is sensitiveSend the explicit model key nebula on the direct route instead of relying on deployment configuration

Use the unversioned BayanArEn alias for the released code-switching default. Choose BayanArEnV1 or BayanArEnV2 only when you intentionally require that specific model. See Models and languages.

Missing final signals and deadline symptoms

SymptomLikely causeEvidence or checkFix
A batch job never returns from the helperIt remains non-terminal, becomes cleared, or exceeds the helper deadlineLog every status: queued, processing, done, failed, or clearedUse a finite poll deadline; stop on done, failed, or cleared; call getResult() / get_result() directly when immediate cleared handling is required
Fast upload acknowledgement arrives but the request never completesaudio_file_upload_success was mistaken for transcription completionMatch its id, then check for transcription_result.is_final === trueWait only under an app deadline; Python defaults timeout_seconds to 60, while JavaScript 0.18.0 has no fast-request timeout option
RealtimeStream.close() / close() returns without protocol is_finalIts bounded protocol-final wait expiredTrack wire-terminal is_final; the default close wait is one secondis_speech_final is only an utterance boundary and does not satisfy the SDK helper. Preserve confirmed text and mark the result incomplete.
Diarization close() returns a timeline without a final updateIts five-second close wait expiredTrack the last update's isFinal / is_final; the returned timeline is the best-known snapshotMark it incomplete unless finality was observed; retain the reconciled snapshot and disconnect
TTS times out between chunks or never emits its final chunkThe per-chunk inactivity wait expired, or byte 16 bit 0 never arrivedRecord the time of each tts_audio frame and its is_last valueBound per-chunk inactivity and the whole synthesis separately; JavaScript defaults each chunk wait to 30 seconds, while Python has no default

An SDK timeout and an application deadline are different. The SDK timeout may bound a poll, close wait, or next chunk. Your application deadline must bound the entire operation, including connection, work, finality, and retries. A timeout never proves that an upload failed or a stream finalized.

Subtitle and diarization result symptoms

SymptomLikely causeEvidence or checkFix
Live captions repeat provisional textEvery transcription_result was appendedLog id, arrival order, seq, is_final, and is_speech_finalKeep one replaceable provisional line per id; commit only final or speech-final event words
RealtimeSubtitles keeps only one of several final eventsThe helper deduplicates by id:seq, but the current wire contract does not guarantee distinct seq valuesCompare final-event count and seq values with RealtimeSubtitles.wordsCollect final-event words in observed arrival order and render them with Subtitles after termination
Speaker turns repeat, disappear, or jumpRaw final_segments and active_segments were concatenatedCompare consecutive raw arrays with update.segmentsAccumulate unseen finalized segments, replace the active tail, sort by start time, or consume the SDK's reconciled update.segments
Batch words have no speaker even though diarization existsWord and diarization timelines are separate in the response shapeInspect final_word_segments / word offsets and diarization_segmentsReconcile by temporal overlap and define an app rule for gaps or ambiguous overlap; do not invent a speaker silently

RealtimeSubtitles deliberately ignores provisional responses and deduplicates final responses by id and seq; that exact behavior is why it can collapse distinct final events under the current wire contract. Live diarization's active_segments remain revisable until they move into finalized state.

TTS voice and playback symptoms

SymptomLikely causeEvidence or checkFix
listVoices() / list_voices() returns []One or more physical variants needed by the configured profiles are unavailableRecord the array length and any structured error; do not index element 0Handle the empty state and do not guess a voice_id; retry only under a bounded policy
Voice discovery waits forever in Python or times out in JavaScriptTimeout defaults differJavaScript defaults to five seconds; Python uses no defaultPass listVoices({ timeoutSeconds: 5 }) or list_voices(timeout_seconds=5) explicitly
Synthesized bytes do not play in a media playerSocket.IO TTS returns raw PCM, not a WAV fileConfirm the response reached is_last; inspect the byte countTreat bytes as PCM16 LE, 24 kHz, mono and add a correct WAV header with the tested TTS-to-WAV recipe
JavaScript WAV output is truncated or contains unrelated bytesA Uint8Array view was converted without its offset and lengthCompare byteLength with the resulting Buffer.lengthConstruct the Buffer with the view's byteOffset and byteLength
Code matching on a previously-seen label string stops finding a voiceThe catalog exposes profile labels such as mul_<name> rather than physical variant labelsInspect the returned profile metadataMatch and persist the profile id, never label; physical variant IDs are rejected
A multilingual profile selects the unexpected physical variantArabic routing requires an Arabic-script letter in textCheck the text for an Arabic-script letterAny Arabic-script letter selects Arabic; otherwise English is selected

For direct Socket.IO parsing, each tts_audio payload starts with a 16-byte UUID and one header byte. Append only bytes 17..end; byte 16 bit 0 is the final signal.

Rate-limit and retry symptoms

SymptomLikely causeEvidence or checkFix
Batch raises BatchTranscribeRateLimitError or returns 429Audio capacity is temporarily unavailable for the requestInspect retryAfter / retry_after, capacity, retryable, and code when presentHonor a supplied delay, add exponential backoff with jitter, and cap attempts and total elapsed time
maxRetries / max_retries appears to do nothingIt is deprecated and ignored in 0.18.0The SDK emits a deprecation warning for a nonzero valueImplement the bounded retry policy in application code
A connection fails after an upload was sentThe outcome is ambiguousRecord whether a job ID or upload acknowledgement was receivedDo not blindly upload again; the API publishes no idempotency-key contract, so apply an app duplication policy or escalate with the evidence
A Socket.IO error says retryable: trueThe server classified the event as retryable, not guaranteed to succeedCapture id, code, message, retryable, and timestamp from onError / on_errorUse that field as one input to the same bounded policy; do not loop indefinitely

capacity is an operational response field, not a published account quota or availability guarantee. A Batch status read is repeatable only when save_result=true preserved terminal output before retrieval; the default read can clear it. An upload whose outcome is unknown remains unsafe to replay. See Errors and rate limits.

Cleanup and leaked-connection symptoms

SymptomLikely causeEvidence or checkFix
The process stays alive after work completesA Socket.IO client or Python HTTP session remains openLog client creation, final signal, and cleanup once per operation; Python may report an unclosed sessionPut cleanup in finally; call FastTranscriptionClient.close(), TTSClient.close(), RealtimeClient.disconnect(), or RealtimeDiarizationClient.disconnect() as appropriate
Connections increase after errors or timeoutsA new client is created before the failed one is closedCompare connection and disconnection callback countsReuse one healthy client where appropriate and close the failed client before retrying
A stream ends without cleanupThe result loop exited before stream.close()Record whether the final input and close path ranClose the stream in finally, then disconnect the client if the failure occurred outside normal stream cleanup
Python batch warns about an unclosed aiohttp sessionBatchTranscribeClient.close() / close_sync() was skippedReproduce one request and observe process shutdownUse the async or sync context manager, or call the matching close method in finally

JavaScript BatchTranscribeClient.close() is a compatibility no-op in 0.18.0; its requests use fetch. The other JavaScript clients own Socket.IO connections and require their documented cleanup paths.

Escalate with reproducible evidence

Retry one known input only when the outcome is unambiguous and the policy allows it. If the problem remains, send your HUMAIN contact a minimal reproduction and this sanitized record:

sdk: "@humain-voice/sdk@0.18.0 | humain-voice==0.18.0"
operation: "batch | fast | realtime | diarization | tts | voice-list"
api_url_host: "host only"
api_path: "Socket.IO path or not-applicable"
started_at_utc: "ISO-8601 timestamp"
request_or_job_id: "UUID if available"
input: "codec, sample_rate, channels, duration, byte_count"
observed: "http_status, event, final_signal"
error: "code, message, retryable, timestamp"
retries: "count and delays"
cleanup: "final frame, stream close, client disconnect"

Attach the smallest code sample that reproduces the issue and state the expected final signal. Do not send the API key, a full Socket.IO URL containing its query string, or sensitive audio/text without authorization. For an ambiguous upload, include its UTC window, safe input checksum, and any job or request ID instead of submitting it again.

These docs do not publish an outage-status URL, retention duration, quota, availability target, or support response-time guarantee. Escalate credential and access-scope failures to the key issuer; escalate repeatable protocol or finality failures with the evidence above.

On this page