HeyPuter/puter · error · HttpError
insufficient_funds
insufficient_funds
Error message
Insufficient credits
What it means
Before calling ElevenLabs, the driver estimates cost from raw byte size (Math.ceil(byteLength/16000) seconds at the 16 kbps speech floor) and asks metering.hasEnoughCredits(actor, estimatedCost). If the user cannot cover the estimate it throws 402 / insufficient_funds to avoid a billable upstream call the user cannot pay for. Post-call, metering.incrementUsage charges the same estimate.
Source
Thrown at src/backend/drivers/ai-speech2speech/VoiceChangerDriver.ts:198
// Metering: estimate duration from file size if we don't parse metadata.
// 16 kbit/s is a safe lower bound for speech audio; pre-check credits
// before we hit the ElevenLabs API. Post-usage we increment by the same
// estimate — duration parsing is deferred to v2.1 if needed.
const estimatedSeconds = Math.max(
1,
Math.ceil(loaded.buffer.byteLength / 16000),
);
const usageKey = `elevenlabs:${modelId}:second`;
const ucentsPerSecond = VOICE_CHANGER_COSTS[usageKey] ?? 0;
const estimatedCost = ucentsPerSecond * estimatedSeconds;
const hasCredits = await this.services.metering.hasEnoughCredits(
actor,
estimatedCost,
);
if (!hasCredits) {
throw new HttpError(402, 'Insufficient credits', {
legacyCode: 'insufficient_funds',
});
}
const formData = new FormData();
const blob = new Blob([loaded.buffer as BlobPart], {
type: loaded.mimeType ?? 'application/octet-stream',
});
formData.append('audio', blob, loaded.filename);
formData.append('model_id', modelId);
const settings = args.voice_settings ?? args.voiceSettings;
if (settings !== undefined && settings !== null) {
formData.append(
'voice_settings',
typeof settings === 'string'
? settings
: JSON.stringify(settings),View on GitHub (pinned to 908ec23eda)
Solutions
- Top up the user's credit balance.
- Trim or compress the audio to lower the byte-based duration estimate (and the real cost).
- Pre-check the balance and gate the UI; show the estimated cost before submit.
Defensive patterns
Strategy: validation
Validate before calling
// mirror the server's byte-based estimate
const secs = Math.max(1, Math.ceil(audioByteLength / 16000));
const UCENTS_PER_SEC = 300000 * 0.9; // eleven_multilingual_sts_v2 / eleven_english_sts_v2
if (userBalanceUcents < secs * UCENTS_PER_SEC) {
throw new Error('insufficient credits — top up');
} Try / catch
try {
await driver.convert(args);
} catch (e) {
if (e?.legacyCode === 'insufficient_funds' || e?.status === 402) {
showTopUpPrompt();
} else throw e;
} Prevention
- Show the estimated cost in the UI before submit.
- Check balance before starting long uploads.
- Trim/compress audio to lower the byte-based estimate.
When it happens
Trigger: A user with zero or near-zero balance calling convert on a multi-second clip; the byte-based estimate exceeds the remaining credits.
Common situations: Free-tier users; large audio files pushing the byte-based duration estimate up; org accounts with depleted budgets.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/9384f4240dfad8a1.
Report an issue: GitHub.