HeyPuter/puter · error · HttpError
insufficient_funds
insufficient_funds
Error message
Insufficient credits
What it means
Pre-flight credit gate in the xAI STT provider. Before calling xAI it estimates cost as UCENTS_PER_SECOND (2778 microcents/s) times estimated duration — for uploaded files estimated as max(1, ceil(byteLength/16000)) seconds, for URLs a flat 60s — and rejects with HTTP 402 (legacyCode insufficient_funds) when metering.hasEnoughCredits(actor, estimatedCost) returns false. Actual usage is metered from the response duration afterwards.
Source
Thrown at src/backend/drivers/ai-speech2txt/providers/xai/XAISpeechToTextProvider.ts:161
);
fileBuffer = loaded.buffer;
filename = loaded.filename || 'audio.mp3';
mimeType = loaded.mimeType || 'audio/mpeg';
}
// Pre-flight credit check. For URLs we can't know the duration
// upfront, so use a conservative 60-second estimate; actual usage
// is metered from the API response duration afterwards.
const estimatedSeconds = fileBuffer
? Math.max(1, Math.ceil(fileBuffer.byteLength / 16000))
: 60;
const estimatedCost = UCENTS_PER_SECOND * estimatedSeconds;
const allowed = await this.deps.metering.hasEnoughCredits(
actor,
estimatedCost,
);
if (!allowed)
throw new HttpError(402, 'Insufficient credits', {
legacyCode: 'insufficient_funds',
});
// Build multipart form data
const formData = new FormData();
if (args.language) formData.append('language', args.language);
if (args.format !== undefined)
formData.append('format', String(args.format));
if (args.diarize) formData.append('diarize', 'true');
if (args.multichannel) formData.append('multichannel', 'true');
if (args.channels) formData.append('channels', String(args.channels));
if (args.audio_format)
formData.append('audio_format', args.audio_format);
if (args.sample_rate)
formData.append('sample_rate', String(args.sample_rate));
if (isUrl) {View on GitHub (pinned to 908ec23eda)
Solutions
- Top up the user/workspace credit balance and retry.
- Trim or compress the audio so the byte-based estimate fits the remaining balance.
- For URL inputs, confirm at least 60s-equivalent credits are available since the estimate is fixed regardless of actual duration.
- Use test_mode: true to validate the pipeline without consuming credits.
Defensive patterns
Strategy: try-catch
Validate before calling
// Estimate the xAI cost before calling, using the same heuristic the server uses. const UCENTS_PER_SECOND = 2778; // for an uploaded file: const estimatedSeconds = Math.max(1, Math.ceil(fileBytes / 16000)); // for a URL the server assumes 60s: // const estimatedSeconds = 60; const estimatedCostUcents = UCENTS_PER_SECOND * estimatedSeconds; // Compare against the user's balance via your billing/balance endpoint before calling.
Try / catch
try {
const t = await driver.transcribe({ provider: 'xai', file });
} catch (e) {
if (e?.status === 402 || e?.fields?.legacyCode === 'insufficient_funds') {
// prompt the user to top up; optionally retry after balance refresh
} else throw e;
} Prevention
- Surface the user's remaining credits in the UI so they know before submitting.
- Warn before uploading very large audio (the estimate scales with byte length).
- For URL inputs, remember the server reserves 60s of credit regardless of true duration.
When it happens
Trigger: An authenticated actor with zero/negative balance calls xAI transcription on either an uploaded audio file or an HTTP URL, and the estimated cost exceeds their remaining credits. Large files push the estimate higher (byteLength/16000).
Common situations: New/free users with no balance; a workspace that burned through its grant; uploading a very long recording where the byte-based estimate alone exceeds the balance; URL inputs always assume 60s so even short clips need 60s worth of credit.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/16cefe267944886e.
Report an issue: GitHub.