nexu-io/open-design · error · Error

xai tts ${resp.status}: ${truncate(errText, 240)}

Error message

xai tts ${resp.status}: ${truncate(errText, 240)}

What it means

Thrown by renderXAITTS() after POST {baseUrl}/tts returns a non-2xx response. The error embeds the HTTP status code and the first 240 characters of the response body (via truncate), so the upstream xAI error is surfaced rather than swallowed. This is the only signal of provider-side rejection (bad voice id, invalid language, rate limit, auth failure).

Source

Thrown at apps/daemon/src/media/index.ts:2504

  // we want. Future work: surface sample_rate / bit_rate / codec via
  // ctx so the agent can request wav for high-fidelity workflows.
  const body = {
    text,
    voice_id: voiceId,
    language,
  };

  const resp = await fetch(`${baseUrl}/tts`, withMediaRequestInit(ctx, {
    method: 'POST',
    headers: {
      authorization: `Bearer ${credentials.apiKey}`,
      'content-type': 'application/json',
    },
    body: JSON.stringify(body),
  }));
  if (!resp.ok) {
    const errText = await resp.text().catch(() => '');
    throw new Error(`xai tts ${resp.status}: ${truncate(errText, 240)}`);
  }
  const arrayBuffer = await resp.arrayBuffer();
  const bytes = Buffer.from(arrayBuffer);
  if (bytes.length === 0) {
    throw new Error('xai tts response had zero bytes');
  }
  return {
    bytes,
    providerNote: `xai/${ctx.wireModel} · voice=${voiceId} · ${language} · ${bytes.length} bytes`,
    suggestedExt: '.mp3',
  };
}

// ---------------------------------------------------------------------------
// Provider: ElevenLabs — v3 text-to-speech (synchronous).
//
// Docs: https://elevenlabs.io/docs/api-reference/text-to-speech/convert
// The API returns MP3 bytes directly. The catalogue id `elevenlabs-v3`

View on GitHub (pinned to 5be4028344)

Solutions

  1. Read the embedded status and body text: 401/403 means re-authorize via hermes auth add xai-oauth or refresh XAI_API_KEY
  2. For 400, check that ctx.voice (voice_id) and ctx.language are values xAI accepts; fall back to defaults (eve / en) by clearing the overrides
  3. For 429, back off and retry with fewer concurrent TTS requests
  4. For 5xx, retry after a short delay; check the xAI status page
  5. Confirm credentials.baseUrl is either unset (uses https://api.x.ai/v1) or points at a valid xAI-compatible endpoint

Example fix

// before: voice override that xAI rejects
const ctx = { prompt: 'hello', voice: 'nonexistent-voice', wireModel: 'grok-tts' };
// after: omit voice to use the documented default 'eve'
const ctx = { prompt: 'hello', wireModel: 'grok-tts' };
Defensive patterns

Strategy: try-catch

Type guard

function isXaiTtsHttpError(err: unknown): boolean {
  return err instanceof Error && /^xai tts \d{3}:/.test(err.message);
}

Try / catch

try {
  const result = await renderXAITTS(ctx, credentials);
} catch (err) {
  if (err instanceof Error && /^xai tts (\d{3}):/.test(err.message)) {
    const status = Number(RegExp.$1);
    if (status === 401 || status === 403) await refreshXaiCredentials();
    else if (status === 429) await backoffThenRetry();
    else throw err;
  } else throw err;
}

Prevention

When it happens

Trigger: xAI rejects the TTS request: 401/403 for bad or expired token, 400 for an unsupported voice_id or language, 429 for rate limiting, 5xx for provider outage, or a wrong baseUrl pointing at a non-xAI host.

Common situations: OAuth token expired; voice_id does not exist on the user's plan; language code not supported; baseUrl override points at a proxy that returns its own error shape; rate limit hit during batch generation; xAI incident.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/229c999beb939a4c. Report an issue: GitHub.