HeyPuter/puter · error · HttpError
insufficient_funds
insufficient_funds
Error message
Insufficient funds
What it means
Credit gate in ElevenLabsTTSProvider.synthesize. totalCost = ELEVENLABS_TTS_COSTS[modelId] * text.length; if meteringService.hasEnoughCredits(actor, totalCost) is false, it throws HTTP 402 (legacyCode insufficient_funds). Billing is per character, and different models carry different per-character rates.
Source
Thrown at src/backend/drivers/ai-tts/providers/elevenlabs/ElevenLabsTTSProvider.ts:248
fields: { key: 'model', expected, got: modelId },
},
);
}
const desiredFormat =
output_format || response_format || DEFAULT_OUTPUT_FORMAT;
const actor = Context.get('actor')!;
const usageKey = `elevenlabs:${modelId}:character`;
const ucentsPerChar = ELEVENLABS_TTS_COSTS[modelId];
const totalCost = ucentsPerChar * text.length;
const usageAllowed = await this.meteringService.hasEnoughCredits(
actor,
totalCost,
);
if (!usageAllowed) {
throw new HttpError(402, 'Insufficient funds', {
legacyCode: 'insufficient_funds',
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const payload: any = {
text,
model_id: modelId,
output_format: desiredFormat,
};
const finalVoiceSettings = voice_settings ?? voiceSettings;
if (finalVoiceSettings) {
payload.voice_settings = finalVoiceSettings;
}
const response = await this.request(`/v1/text-to-speech/${voiceId}`, {
method: 'POST',View on GitHub (pinned to 908ec23eda)
Solutions
- Top up the actor/workspace balance.
- Shorten the text or batch it.
- Switch to a cheaper model (e.g. eleven_flash_v2_5).
- Pre-estimate cost as chars * ELEVENLABS_TTS_COSTS[model] before submitting.
Defensive patterns
Strategy: try-catch
Validate before calling
// Estimate ElevenLabs cost (per-character) before calling.
const ELEVENLABS_TTS_COSTS = {
'eleven_multilingual_v2': 190,
'eleven_flash_v2_5': 50,
'eleven_turbo_v2_5': 100,
'eleven_v3': 300,
};
function estimateUcents(text, model) {
return ELEVENLABS_TTS_COSTS[model] * text.length;
}
// Compare to the user's balance; switch to a cheaper model if it won't fit. Try / catch
try {
await driver.synthesize({ text, provider: 'elevenlabs', model });
} catch (e) {
if (e?.status === 402 || e?.fields?.legacyCode === 'insufficient_funds') {
// prompt top-up, or retry on eleven_flash_v2_5 with the same text
} else throw e;
} Prevention
- Prefer eleven_flash_v2_5 for long/draft text; reserve pricier models for final output.
- Show estimated cost (chars * rate) before submission.
- Keep the balance visible so the 402 isn't unexpected.
When it happens
Trigger: An actor whose balance is below the per-character cost of text on the chosen model. Premium models (e.g. eleven_v3) cost more per character, so the same text can pass on eleven_flash_v2_5 but fail on a pricier model.
Common situations: Long-form narration on a premium model exhausting a small balance; free-tier users hitting limits; switching to a costlier model without budget awareness.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/7ea44fce826e41e2.
Report an issue: GitHub.