HeyPuter/puter · error · HttpError

insufficient_funds

insufficient_funds

Error message

Insufficient funds

What it means

Credit gate in GeminiTTSProvider.synthesize. It estimates input tokens (~chars/4) and output audio tokens (~150 wpm, 25 tokens/sec), converts to microcents via the model's input/output_audio rates, and rejects with HTTP 402 (legacyCode insufficient_funds) when meteringService.hasEnoughCredits(actor, estimate) is false. Because the estimate is heuristic, actual metered usage may differ.

Source

Thrown at src/backend/drivers/ai-tts/providers/gemini/GeminiTTSProvider.ts:220

        const estimatedInputTokens = Math.max(1, Math.ceil(text.length / 4));
        const wordCount = text.split(/\s+/).length;
        const estimatedDurationSec = Math.max(1, (wordCount / 150) * 60);
        const estimatedOutputTokens = Math.ceil(estimatedDurationSec * 25);

        const estimatedInputCostCents =
            (estimatedInputTokens / 1_000_000) * costs.input;
        const estimatedOutputCostCents =
            (estimatedOutputTokens / 1_000_000) * costs.output_audio;
        const estimatedTotalMicroCents = this.#toMicroCents(
            estimatedInputCostCents + estimatedOutputCostCents,
        );

        const usageAllowed = await this.meteringService.hasEnoughCredits(
            actor,
            estimatedTotalMicroCents,
        );
        if (!usageAllowed) {
            throw new HttpError(402, 'Insufficient funds', {
                legacyCode: 'insufficient_funds',
            });
        }

        // The TTS models require the text to be framed as a transcript
        // to read aloud. Prefixing with "Say:" prevents the model from
        // trying to generate conversational text instead of audio.
        const inputText = instructions
            ? `${instructions}\n\nSay the following text aloud:\n${text}`
            : `Say the following text aloud:\n${text}`;

        // Let Google GenAI `ApiError`s bubble — they carry `.status` and
        // are mapped to `upstream_*` HttpErrors by the driver-boundary
        // translator. Catching here and wrapping as 502 hid the upstream
        // status and caused 4xx validation errors to page.
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const response: any = await this.#client.models.generateContent({
            model,

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Top up the actor/workspace balance.
  2. Shorten the text or split into smaller requests.
  3. Use the Flash model (cheaper) instead of Pro.
  4. Note the estimate is conservative; retry after a small top-up may succeed even for the same text.
Defensive patterns

Strategy: try-catch

Validate before calling

// Rough client-side estimate mirroring the server heuristic.
function estimateGeminiUcents(text, modelCosts) {
  const inputTokens = Math.max(1, Math.ceil(text.length / 4));
  const words = text.split(/\s+/).length;
  const durSec = Math.max(1, (words / 150) * 60);
  const outputTokens = Math.ceil(durSec * 25);
  const centsIn = (inputTokens / 1_000_000) * modelCosts.input;
  const centsOut = (outputTokens / 1_000_000) * modelCosts.output_audio;
  return Math.ceil((centsIn + centsOut) * 1_000_000); // microcents
}
// Compare to balance; note the server estimate is conservative.

Try / catch

try {
  await driver.synthesize({ text, provider: 'gemini', model });
} catch (e) {
  if (e?.status === 402 || e?.fields?.legacyCode === 'insufficient_funds') {
    // prompt top-up, shorten text, or switch Flash->same text after balance refresh
  } else throw e;
}

Prevention

When it happens

Trigger: An actor whose balance is below the estimated combined input+output cost for the text on the chosen Gemini TTS model. Long text drives both input and output estimates up.

Common situations: Long narration on a Pro model exhausting a small balance; free-tier users; the estimate being conservative so a request is rejected even though actual cost would have fit.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/34713b5b634a6c8a. Report an issue: GitHub.