{"record":{"id":"f95a456bd9934f15","repo":"decolua/9router","slug":"upstream-error-res-status","errorCode":null,"errorMessage":"Upstream error (${res.status})","messagePattern":"Upstream error \\((.+?)\\)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"open-sse/handlers/ttsProviders/_base.js","lineNumber":25,"sourceCode":"export async function responseToBase64(res, defaultFormat = \"mp3\") {\n  const buf = await res.arrayBuffer();\n  if (buf.byteLength < 100) throw new Error(\"Upstream returned empty audio\");\n  const ctype = res.headers.get(\"content-type\") || \"\";\n  let format = defaultFormat;\n  if (ctype.includes(\"wav\")) format = \"wav\";\n  else if (ctype.includes(\"mpeg\") || ctype.includes(\"mp3\")) format = \"mp3\";\n  else if (ctype.includes(\"ogg\")) format = \"ogg\";\n  return { base64: Buffer.from(buf).toString(\"base64\"), format };\n}\n\nexport async function throwUpstreamError(res) {\n  const text = await res.text().catch(() => \"\");\n  let msg = `Upstream error (${res.status})`;\n  try {\n    const parsed = JSON.parse(text);\n    msg = parsed?.error?.message || parsed?.message || parsed?.detail?.message || (typeof parsed?.detail === \"string\" ? parsed.detail : null) || text || msg;\n  } catch { msg = text || msg; }\n  throw new Error(msg);\n}\n\n// Parse `model` string as \"modelId/voiceId\" — match against known model list (longest prefix wins)\nexport function parseModelVoice(model, defaultModel = \"\", defaultVoice = \"\", knownModels = []) {\n  if (!model) return { modelId: defaultModel, voiceId: defaultVoice };\n  const known = knownModels.map((m) => m.id || m).filter(Boolean).sort((a, b) => b.length - a.length);\n  for (const id of known) {\n    if (model === id) return { modelId: id, voiceId: defaultVoice };\n    if (model.startsWith(`${id}/`)) return { modelId: id, voiceId: model.slice(id.length + 1) };\n  }\n  const idx = model.lastIndexOf(\"/\");\n  if (idx > 0) return { modelId: model.slice(0, idx), voiceId: model.slice(idx + 1) };\n  return { modelId: defaultModel || model, voiceId: defaultVoice || model };\n}\n","sourceCodeStart":7,"sourceCodeEnd":40,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/ttsProviders/_base.js#L7-L40","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait synthesize({ provider: \"deepgram\", apiKey: process.env.OLD_KEY });\n// after\nconst apiKey = process.env.DEEPGRAM_API_KEY; // refreshed, valid key\nif (!apiKey) throw new Error(\"DEEPGRAM_API_KEY missing\");\nawait synthesize({ provider: \"deepgram\", apiKey });","handlingStrategy":"try-catch","validationCode":"if (!apiKey) throw new Error(\"Provider API key missing before TTS call\");\nif (!knownModels.some(m => m.id === modelId)) throw new Error(`Unknown model ${modelId} for provider`);","typeGuard":"function isUpstreamHttpError(e) { return e instanceof Error && /^Upstream error \\(\\d+\\)/.test(e.message) || e instanceof Error && /\\(\\d{3}\\)/.test(e.message); }","tryCatchPattern":"try {\n  await providerSynthesize(req);\n} catch (e) {\n  const m = e.message.match(/\\((\\d{3})\\)/);\n  const status = m ? Number(m[1]) : 0;\n  if (status === 429 || status >= 500) return retryWithBackoff(req);\n  if (status === 401 || status === 403) throw new Error(\"Check provider API key: \" + e.message);\n  throw e;\n}","preventionTips":["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"],"tags":["tts","upstream","http-error","api-key"],"backgroundTag":"upstream-http-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}