HeyPuter/puter · error · HttpError
ElevenLabs returned ${response.status}
Error message
ElevenLabs returned ${response.status} What it means
After the upstream fetch, if response.ok is false the driver wraps the ElevenLabs status into a tagged legacyCode: upstream_provider_unavailable (ElevenLabs 5xx, exposed as 400), upstream_auth_failed (ElevenLabs 401/403, exposed as 500), upstream_rate_limited (429, exposed as 429), otherwise upstream_bad_request (exposed as the raw status). The message is ElevenLabs' detail field when present, else 'ElevenLabs returned <status>'. provider and upstreamStatus are attached as fields.
Source
Thrown at src/backend/drivers/ai-speech2speech/VoiceChangerDriver.ts:289
// as 400 like the TTS provider does — user can't act on
// them, but it's not our bug either).
const legacyCode =
response.status >= 500
? 'upstream_provider_unavailable'
: response.status === 401 || response.status === 403
? 'upstream_auth_failed'
: response.status === 429
? 'upstream_rate_limited'
: 'upstream_bad_request';
const exposedStatus =
legacyCode === 'upstream_rate_limited'
? 429
: legacyCode === 'upstream_auth_failed'
? 500
: legacyCode === 'upstream_provider_unavailable'
? 400
: response.status;
throw new HttpError(exposedStatus, message, {
legacyCode,
fields: {
provider: 'elevenlabs',
upstreamStatus: response.status,
},
});
}
const arrayBuffer = await response.arrayBuffer();
const stream = Readable.from(Buffer.from(arrayBuffer));
this.services.metering.incrementUsage(
actor,
usageKey,
estimatedSeconds,
ucentsPerSecond * estimatedSeconds,
);
return {View on GitHub (pinned to 908ec23eda)
Solutions
- For 429 (upstream_rate_limited): backoff and retry with jitter; reduce concurrency.
- For 401/403 (upstream_auth_failed): rotate the ElevenLabs API key in config.
- For 5xx (upstream_provider_unavailable): surface a 'provider unavailable' message and check the ElevenLabs status page.
- For 400 (upstream_bad_request): inspect fields.upstreamStatus and the message detail; fix the offending voice/model/params.
Defensive patterns
Strategy: retry
Try / catch
async function convertWithRetry(args, tries = 3) {
for (let i = 0; i < tries; i++) {
try {
return await driver.convert(args);
} catch (e) {
const retriable = e?.legacyCode === 'upstream_rate_limited'
|| e?.legacyCode === 'upstream_provider_unavailable';
if (retriable && i < tries - 1) {
await new Promise(r => setTimeout(r, 2 ** i * 500 + Math.random() * 250));
continue;
}
throw e;
}
}
} Prevention
- Retry only on upstream_rate_limited / upstream_provider_unavailable; never retry upstream_bad_request.
- Exponential backoff with jitter to avoid amplifying rate limits.
- Log fields.upstreamStatus to distinguish upstream causes in monitoring.
When it happens
Trigger: ElevenLabs returns 429 (quota/rate), 401/403 (our key revoked or invalid), 400 (a voice/model it rejects), or 5xx (ElevenLabs outage). The exposed HTTP status depends on the mapped legacyCode, not always the upstream status.
Common situations: Burst traffic hitting ElevenLabs rate limits; an expired or rotated API key; an ElevenLabs incident; a voice id that exists but is disabled on the account.
Related errors
- upstreamMessage ?? `ElevenLabs request failed (status ${resp
- internal_error
- errText || `xAI STT request failed (status ${response.status
- internal_error
- internal_error
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/797dec36c4091cde.
Report an issue: GitHub.