decolua/9router · error
Upstream returned empty audio
Error message
Upstream returned empty audio
What it means
responseToBase64() converts an upstream TTS provider's binary audio Response into base64. It guards against providers returning an HTTP 200 with a tiny/empty body (e.g. an error JSON or blank payload) by requiring at least 100 bytes of audio data; anything shorter throws 'Upstream returned empty audio'. This is the library's sanity check that real audio bytes were received before embedding them in a data URI.
Source
Thrown at open-sse/handlers/ttsProviders/_base.js:9
// Shared TTS helpers
import { Buffer } from "node:buffer";
export const UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
// Convert upstream Response (binary audio) to { base64, format }
export async function responseToBase64(res, defaultFormat = "mp3") {
const buf = await res.arrayBuffer();
if (buf.byteLength < 100) throw new Error("Upstream returned empty audio");
const ctype = res.headers.get("content-type") || "";
let format = defaultFormat;
if (ctype.includes("wav")) format = "wav";
else if (ctype.includes("mpeg") || ctype.includes("mp3")) format = "mp3";
else if (ctype.includes("ogg")) format = "ogg";
return { base64: Buffer.from(buf).toString("base64"), format };
}
export async function throwUpstreamError(res) {
const text = await res.text().catch(() => "");
let msg = `Upstream error (${res.status})`;
try {
const parsed = JSON.parse(text);
msg = parsed?.error?.message || parsed?.message || parsed?.detail?.message || (typeof parsed?.detail === "string" ? parsed.detail : null) || text || msg;
} catch { msg = text || msg; }
throw new Error(msg);
}
View on GitHub (pinned to 90b52e06ff)
Solutions
- Log the raw upstream response body/status in the provider executor to see what the <100-byte payload actually contains
- Verify the voiceId and modelId against the provider's current voice list (voices change/deprecate)
- Confirm the account has quota/credits and the API key is valid — providers often return empty 200s on soft quota exhaustion
- Ensure the input text is non-empty and within provider length limits
- Retry the request; transient upstream issues can produce truncated bodies
Example fix
// before
const { base64 } = await responseToBase64(res);
// after
if (!text || !text.trim()) throw new Error("TTS input text is empty");
try {
const { base64 } = await responseToBase64(res);
} catch (e) {
console.error("TTS upstream body:", await res.clone?.().text?.().catch(() => "<binary>"));
throw e;
} Defensive patterns
Strategy: validation
Validate before calling
if (typeof text !== "string" || !text.trim()) throw new Error("TTS text must be a non-empty string");
if (!voiceId) throw new Error("voiceId is required for TTS synthesis"); Type guard
function isUsableAudio(buf) { return buf instanceof ArrayBuffer && buf.byteLength >= 100; } Try / catch
try {
const { base64, format } = await responseToBase64(res);
} catch (e) {
if (e.message === "Upstream returned empty audio") {
// inspect res.status/body, retry once or surface provider-specific guidance
}
throw e;
} Prevention
- Always pass non-empty, trimmed text and a validated voiceId
- Keep provider API keys and quota in good standing — empty 200s often signal soft quota exhaustion
- Log upstream content-type and status before conversion to catch stub responses early
- Add a retry with a different voice/model on empty-audio failures
When it happens
Trigger: Any TTS request through deepgram, nvidia, huggingface, fishAudio, cartesia, or playht where the upstream responds 2xx but the body is <100 bytes: invalid/unsupported voice id with a silent 200, empty text input, exhausted free-tier quota returning a stub body, or a proxy/CDN stripping the response body.
Common situations: Developer passes a voiceId that doesn't exist on the provider; text is empty or whitespace after trimming; account has no credits so the provider returns an empty success instead of an error status; corporate proxy or bot-protection (Cloudflare) returns a short challenge page under 100 bytes with 200.
Related errors
- Upstream error (${res.status})
- Bing TTS returned empty audio
- ElevenLabs TTS returned empty audio
- Gemini TTS returned no audio (finishReason: ${reason}, voice
- Inworld TTS returned no audio
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/475e934907e9fa1f.
Report an issue: GitHub.