moeru-ai/airi · error · Error
audio voices upstream ${res.status}: ${await res.text().catc
Error message
audio voices upstream ${res.status}: ${await res.text().catch(() => '')} What it means
Thrown by the official speech provider's `listVoices()` when `GET ${SERVER_URL}/api/v1/audio/voices?model=<id|auto>` replies non-2xx. The `model` query param is passed through so the server routes to the right TTS adapter (Azure, cosyvoice, …), defaulting to `auto` when model discovery has not run yet. Auth header comes from `getAuthToken()`.
Source
Thrown at packages/stage-ui/src/libs/providers/providers/official/index.ts:157
return data.models.map(m => ({
id: m.id,
name: m.name,
description: m.description,
provider: OFFICIAL_SPEECH_PROVIDER_ID,
}))
},
listVoices: async (_config, _provider, model): Promise<VoiceInfo[]> => {
// Voice catalogs are model-scoped on the server side. Pass the active
// model through so Azure / cosyvoice / future provider voices route to
// the right adapter. If model discovery has not completed yet, keep the
// legacy `auto` request as a startup fallback.
const target = model && model.length > 0 ? model : 'auto'
const url = new URL(`${SERVER_URL}/api/v1/audio/voices`)
url.searchParams.set('model', target)
const res = await globalThis.fetch(url.toString(), { headers: authHeaders() })
if (!res.ok)
throw new Error(`audio voices upstream ${res.status}: ${await res.text().catch(() => '')}`.slice(0, 256))
// Shape aligned with unspeech's types.ListVoicesResponse, plus the
// `recommended` field our server injects from configKV DEFAULT_TTS_VOICES.
// https://github.com/moeru-ai/unspeech/blob/main/pkg/backend/types/voices.go
const data = await res.json() as {
voices?: {
id: string
name: string
description?: string
labels?: Record<string, unknown>
tags?: string[]
languages?: { code: string, title: string }[]
compatible_models?: string[]
preview_audio_url?: string
}[]
recommended?: Record<string, string>
}
View on GitHub (pinned to 677329427f)
Solutions
- Retry after re-authenticating if the status is 401/403.
- Check that the active model id is one returned by `listModels()` for this provider, not a streaming id or stale id.
- Verify with curl `'/api/v1/audio/voices?model=auto'` using a valid bearer token and read the server-side upstream error.
- Ensure `UNSPEECH_UPSTREAM` is fully configured on the server (backend ids, credentials, voice catalogs).
Example fix
// before
const voices = await provider.extraMethods.listVoices(config, provider, model)
// after — fall back to the catalog-wide query when the model-scoped one fails
let voices
try {
voices = await provider.extraMethods.listVoices(config, provider, model)
}
catch {
voices = await provider.extraMethods.listVoices(config, provider, 'auto')
} Defensive patterns
Strategy: try-catch
Validate before calling
const token = getAuthToken()
if (!token)
throw new Error('Authentication required to list official voices')
if (model && !model.includes('/') && model !== 'auto')
// ensure the model id came from listModels() Try / catch
try {
voices = await provider.extraMethods.listVoices(config, provider, model)
}
catch (error) {
voices = await provider.extraMethods.listVoices(config, provider, 'auto')
.catch(() => { throw error })
} Prevention
- Always pass a model id obtained from this provider's `listModels()`.
- Keep `UNSPEECH_UPSTREAM` backend ids and credentials in sync with the voice catalogs.
- Fall back to the `auto` (catalog-wide) query when the model-scoped query fails.
- Re-probe voices after re-authentication, not with the stale token.
When it happens
Trigger: Expired/missing JWT (401); requesting voices for a `model` id the server does not know (400/404); the server's per-backend voice adapter failing (502) because `UNSPEECH_UPSTREAM` names a backend whose credentials are missing; older server without the voices route (404).
Common situations: Voice list loads on the speech settings page right after token expiry; selecting a model id that came from a stale provider cache after the server catalog changed; partial server config where the HTTP TTS upstream is set but its voice catalog fetch fails.
Related errors
- streaming models upstream ${res.status}: ${await res.text().
- streaming voices upstream ${res.status}: ${await res.text().
- audio models upstream ${res.status}: ${await res.text().catc
- audio voices upstream returned malformed body
- MiniMax TTS request failed: ${response.status} ${response.st
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/2186e077f39428ad.
Report an issue: GitHub.