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
- Top up the actor's credit balance or grant credits via the metering/billing system.
- Switch to a cheaper model or lower quality tier (e.g. gemini-2.5-flash-image, 1K instead of 4K).
- Retry after confirming the balance reflects recent payments — the estimate may have been based on a stale balance.
- 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
- Check the actor's credit balance via meteringService before calling generate for expensive models.
- Default to cheaper models/tiers when balance is unknown or low.
- Surface top-up flows proactively when balance is near zero.
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.