decolua/9router · error
Upstream error (${res.status})
Error message
Upstream error (${res.status}) What it means
throwUpstreamError() normalizes a non-OK upstream HTTP response into a thrown Error. It prefers the provider's structured error message (error.message, message, detail) and falls back to the raw body text, wrapping or replacing the generic 'Upstream error (<status>)' message. Providers like hyperbolic, deepgram, nvidia, huggingface, fishAudio, and inworld call this whenever the TTS endpoint returns a non-2xx status.
Source
Thrown at open-sse/handlers/ttsProviders/_base.js:25
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);
}
// Parse `model` string as "modelId/voiceId" — match against known model list (longest prefix wins)
export function parseModelVoice(model, defaultModel = "", defaultVoice = "", knownModels = []) {
if (!model) return { modelId: defaultModel, voiceId: defaultVoice };
const known = knownModels.map((m) => m.id || m).filter(Boolean).sort((a, b) => b.length - a.length);
for (const id of known) {
if (model === id) return { modelId: id, voiceId: defaultVoice };
if (model.startsWith(`${id}/`)) return { modelId: id, voiceId: model.slice(id.length + 1) };
}
const idx = model.lastIndexOf("/");
if (idx > 0) return { modelId: model.slice(0, idx), voiceId: model.slice(idx + 1) };
return { modelId: defaultModel || model, voiceId: defaultVoice || model };
}
View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the thrown message — it contains the provider's own error detail and status; fix what it names first
- Check the provider API key in the TTS provider config is present, valid, and for the right account
- Verify modelId/voiceId against the provider's current catalog
- If 429/402, wait for rate-limit reset or top up quota; add retry/backoff for 429 and 5xx
- Check the provider status page if the status is 5xx
Example fix
// before
await synthesize({ provider: "deepgram", apiKey: process.env.OLD_KEY });
// after
const apiKey = process.env.DEEPGRAM_API_KEY; // refreshed, valid key
if (!apiKey) throw new Error("DEEPGRAM_API_KEY missing");
await synthesize({ provider: "deepgram", apiKey }); Defensive patterns
Strategy: try-catch
Validate before calling
if (!apiKey) throw new Error("Provider API key missing before TTS call");
if (!knownModels.some(m => m.id === modelId)) throw new Error(`Unknown model ${modelId} for provider`); Type guard
function isUpstreamHttpError(e) { return e instanceof Error && /^Upstream error \(\d+\)/.test(e.message) || e instanceof Error && /\(\d{3}\)/.test(e.message); } Try / catch
try {
await providerSynthesize(req);
} catch (e) {
const m = e.message.match(/\((\d{3})\)/);
const status = m ? Number(m[1]) : 0;
if (status === 429 || status >= 500) return retryWithBackoff(req);
if (status === 401 || status === 403) throw new Error("Check provider API key: " + e.message);
throw e;
} Prevention
- Validate API keys and model/voice ids against the provider catalog before calling
- Implement backoff retry for 429 and 5xx statuses
- Monitor provider status pages and rotate keys before expiry
- Parse the status code out of the message to branch handling
When it happens
Trigger: Any TTS call to those providers where res.ok is false: 401 invalid/expired API key, 402/429 quota or rate limit, 404 unknown model or voice id, 400 malformed request body (bad voice settings, text too long), 5xx provider outage.
Common situations: API key rotated or revoked upstream; free tier exhausted (deepgram/nvidia give 429/402); model id removed by provider; voice not available for the chosen model (fishAudio/inworld); regional outage returning 502/503.
Related errors
- OpenAI TTS failed: ${res.status}
- OpenRouter TTS failed: ${res.status}
- MiMo TTS error (${res.status})
- Upstream returned empty audio
- Edge TTS voices fetch failed: ${res.status}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/f95a456bd9934f15.
Report an issue: GitHub.