HeyPuter/puter · error · HttpError

insufficient_funds

insufficient_funds

Error message

Insufficient credits for image generation

What it means

Thrown by GeminiImageProvider.generate() (GeminiImageProvider.ts:188) on the generateContent path after #meteringService.hasEnoughCredits() returns false for the pre-flight cost estimate. The estimate sums input token cost (prompt + estimated image-input tokens), output image token cost, and a small output text overhead, converts to microCents, and checks the actor's balance. HTTP 402 with legacyCode insufficient_funds.

Source

Thrown at src/backend/drivers/ai-image/providers/gemini/GeminiImageProvider.ts:188

            selectedModel.costs.output_image,
        );
        const estimatedOutputTextCostInCents = this.#calculateTokenCostInCents(
            50,
            selectedModel.costs.output,
        ); // small text overhead estimate
        const estimatedOutputCostInCents =
            estimatedOutputImageCostInCents + estimatedOutputTextCostInCents;

        const estimatedTotalCostInMicroCents = this.#toMicroCents(
            estimatedInputCostInCents + estimatedOutputCostInCents,
        );
        const usageAllowed = await this.#meteringService.hasEnoughCredits(
            actor,
            estimatedTotalCostInMicroCents,
        );

        if (!usageAllowed) {
            throw new HttpError(
                402,
                'Insufficient credits for image generation',
                { legacyCode: 'insufficient_funds' },
            );
        }

        // --- API call ---
        const contents = this.#buildContents(
            prompt,
            input_images,
            input_image_mime_type,
        );
        const aspectRatio = `${ratio.w}:${ratio.h}`;

        const imageConfig: Record<string, string> = { aspectRatio };
        if (quality && selectedModel.allowedQualityLevels?.includes(quality)) {
            imageConfig.imageSize = quality;
        }

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Top up the actor's credit balance or grant credits via the metering/billing system.
  2. Switch to a cheaper model or lower quality tier (e.g. gemini-2.5-flash-image, 1K instead of 4K).
  3. Retry after confirming the balance reflects recent payments — the estimate may have been based on a stale balance.
  4. Check hasEnoughCredits logic and the actor's current usage to confirm the rejection is correct.
Defensive patterns

Strategy: try-catch

Validate before calling

const estimate = await meteringService.hasEnoughCredits(actor, estimatedCostMicroCents);
if (!estimate) {
  // prompt user to top up before calling generate
}

Try / catch

try {
  await provider.generate(params);
} catch (e) {
  if (e instanceof HttpError && e.status_code === 402) {
    // surface 'insufficient credits' to the user, offer top-up or cheaper model
  }
  throw e;
}

Prevention

When it happens

Trigger: A user with zero or near-zero credit balance calling generate(); a very expensive model (e.g. gemini-3-pro-image-preview at 4K) whose estimate exceeds remaining credits; a fresh account that has not been granted credits; a user who exhausted credits on prior calls.

Common situations: New trial users hitting a high-tier model; cost estimate being conservative (it overestimates to be safe) causing rejection when actual cost would fit; metering service returning a stale balance.

Related errors


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