{"record":{"id":"0ef3d01836b24ae6","repo":"HeyPuter/puter","slug":"insufficient-funds-0ef3d0","errorCode":"insufficient_funds","errorMessage":"Insufficient credits for image generation","messagePattern":"Insufficient credits for image generation","errorType":"http","errorClass":"HttpError","httpStatus":402,"severity":"warning","filePath":"src/backend/drivers/ai-image/providers/xai/XAIImageProvider.ts","lineNumber":110,"sourceCode":"        const resolution = this.#normalizeResolution(quality);\n        const aspectRatio = this.#aspectRatio(ratio);\n\n        const actor = Context.get('actor');\n        const userIdentifier =\n            actor?.user.id + actor?.app?.uid ? `:${actor?.app?.uid}` : '';\n\n        const outputPriceInCents = selectedModel.costs[`output:${resolution}`];\n        const mediaInputPriceInCents = selectedModel.costs.media_input ?? 0;\n        const estimatedCostInCents =\n            outputPriceInCents +\n            (hasInputImages ? mediaInputPriceInCents * inputImageCount : 0);\n        const usageAllowed = await this.#meteringService.hasEnoughCredits(\n            actor,\n            estimatedCostInCents * 1_000_000,\n        );\n\n        if (!usageAllowed) {\n            throw new HttpError(\n                402,\n                'Insufficient credits for image generation',\n                { legacyCode: 'insufficient_funds' },\n            );\n        }\n\n        const response = hasInputImages\n            ? await this.#edit(\n                  selectedModel.id,\n                  prompt,\n                  input_images!,\n                  input_image_mime_type,\n                  resolution,\n                  aspectRatio,\n              )\n            : ((await this.#client.images.generate({\n                  model: selectedModel.id,\n                  prompt,","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/drivers/ai-image/providers/xai/XAIImageProvider.ts#L92-L128","documentation":"The xAI image provider estimates the cost of a generation (output resolution price plus per-image media input price for edits) and checks the user's credit balance before making the paid upstream call. If the estimated cost exceeds the user's remaining credits, generation is blocked. The cost scales with resolution tier (1k vs 2k) and the number of input images for edit operations.","triggerScenarios":"The user's credit balance is below the estimated cost, which is computed as outputPriceInCents (from costs['output:1k'] or costs['output:2k']) plus mediaInputPriceInCents * inputImageCount when editing with input images.","commonSituations":"A user with low credits attempting a 2k resolution generation (more expensive than 1k); editing multiple input images (each adds media_input cost); a free-tier user who has exhausted their allowance.","solutions":["Top up the user's credit balance.","Use a lower resolution tier (quality: '1k' instead of '2k') to reduce cost.","Reduce the number of input images for edit operations.","Use test_mode: true for development to bypass the credit check."],"exampleFix":"// before — requesting 2k with multiple input images\nconst url = await provider.generate({\n  prompt: 'edit this',\n  quality: '2k',\n  input_images: ['img1.jpg', 'img2.jpg', 'img3.jpg'],\n});\n\n// after — use 1k to reduce cost, or catch the error\ntry {\n  const url = await provider.generate({ prompt: 'edit this', quality: '1k', input_images: ['img1.jpg'] });\n} catch (e) {\n  if (e.code === 'insufficient_funds') {\n    // prompt user to add credits or downgrade resolution\n  }\n}","handlingStrategy":"try-catch","validationCode":"// Pre-check balance if metering is accessible\nconst estimatedCost = computeXaiCost(model, quality, inputImages?.length);\nconst balance = await meteringService.getUserBalance(actor);\nif (balance < estimatedCost) {\n  throw new Error('Insufficient credits for this generation');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const url = await provider.generate(params);\n} catch (e) {\n  if (e.legacyCode === 'insufficient_funds' || e.code === 'insufficient_funds') {\n    // Prompt user to top up, or retry with quality: '1k'\n    params.quality = '1k';\n    const url = await provider.generate(params);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Use quality: '1k' instead of '2k' when credits are low.","Limit the number of input images for edit operations to reduce cost.","Use test_mode: true during development."],"tags":["xai","billing","credits","insufficient-funds","image-generation"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}