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

  1. Top up the user/workspace credit balance and retry.
  2. Trim or compress the audio so the byte-based estimate fits the remaining balance.
  3. For URL inputs, confirm at least 60s-equivalent credits are available since the estimate is fixed regardless of actual duration.
  4. 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

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.