moeru-ai/airi · error · Error

Speech engine answered ${response.status} ${response.statusT

Error message

Speech engine answered ${response.status} ${response.statusText} for /${engineRequest.endpoint}${suffix}

What it means

request() treats any non-ok HTTP response as fatal and throws with the status, statusText, endpoint, and up to 200 chars of the response body as detail. This is the generic engine-side error surface: the request reached a VOICEVOX-compatible engine but the engine rejected it (or an intermediary rejected it).

Source

Thrown at packages/stage-ui/src/libs/providers/providers/voicevox/engine.ts:207

    throw new Error('The Base URL is not an absolute http:// or https:// address.')
  }

  const doFetch = options?.fetch ?? globalThis.fetch
  const response = await doFetch(url, {
    method: POST_ENDPOINTS.has(engineRequest.endpoint) ? 'POST' : 'GET',
    // No engine in the family redirects. A redirect therefore means the Base URL
    // points at something else, and following it would hide that.
    redirect: 'error',
    signal: options?.signal,
    ...(engineRequest.body === undefined
      ? {}
      : { body: JSON.stringify(engineRequest.body), headers: { 'Content-Type': 'application/json' } }),
  })

  if (!response.ok) {
    const detail = (await response.text()).trim()
    const suffix = detail ? `: ${detail.slice(0, 200)}` : ''
    throw new Error(`Speech engine answered ${response.status} ${response.statusText} for /${engineRequest.endpoint}${suffix}`)
  }

  return response
}

View on GitHub (pinned to 9c213115f8)

Solutions

  1. Read the `: <detail>` suffix in the message — it contains the engine's own error text identifying the root cause.
  2. Re-fetch the speaker list from the engine (/speakers) and re-select the voice so ids match the running engine.
  3. Confirm the Base URL points at the intended engine and version; restart the engine if it returns 500/503.
  4. If using a proxy, ensure it forwards POST bodies with Content-Type application/json untouched.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await synthesize(request)
}
catch (error) {
  const message = errorMessageFrom(error)
  const match = message.match(/answered (\d{3})/)
  if (match?.[1] === '404')
    await refreshSpeakersAndReselectVoice()
  else
    showSpeechError(message)
}

Prevention

When it happens

Trigger: Any endpoint returning 4xx/5xx: /audio_query with an unknown speaker id (400/404/422), engine overloaded (500/503), unsupported endpoint on a compatible engine like CoqPIt/ShareVOX (404), auth/proxy rejection (401/403).

Common situations: Voice id from a different engine version (speaker id no longer exists); engine still initializing; style id out of range; Base URL behind a proxy that strips or blocks POST; wrong engine family exposing different endpoint paths.

Related errors


AI-assisted analysis of moeru-ai/airi@9c213115f8 (2026-09-02). Data as JSON: /api/errors/a191c4e278822400. Report an issue: GitHub.