moeru-ai/airi · error · Error
audio models upstream ${res.status}: ${await res.text().catc
Error message
audio models upstream ${res.status}: ${await res.text().catch(() => '')} What it means
Thrown by the official speech provider's `listModels()` when the AIRI server endpoint `${SERVER_URL}/api/v1/audio/models` (proxied to the unspeech gateway) replies with a non-2xx status. The request carries `Authorization: Bearer <token>` from `getAuthToken()`, so auth failures and server-side upstream configuration failures are the two dominant causes. The body is truncated to 256 chars in the message.
Source
Thrown at packages/stage-ui/src/libs/providers/providers/official/index.ts:132
const provider = createOfficialAudioProvider()
const originalSpeech = provider.speech.bind(provider)
provider.speech = (model: string, extraOptions?: Record<string, unknown>) => {
const result = {
...originalSpeech(model),
...extraOptions,
}
result.fetch = withCredentials()
return result
}
return provider
},
validationRequiredWhen: () => false,
extraMethods: {
listModels: async (): Promise<ModelInfo[]> => {
defaultSpeechModelId = null
const res = await globalThis.fetch(`${SERVER_URL}/api/v1/audio/models`, { headers: authHeaders() })
if (!res.ok)
throw new Error(`audio models upstream ${res.status}: ${await res.text().catch(() => '')}`.slice(0, 256))
const data = await res.json() as { models?: { id: string, name: string, description?: string }[], default?: string | null }
if (!Array.isArray(data.models))
throw new Error('audio models upstream returned malformed body')
defaultSpeechModelId = typeof data.default === 'string' && data.default.length > 0 ? data.default : null
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 theView on GitHub (pinned to 677329427f)
Solutions
- Re-authenticate (sign out/in) so `getAuthToken()` returns a fresh JWT, then retry the model listing.
- Confirm the client's `SERVER_URL` points at a running server that serves `/api/v1/audio/models` (curl it with the bearer token).
- Inspect the server logs for the unspeech upstream error behind a 502/503 and fix `UNSPEECH_UPSTREAM` configuration.
- If the server is old, update it to a revision shipping the audio models route.
Example fix
// before
const models = await provider.extraMethods.listModels()
// after — only probe when authenticated, and report the upstream cause
import { getAuthToken } from '../libs/auth'
const token = getAuthToken()
if (!token)
throw new Error('Sign in before listing official speech models')
const models = await provider.extraMethods.listModels() Defensive patterns
Strategy: try-catch
Validate before calling
import { getAuthToken } from '../libs/auth'
const token = getAuthToken()
if (!token)
throw new Error('Authentication required to list official speech models') Try / catch
try {
const models = await provider.extraMethods.listModels()
}
catch (error) {
const message = errorMessageFrom(error)
if (message.includes('401'))
// re-authenticate then retry
else
// treat as server/upstream misconfiguration; surface upstream body (truncated to 256 chars)
} Prevention
- Probe model lists only after sign-in completes.
- Keep client and server revisions in lockstep for `/api/v1/audio/*` contracts.
- Health-check the unspeech gateway config (`UNSPEECH_UPSTREAM`) when deploying.
- Cache the last successful model list to keep the UI usable during outages.
When it happens
Trigger: Calling `listModels()` while signed out or with an expired JWT (401), pointing `SERVER_URL` at a server that lacks the audio route (404), or the server's `UNSPEECH_UPSTREAM` being unreachable/misconfigured so the gateway returns 502/503.
Common situations: Session expired overnight and the speech settings page probes models on mount; local docker-compose backend not running while the client still points at it; server deployed from an older revision without the unspeech routes; auth service (server/apps/auth) token audience mismatch after redeploying only one service.
Related errors
- audio voices upstream ${res.status}: ${await res.text().catc
- streaming models upstream ${res.status}: ${await res.text().
- streaming voices upstream ${res.status}: ${await res.text().
- MiniMax TTS request failed: ${response.status} ${response.st
- audio models upstream returned malformed body
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/b1d721f81497339f.
Report an issue: GitHub.