{"record":{"id":"eaacbc306196eb89","repo":"moeru-ai/airi","slug":"ttsupstreamresponseerror-rethrown-upstream-respon","errorCode":null,"errorMessage":"TtsUpstreamResponseError (rethrown upstream response body with upstream status/headers)","messagePattern":"TtsUpstreamResponseError \\(rethrown upstream response body with upstream status/headers\\)","errorType":"http","errorClass":"TtsUpstreamResponseError","httpStatus":null,"severity":"error","filePath":"server/apps/api/src/services/adapters/tts/unspeech.ts","lineNumber":76,"sourceCode":"      speed,\n      voice,\n      abortSignal: ctx.abortSignal,\n      extraBody,\n    })\n\n    return {\n      contentType: result.contentType ?? fallbackContentType,\n      body: result.body,\n    }\n  }\n  catch (error) {\n    // Keep abort identity intact so the router can apply `onTimeout`\n    // independently from HTTP 500 fallback policy.\n    if (ctx.abortSignal?.aborted)\n      throw error\n\n    if (error instanceof UnSpeechAPIError) {\n      throw new TtsUpstreamResponseError(new Response(error.responseBody, {\n        status: error.status,\n        headers: error.responseHeaders,\n      }))\n    }\n\n    throw createInternalError(`${providerLabel} tts fetch failed: ${errorMessageFrom(error) ?? 'unknown'}`)\n  }\n}\n\ninterface ListVoicesOptions {\n  ctx: TtsVoiceCatalogContext\n  query: string\n  providerLabel: string\n}\n\n/**\n * Lists unspeech voices and maps SDK failures into AIRI gateway errors.\n *","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/moeru-ai/airi/blob/f679616c34f1cf6d282c8d64264242af944b3fed/server/apps/api/src/services/adapters/tts/unspeech.ts#L58-L94","documentation":"`TtsUpstreamResponseError` is the adapter's normalized wrapper for a non-OK response returned by the upstream TTS provider reached through the unspeech SDK. `sendSpeechViaUnSpeech` catches `UnSpeechAPIError` and rethrows it as this error carrying a reconstructed `Response` with the upstream status, body, and headers, so the router can apply its HTTP 500 fallback policy while preserving the upstream's actual error payload.","triggerScenarios":"The unspeech `generateSpeechResponse` call receives an HTTP error response from the upstream provider: invalid/expired upstream API key, unknown model or voice id for that provider, provider-side quota/rate limit, or malformed `extraBody` rejected by the upstream.","commonSituations":"Encrypted upstream key (`ctx.keyPlaintext`) is wrong or revoked; requested voice does not exist for the configured provider/region; upstream provider outage returning 4xx/5xx; base URL misconfigured so requests hit the wrong upstream endpoint.","solutions":["Inspect the wrapped Response's status and body to read the upstream's own error message, then fix the matching cause (key, voice, model).","Verify the upstream API key stored in LLM router config is valid and not expired.","Check that the requested voice/model exist in the provider catalog (list voices via the adapter's /audio/voices path).","If status is 429 or 5xx, retry after backoff; the router may fall back to the next upstream in the chain.","Confirm `ctx.unspeechBaseURL` points to the correct unspeech-compatible endpoint."],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"// validate upstream config before dispatch\nif (!ctx.keyPlaintext?.length) throw createInternalError('missing upstream TTS API key')\nif (!ctx.unspeechBaseURL) throw createInternalError('missing unspeech base URL')","typeGuard":"function isTtsUpstreamResponseError(e: unknown): e is TtsUpstreamResponseError { return e instanceof TtsUpstreamResponseError }","tryCatchPattern":"try {\n  const result = await sendSpeechViaUnSpeech(options)\n} catch (error) {\n  if (error instanceof TtsUpstreamResponseError) {\n    const status = error.response.status\n    if (status === 429 || status >= 500) return retryWithNextUpstream(error)\n    return new Response(error.response.body, { status, headers: error.response.headers })\n  }\n  throw error\n}","preventionTips":["Validate that the requested voice/model exist in the provider's voice catalog before calling the upstream.","Rotate and verify upstream API keys stored in the encrypted router config.","Implement upstream-chain fallback for 429/5xx statuses at the router level.","Log the upstream response body on failure to speed up provider-side diagnosis."],"tags":["tts","upstream","http","gateway"],"backgroundTag":"upstream-api-error","analyzedSha":"f679616c34f1cf6d282c8d64264242af944b3fed","analyzedAt":"2026-09-08T13:18:15.005Z","contentChangedAt":"2026-09-08T13:18:15.005Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}