HeyPuter/puter · error · HttpError
insufficient_funds
insufficient_funds
Error message
Insufficient credits
What it means
Same metering pattern as the voice driver: estimatedSeconds = Math.ceil(byteLength/16000), estimatedCost = SPEECH_TO_TEXT_COSTS['openai:<model>:second'] * seconds. If metering.hasEnoughCredits returns false, the provider throws 402 / insufficient_funds before the OpenAI call. Usage is incremented post-call by the same estimate. Costs are defined per model in costs.ts.
Source
Thrown at src/backend/drivers/ai-speech2txt/providers/openai/OpenAISpeechToTextProvider.ts:223
);
}
// Estimate seconds from raw bytes — 16 kbps is a conservative speech-audio
// lower bound. Full metadata parsing (music-metadata) is deferred — clients
// aren't observably sensitive to billing-time delta vs real duration.
const estimatedSeconds = Math.max(
1,
Math.ceil(loaded.buffer.byteLength / 16000),
);
const usageType = `openai:${selectedModel}:second`;
const ucentsPerSecond = SPEECH_TO_TEXT_COSTS[usageType] ?? 0;
const estimatedCost = ucentsPerSecond * estimatedSeconds;
const allowed = await this.deps.metering.hasEnoughCredits(
actor,
estimatedCost,
);
if (!allowed)
throw new HttpError(402, 'Insufficient credits', {
legacyCode: 'insufficient_funds',
});
const openaiFile = await toFile(
loaded.buffer,
loaded.filename,
loaded.mimeType ? { type: loaded.mimeType } : undefined,
);
const payload: Record<string, unknown> = {
file: openaiFile,
model: selectedModel,
};
if (args.response_format)
payload.response_format = args.response_format;
if (args.language) payload.language = args.language;
if (typeof args.temperature === 'number')
payload.temperature = args.temperature;View on GitHub (pinned to 908ec23eda)
Solutions
- Top up credits.
- Shorten or compress the audio to cut the byte-based duration estimate (and real cost).
- Pre-check balance and show the estimated cost to the user before submit.
Defensive patterns
Strategy: validation
Validate before calling
// mirror costs.ts: microcents per second per model
const PER_SEC_UCENTS = { 'gpt-4o-transcribe':10000,'gpt-4o-mini-transcribe':5000,'gpt-4o-transcribe-diarize':10000,'whisper-1':10000 };
const secs = Math.max(1, Math.ceil(fileBytes / 16000));
if (userBalanceUcents < secs * PER_SEC_UCENTS[model]) {
throw new Error('insufficient credits — top up');
} Try / catch
try {
await driver.transcribe(args);
} catch (e) {
if (e?.legacyCode === 'insufficient_funds' || e?.status === 402) {
showTopUpPrompt();
} else throw e;
} Prevention
- Show the estimated cost from byte size before upload.
- Check balance before starting transcription.
- Trim/compress audio to lower the byte-based estimate.
When it happens
Trigger: A low-balance user transcribing a long clip; a 25 MB max-size clip producing a large byte-based estimate.
Common situations: Free-tier users; verbose_json on a long recording; org budgets exhausted.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/fdc469223ec270ae.
Report an issue: GitHub.