How the Credit Reservation System Works Before API Submission in Awesome-GPT-Image-2

The credit reservation system uses atomic PostgreSQL transactions to lock user profiles and deduct credits (or consume free generations) before submitting requests to the external Apimart API, with automatic rollback via the release_generation_reservation RPC if the downstream request fails.

The freestylefly/awesome-gpt-image-2 repository implements a financial safety layer that guarantees users are only charged for image generations the provider successfully accepts. By isolating the billing checkpoint from the external API call, the system prevents credit loss during network failures, rate limiting, or provider errors.

The Three-Step Reservation Architecture

The credit reservation system operates through a strict sequence implemented in api/generate-image.js. This pattern separates credit management from external service dependencies, ensuring financial consistency even when downstream systems fail.

  • Reserve usage – Atomically deduct credits or consume free quota via Supabase RPC
  • Submit to Apimart – POST the generation request to the external provider
  • Finalize state – Mark as succeeded on confirmation, or release/refund on failure

Step 1: Reserving Credits with Atomic Database Locks

Before any external API call, the handler invokes reserveGeneration(), which calls the Supabase RPC public.reserve_generation_usage (defined in supabase/migrations/202605090001_user_credits.sql). This function executes within a transaction that locks the user's profile row using FOR UPDATE, preventing race conditions during concurrent requests.

The RPC checks the user's profile in public.profiles and applies this logic:

  • If free_generations_used < 1, increment the counter and create a reservation with used_free_generation = true
  • If credit_balance >= 1, decrement the balance, insert a reservation with used_free_generation = false and credit_amount = 1, and log a negative transaction in credit_transactions
  • If neither condition is met, raise CREDITS_REQUIRED
// Inside api/generate-image.js (lines 30-38)
const { data, error } = await client.rpc('reserve_generation_usage', {
  p_user_id: auth.user.id,
  p_case_id: caseId,
  p_prompt: prompt,
  p_force_credit: Boolean(profile?.isSuperAdmin)   // bypass free-gen limits
});

The SQL function guarantees atomicity—two simultaneous requests cannot over-draw the same credit because the row lock serializes access.

Step 2: Submitting to the External Provider

Once the reservation returns a reservationId, the handler proceeds to buildApimartGenerationPayload and submitApimartGeneration (located in shared/apimart.js). At this stage, the credits are already deducted, but the service has not yet been rendered.

Upon successful submission to Apimart, the system updates the corresponding row in public.generation_reservations with the provider_task_id, enabling asynchronous correlation between the local reservation and the provider's task tracking system.

// api/generate-image.js (lines 44-54)
const payload = buildApimartGenerationPayload(caseId, prompt);
const apimartResult = await submitApimartGeneration(payload);
// Update reservation with external task ID
await updateReservationWithProviderTask(reservation.reservationId, apimartResult.taskId);

Step 3: Failure Recovery and Success Completion

The critical safety mechanism occurs after the Apimart submission. If the request fails—whether due to network timeouts, rate limiting, or insufficient provider balance—the system triggers releaseReservation(), which invokes the public.release_generation_reservation RPC.

Refund Logic:

  • Free generations: Decrements free_generations_used (protected by GREATEST(..., 0) to prevent negative values)
  • Paid credits: Restores the credit_balance and inserts a refund transaction into credit_transactions with type = 'refund' and metadata containing the error code
-- From supabase/migrations/202605090001_user_credits.sql
IF v_reservation.used_free_generation THEN
  UPDATE public.profiles SET free_generations_used = GREATEST(free_generations_used - 1, 0)
  WHERE id = v_reservation.user_id;
ELSIF v_reservation.credit_amount > 0 THEN
  UPDATE public.profiles SET credit_balance = credit_balance + v_reservation.credit_amount
  WHERE id = v_reservation.user_id;
  INSERT INTO public.credit_transactions (user_id, amount, type, source, reference_id, metadata)
    VALUES (v_reservation.user_id, v_reservation.credit_amount,
            'refund', 'generation_failed', v_reservation.id,
            jsonb_build_object('error', p_error_code));
END IF;

On success, the webhook handler calls public.complete_generation_reservation, which simply updates the reservation status to 'succeeded' without modifying credits, as the deduction was already finalized during reservation.

Database Schema and Audit Trail

The credit reservation system relies on three core tables defined in the migration file:

  • public.profiles: Stores credit_balance, free_generations_used, and user tier information
  • public.generation_reservations: Tracks each pending generation with fields for user_id, case_id, prompt, used_free_generation, credit_amount, provider_task_id, and status
  • public.credit_transactions: Immutable ledger recording all credit changes with type (generation, refund, purchase) and metadata JSONB for debugging

This schema creates a complete audit trail, allowing administrators to trace any credit discrepancy from the reservation through to final completion or refund.

Summary

  • The credit reservation system in awesome-gpt-image-2 deducts credits atomically before external API calls using the reserve_generation_usage PostgreSQL function with row-level locking.
  • The api/generate-image.js handler orchestrates three phases: reservation, external submission, and finalization via releaseReservation or completion RPCs.
  • Rollback safety is enforced by the release_generation_reservation function, which restores free-generation quotas or refunds credits when Apimart requests fail.
  • All financial operations are logged in credit_transactions, creating an immutable record for accountability.
  • The design prevents race conditions through FOR UPDATE locks and guarantees that users are never charged for failed provider requests.

Frequently Asked Questions

What happens if the Apimart API times out after credits are reserved?

If the Apimart API times out or returns an error, the releaseReservation() function in api/generate-image.js is invoked with the specific error code. This calls the public.release_generation_reservation RPC, which examines the original reservation in public.generation_reservations. If the reservation consumed a free generation, the system decrements free_generations_used in the user's profile; if it deducted a credit, the balance is restored and a refund transaction is logged. The reservation status is then marked as 'failed'.

How does the system prevent race conditions when deducting credits?

The system prevents race conditions by using PostgreSQL's FOR UPDATE row locking inside the public.reserve_generation_usage function. When a user initiates a generation, the function locks the specific row in public.profiles before checking the credit balance or free-generation quota. This serialization ensures that concurrent requests see the correct, updated balance and cannot simultaneously deduct the same credit.

Can administrators bypass the credit reservation system?

Yes, super-administrators can bypass free-generation limits by setting the p_force_credit parameter to true when calling reserve_generation_usage. This boolean flag, passed from api/generate-image.js based on the isSuperAdmin profile property, forces the system to use paid credits even if free generations remain available. However, the reservation itself is still created and tracked; the bypass only affects the free-generation logic, not the atomic reservation requirement.

What is the difference between a released and completed reservation?

A released reservation indicates that the Apimart API request failed after the initial credit deduction, triggering the release_generation_reservation RPC to refund the user and mark the reservation status as 'failed'. A completed reservation indicates the external provider successfully accepted and processed the generation request; the complete_generation_reservation RPC marks the status as 'succeeded' without altering credits, as they were already consumed during the reservation phase.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →