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

> Understand the credit reservation system in Awesome-GPT-Image-2. Learn how PostgreSQL transactions lock profiles and deduct credits pre-API submission, with automatic rollback on failure.

- Repository: [苍何/awesome-gpt-image-2](https://github.com/freestylefly/awesome-gpt-image-2)
- Tags: how-to-guide
- Published: 2026-09-04

---

**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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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`

```javascript
// 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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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.

```javascript
// 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

```sql
-- 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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/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.