decolua/9router · error · Error
OpenRouter TTS failed: ${res.status}
Error message
OpenRouter TTS failed: ${res.status} What it means
Thrown when OpenRouter's TTS (chat-completions-with-audio) endpoint returns a non-2xx HTTP status. Like the OpenAI variant it prefers the JSON error.message from the body and falls back to a status-code template. Any upstream rejection (auth, model access, moderation, quota) surfaces here.
Source
Thrown at open-sse/handlers/ttsProviders/openrouter.js:45
const res = await fetch(TTS_CFG.baseUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${credentials.apiKey}`,
...(TTS_CFG.headers || {}),
},
body: JSON.stringify({
model: ttsModel,
modalities: ["text", "audio"],
audio: { voice, format: "wav" },
stream: true,
messages: [{ role: "user", content: text }],
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err?.error?.message || `OpenRouter TTS failed: ${res.status}`);
}
// Parse SSE stream, accumulate base64 audio chunks
const chunks = [];
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (!line.startsWith("data: ") || line === "data: [DONE]") continue;
try {
const json = JSON.parse(line.slice(6));View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the status code: 401 -> fix key, 402 -> add OpenRouter credits, 404/400 -> check model slug supports audio output
- Verify the model ID in the TTS config exists on OpenRouter and your account can use it
- Check openrouter.ai activity page for rejected-request details
- Retry with backoff if 429 or 5xx
- Confirm the request body shape still matches OpenRouter's audio-modality API if versions changed
Defensive patterns
Strategy: retry
Validate before calling
if (!credentials?.apiKey) throw new Error("OpenRouter TTS: missing API key");
if (typeof text !== "string" || text.length === 0) throw new Error("OpenRouter TTS: text required"); Type guard
function isOpenRouterHttpError(e) {
return e instanceof Error && /^OpenRouter TTS failed: \d{3}$/.test(e.message);
} Try / catch
try {
return await openrouterTts.synthesize(text, model, creds);
} catch (e) {
const m = e.message.match(/OpenRouter TTS failed: (\d{3})/);
if (m && ["429", "500", "502", "503", "504"].includes(m[1])) return retryWithBackoff(() => openrouterTts.synthesize(text, model, creds));
throw e;
} Prevention
- Verify the model slug supports audio modality before routing
- Keep OpenRouter credits funded; alert on 402s
- Back off on 429/5xx with retry-after headers when available
- Log full response bodies when JSON parsing fails to catch HTML error pages
When it happens
Trigger: Non-OK response from OpenRouter chat completions with audio modality: 401 bad key, 404 model not found/not permitted, 402 insufficient credits, 429 rate limited, 5xx from the upstream provider OpenRouter proxies to.
Common situations: Free-tier OpenRouter account lacking access to the TTS model, credits exhausted, model slug misspelled (e.g. openai/gpt-4o-mini-tts unavailable), or OpenRouter returning HTML error pages that fail JSON parsing.
Related errors
- Upstream error (${res.status})
- OpenAI 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/61c1113e46bf4a36.
Report an issue: GitHub.