How Generation Credits Are Released When APIMart Submission Fails
When an APIMart image generation request fails, the system immediately restores the reserved credits by invoking the grant_user_credits Supabase RPC through the adjustCredits helper function, ensuring users are never charged for unsuccessful submissions.
The freestylefly/awesome-gpt-image-2 repository implements a transactional credit system that reserves generation credits before submitting tasks to APIMart. Understanding how generation credits are released if APIMart submission fails is essential for maintaining accurate billing and user trust.
The Credit Reservation Pattern
Before any network request is sent to APIMart, the system proactively reserves the required credits from the user’s balance. This reservation occurs in the main generation handler located in api/generate-image.js. The code calls adjustCredits with a negative delta to deduct the estimated cost upfront.
This approach prevents race conditions where a user might spend credits on multiple concurrent requests without sufficient balance. The reservation is temporary; the credits are only permanently consumed once APIMart confirms the task creation.
Handling Submission Failures in the API Layer
The actual submission to APIMart is wrapped in a try-catch block within api/generate-image.js. The handler imports the apimartSubmit function from api/_lib/apimart.js, which encapsulates the HTTP logic for communicating with APIMart’s API.
If the apimartSubmit call throws an error—whether due to network timeouts, invalid authentication, or APIMart service errors—the catch block intercepts the failure. At this point, the system immediately triggers the credit restoration process before returning the error response to the client. This ensures that failed submissions never result in permanent credit deductions.
Restoring Credits via Supabase RPC
The credit restoration mechanism relies on the adjustCredits utility defined in api/_lib/supabase.js. This helper function interfaces with the database by calling the grant_user_credits Supabase RPC (Remote Procedure Call).
When handling a failure, the code invokes adjustCredits(userId, +creditsNeeded), passing a positive value to refund the previously deducted amount. The grant_user_credits function atomically increments the user’s credit balance, ensuring consistency even if multiple restoration attempts occur simultaneously.
Persisting State on Successful Submissions
If the APIMart submission succeeds, the system transitions from a reserved state to a committed state. The handler records the task details in the apimart_generation_tasks table, defined in supabase/migrations/20260828090000_apimart_generation_tasks.sql. This table stores the apimart_task_id alongside the user ID and the credit cost.
Only after successfully inserting this record are the credits considered permanently spent. If the database insertion fails after APIMart confirms the task, the system relies on the RPC’s atomicity to handle cleanup, though the primary failure mode addressed is the APIMart submission itself.
Code Implementation
The following excerpts illustrate the transactional credit flow in api/generate-image.js and the restoration helper in api/_lib/supabase.js.
// ----- api/generate-image.js (excerpt) -----
import { apimartSubmit } from './_lib/apimart';
import { adjustCredits } from './_lib/supabase';
async function generateImage(req, res) {
const { userId, prompt, creditsNeeded } = req.body;
// 1️⃣ Reserve credits
await adjustCredits(userId, -creditsNeeded);
try {
// 2️⃣ Submit to APIMart
const task = await apimartSubmit({ prompt });
// 3️⃣ Record task ID on success
await recordApimartTask(userId, task.id, creditsNeeded);
res.json({ taskId: task.id });
} catch (err) {
// 4️⃣ On failure – restore credits
await adjustCredits(userId, +creditsNeeded);
console.warn('APIMart generation submission failed', err);
res.status(500).json({ error: 'Generation submission failed' });
}
}
// ----- api/_lib/supabase.js (excerpt) -----
export async function adjustCredits(userId, delta) {
// Uses the Supabase RPC `grant_user_credits`
const { error } = await supabase
.rpc('grant_user_credits', { uid: userId, amount: delta });
if (error) throw error;
}
Key Files Reference
The credit restoration logic spans several critical files in the repository:
api/generate-image.js– Orchestrates the generation request, handles credit reservation, APIMart submission, and restoration on failure.api/_lib/apimart.js– Wraps the APIMart HTTP API; throws exceptions on non-successful responses to trigger the catch block.api/_lib/supabase.js– Provides theadjustCreditsabstraction that calls thegrant_user_creditsRPC for atomic credit adjustments.supabase/migrations/20260828090000_apimart_generation_tasks.sql– Defines the schema for storing successful APIMart task associations and credit costs.
Summary
- Credits are reserved upfront before any external API call to prevent overspending.
- APIMart submission failures trigger immediate restoration via the catch block in
api/generate-image.js. - The
grant_user_creditsRPC handles atomic credit restoration through theadjustCreditshelper inapi/_lib/supabase.js. - Successful submissions persist task metadata in the
apimart_generation_taskstable, finalizing the credit transaction. - Users are never charged for failed submissions because the restoration logic executes before the error response is returned.
Frequently Asked Questions
What happens to my credits if the APIMart API returns a 500 error?
The system catches the HTTP error in api/generate-image.js and immediately invokes adjustCredits with a positive value to restore the deducted amount via the grant_user_credits RPC. You will see the credits returned to your balance within the same request lifecycle.
Is the credit restoration process atomic with the failure detection?
Yes. While the application-level try-catch block in api/generate-image.js detects the failure, the actual credit update is performed by the grant_user_credits Supabase RPC, which executes atomically on the database side to prevent partial state updates or race conditions.
Where is the generation task recorded after a successful APIMart submission?
Upon successful submission, the task ID and associated metadata are inserted into the apimart_generation_tasks table, as defined in the migration file supabase/migrations/20260828090000_apimart_generation_tasks.sql. This record serves as the permanent receipt for the credit deduction.
Can the credit restoration fail independently of the APIMart submission?
If the adjustCredits call fails—for example, due to a database connectivity issue—the error will propagate up and be logged, but the user will not be charged because the initial reservation was the only state change. However, the system would return a 500 error indicating the restoration attempt failed, requiring administrative review to ensure consistency.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →