decolua/9router · error

Bing TTS failed: ${res.status}${body ? " - " + body : ""}

Error message

Bing TTS failed: ${res.status}${body ? " - " + body : ""}

What it means

The Bing/Edge TTS provider (open-sse/handlers/ttsProviders/edgeTts.js) scrapes a free token from bing.com/translator and POSTs SSML to the undocumented `tfettts` endpoint. After one automatic retry on 429/403 (fresh token), it throws this error if the upstream HTTP status is still not OK, appending any response body it received. It is the library's catch-all for 'Bing refused or could not service this synthesis request'.

Source

Thrown at open-sse/handlers/ttsProviders/edgeTts.js:83

export default {
  noAuth: true,
  async synthesize(text, model) {
    const voiceId = model || "vi-VN-HoaiMyNeural";
    let token = await getToken();
    let res = await ttsRequest(text, voiceId, token);

    // 429/403: invalidate cache and retry once
    if (res.status === 429 || res.status === 403) {
      cache.token = null;
      cache.tokenTime = 0;
      token = await getToken();
      res = await ttsRequest(text, voiceId, token);
    }

    if (!res.ok) {
      const body = await res.text().catch(() => "");
      throw new Error(`Bing TTS failed: ${res.status}${body ? " - " + body : ""}`);
    }
    const buf = await res.arrayBuffer();
    if (buf.byteLength < 1024) throw new Error("Bing TTS returned empty audio");
    return { base64: Buffer.from(buf).toString("base64"), format: "mp3" };
  },
};

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Read the status and body in the error message: 401/403/429 usually mean the scraped token scheme changed or you are rate-limited — retry later or reduce request rate.
  2. Sanitize/escape the input text before passing it to the TTS call (strip or XML-escape characters like <, >, &) so the generated SSML stays well-formed.
  3. Retry the request once after a short delay — the module caches a Bing token for ~5 minutes; a stale token from a previous run can fail until the next refresh cycle.
  4. If 403/429 persists across retries, treat the Bing free endpoint as unavailable and fall back to a paid TTS provider (e.g. ElevenLabs or Gemini) configured in the dashboard.
  5. Check network egress: if you run behind a proxy or from a cloud IP, test https://www.bing.com/translator reachability; consider routing via a residential/different region.

Example fix

// before: raw user text injected into SSML
const ssml = `<speak ...>${text}</speak>`;
// after: XML-escape text before interpolation
const esc = String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const ssml = `<speak ...>${esc}</speak>`;
Defensive patterns

Strategy: retry

Validate before calling

// before calling synthesize
if (!text || !text.trim()) throw new Error("TTS text is empty");
const ssmlUnsafe = /[<>&]/.test(text);
if (ssmlUnsafe) text = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

Try / catch

try {
  const audio = await edgeTts.synthesize(text, voiceId);
} catch (e) {
  if (/Bing TTS failed: (429|403)/.test(e.message)) {
    await sleep(2000);
    return edgeTts.synthesize(text, voiceId); // one retry; fresh token is fetched inside
  }
  if (/Bing TTS failed: 5\d\d/.test(e.message)) return fallbackTts(text);
  throw e;
}

Prevention

When it happens

Trigger: Calling the TTS synthesize path with the edge/bing provider when the POST to https://www.bing.com/tfettts returns any non-OK status after the single token-refresh retry — e.g. 400 from malformed text/SSML, 403/429 that persists even after token invalidation (rate limit or regional block), 5xx from Bing, or an empty-text request producing a server error.

Common situations: Bing changes its undocumented endpoint or abuse-prevention scheme and tokens stop working; heavy automated use trips Bing's rate limiter so even a fresh token gets 429; running from a datacenter IP or region Bing blocks; text containing characters that break the hand-built SSML (e.g. unescaped `<>` or quotes) yielding 400.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/c3ed63997eacea07. Report an issue: GitHub.