HeyPuter/puter · error · HttpError

insufficient_funds

insufficient_funds

Error message

Insufficient funds

What it means

Credit gate in AWSPollyTTSProvider.synthesize. After resolving the engine it computes totalCost = ucentsPerChar (from AWS_POLLY_COSTS[engine]) times text.length and calls meteringService.hasEnoughCredits(actor, totalCost). If false, it throws HTTP 402 (legacyCode insufficient_funds). Usage is metered per character after synthesis.

Source

Thrown at src/backend/drivers/ai-tts/providers/awsPolly/AWSPollyTTSProvider.ts:264

        if (typeof text !== 'string' || text.trim() === '') {
            throw new HttpError(400, 'Missing required field: text', {
                legacyCode: 'field_required',
                fields: { key: 'text' },
            });
        }

        const actor = Context.get('actor')!;
        const usageType = `aws-polly:${engine}:character`;
        const ucentsPerChar = AWS_POLLY_COSTS[engine] ?? 0;
        const totalCost = ucentsPerChar * text.length;

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

        // Resolve voice
        let voice = voiceArg ?? undefined;

        if (!voice && language) {
            voice =
                (await this.getLanguageAppropriateVoice(language, engine)) ??
                undefined;
        }

        if (!voice) {
            voice = await this.getDefaultVoiceForEngine(engine);
        }

        const client = this.getClient();

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Top up the actor/workspace balance.
  2. Shorten the text or split it into smaller chunks.
  3. Use a cheaper engine ('standard') where acceptable.
  4. Pre-check the balance and estimate cost (chars * AWS_POLLY_COSTS[engine]) before submitting.
Defensive patterns

Strategy: try-catch

Validate before calling

// Estimate Polly cost before calling. Mirror AWS_POLLY_COSTS (microcents/char).
const AWS_POLLY_COSTS = { standard: 100, neural: 200, 'long-form': 100, generative: 300 };
function estimateUcents(text, engine) {
  return (AWS_POLLY_COSTS[engine] ?? 0) * text.length;
}
// Compare estimateUcents(text, engine) to the user's balance before submitting.

Try / catch

try {
  await driver.synthesize({ text, provider: 'aws-polly', engine });
} catch (e) {
  if (e?.status === 402 || e?.fields?.legacyCode === 'insufficient_funds') {
    // prompt top-up; consider retrying on 'standard' engine or shorter text
  } else throw e;
}

Prevention

When it happens

Trigger: An actor whose balance is below the per-character cost of the requested text for the chosen engine. Neural/generative/long-form engines cost more per character than standard, so the same text can pass on 'standard' but fail on 'generative'.

Common situations: Long text on a premium engine exhausting a small balance; free-tier users hitting neural voice limits; switching from standard to generative without budget awareness.

Related errors


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