{"record":{"id":"c74072e6a0062f52","repo":"decolua/9router","slug":"openai-tts-failed-res-status","errorCode":null,"errorMessage":"OpenAI TTS failed: ${res.status}","messagePattern":"OpenAI TTS failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"open-sse/handlers/ttsProviders/openai.js","lineNumber":28,"sourceCode":"\n    let ttsModel = DEFAULT_TTS_MODEL;\n    let voice = \"alloy\";\n    if (model && model.includes(\"/\")) {\n      const parts = model.split(\"/\");\n      if (parts.length === 2) [ttsModel, voice] = parts;\n    } else if (model) {\n      voice = model;\n    }\n\n    const baseUrl = (credentials.baseUrl || \"https://api.openai.com\").replace(/\\/+$/, \"\");\n    const res = await fetch(`${baseUrl}/v1/audio/speech`, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${credentials.apiKey}` },\n      body: JSON.stringify({ model: ttsModel, voice, input: text }),\n    });\n    if (!res.ok) {\n      const err = await res.json().catch(() => ({}));\n      throw new Error(err?.error?.message || `OpenAI TTS failed: ${res.status}`);\n    }\n    const buf = await res.arrayBuffer();\n    return { base64: Buffer.from(buf).toString(\"base64\"), format: \"mp3\" };\n  },\n};\n","sourceCodeStart":10,"sourceCodeEnd":34,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/ttsProviders/openai.js#L10-L34","documentation":"Thrown when the OpenAI /v1/audio/speech endpoint returns a non-2xx HTTP status. The code first tries to surface OpenAI's structured error message (err.error.message) and falls back to this template containing the HTTP status code. It wraps any upstream rejection: invalid key, bad model, billing, rate limits.","triggerScenarios":"POST to OpenAI audio/speech fails: 401 invalid API key, 404 wrong model name (e.g. tts-1 typo), 429 rate limit, 402/429 billing/quota exceeded, or a proxy returning an error page without JSON parseable error.message.","commonSituations":"Expired or revoked OpenAI keys, insufficient credits, requesting a model the account lacks access to, or corporate proxies/VPNs returning non-JSON error bodies so the fallback status-code message appears.","solutions":["Check the HTTP status in the message: 401 -> fix API key, 429 -> rate limit/billing, 404 -> model name","Verify the OpenAI account has billing credit and the key is active","Confirm the TTS model name (default from PROVIDER_MEDIA openai.ttsConfig.defaultModel) is valid, e.g. gpt-4o-mini-tts or tts-1","Check network/proxy interference if status is 5xx or 403","Retry with backoff on 429/5xx"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if (!credentials?.apiKey) throw new Error(\"OpenAI TTS: missing API key\");\nif (!/^(gpt-4o-mini-tts|tts-1|tts-1-hd)$/.test(ttsModel)) throw new Error(`Unknown OpenAI TTS model: ${ttsModel}`);","typeGuard":"function isOpenAiHttpError(e) {\n  return e instanceof Error && /^OpenAI TTS failed: \\d{3}$/.test(e.message);\n}","tryCatchPattern":"try {\n  return await openaiTts.synthesize(text, model, creds);\n} catch (e) {\n  const m = e.message.match(/OpenAI TTS failed: (\\d{3})/);\n  if (m && (m[1] === \"429\" || m[1].startsWith(\"5\"))) return retryWithBackoff(() => openaiTts.synthesize(text, model, creds));\n  throw e;\n}","preventionTips":["Handle 429 with exponential backoff and jitter","Keep OpenAI billing credits topped up and alert on low balance","Pin valid TTS model names in config and validate against PROVIDER_MEDIA","Surface err.error.message (when present) to users instead of the status-only fallback"],"tags":["tts","openai","http-error","upstream"],"backgroundTag":"upstream-http-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}