# How the GPT-Image2 Platform Credit System Works: Technical Architecture Guide

> Understand the GPT-Image2 credit system. Discover how this reservation-based model deducts credits post-generation and logs transactions atomically in Supabase.

- Repository: [苍何/awesome-gpt-image-2](https://github.com/freestylefly/awesome-gpt-image-2)
- Tags: architecture
- Published: 2026-09-06

---

**The GPT-Image2 platform uses a reservation-based credit accounting model where credits are deducted only after successful image generation completion, with all transactions logged atomically in Supabase.**

The credit system in `freestylefly/awesome-gpt-image-2` is engineered for reliability and auditability. Built on PostgreSQL stored procedures in Supabase, it separates credit **reservation** from **settlement**, ensuring users never lose credits on failed generations. This article breaks down the complete architecture from database schema to API flows.

## Credit System Architecture Overview

The platform implements four interconnected layers:

- **Storage layer** – `profiles.credit_balance`, `credit_packs`, and `credit_transactions` tables
- **Reservation layer** – `generation_reservations` with `pending`/`completed`/`failed` states
- **Settlement layer** – RPC functions `reserve_generation_usage`, `complete_generation_reservation`, and `release_generation_reservation`
- **API layer** – Next.js endpoints that orchestrate the flow

All credit logic lives in Supabase migrations and serverless functions, making the system provider-agnostic and easily extensible.

## Core Data Model

### Profile Balances and Transaction History

User credit state is split between a fast lookup column and a detailed ledger:

| Component | Table | Purpose |
|-----------|-------|---------|
| **Current balance** | `profiles.credit_balance` | Integer column for instant availability checks |
| **Purchase records** | `credit_packs` | Tracks purchased credit bundles |
| **Audit trail** | `credit_transactions` | Immutable log of all credit movements |

The `credit_transactions` table captures `type` values including `purchase`, `membership_grant`, `generation`, `refund`, and `admin_adjustment`. This design satisfies accounting requirements while keeping balance checks performant.

**Source**: Schema defined in [[`supabase/migrations/20260509090000_membership_billing.sql`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/supabase/migrations/20260509090000_membership_billing.sql)](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/supabase/migrations/20260509090000_membership_billing.sql)

## How Generation Reservations Work

### Step 1: Reserving Credit with `reserve_generation_usage`

When a user requests an image, the API calls the stored procedure `reserve_generation_usage`. This function implements the core reservation logic:

1. Checks if `free_generations_used < 1` → uses free slot if available
2. Otherwise verifies `credit_balance >= 1`
3. Creates a `generation_reservations` row with:
   - `status: 'pending'`
   - `type: 'generation'` (or `'free'` for complimentary uses)
   - `provider: 'apimart'`
4. Returns the `reservation_id` for tracking

```sql
-- Conceptual flow from migration 20260512090000_google_account_center.sql
SELECT reserve_generation_usage(
  p_user_id := 'uuid',
  p_case_id := null,
  p_prompt := 'a red sports car',
  p_force_credit := false
);

```

The `p_force_credit` parameter allows bypassing free generation eligibility—useful for premium features or admin overrides.

**Source**: [`supabase/migrations/20260512090000_google_account_center.sql#L3-L44`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/supabase/migrations/20260512090000_google_account_center.sql)

### Step 2: External Provider Processing

With the reservation created, the API forwards the generation task to APImart:

```javascript
// From api/generate-image.js
const { data: reservation, error } = await supabase.rpc('reserve_generation_usage', {
  p_user_id: user.id,
  p_case_id: caseId || null,
  p_prompt: prompt,
  p_force_credit: forceCredit || false
});

// Reservation.id becomes the task correlation ID
await apimartClient.createTask({
  taskId: reservation.id,
  prompt: prompt,
  webhookUrl: `${BASE_URL}/api/generation/callback`
});

```

The credit remains **undeducted** during this phase—only the reservation exists.

### Step 3: Settlement via Callback

When APImart completes (or fails) the task, it calls the platform's callback endpoint. The helper `settlePlatformGeneration` in [`api/_lib/generation.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/_lib/generation.js) orchestrates the final accounting:

```javascript
// Simplified from api/_lib/generation.js
export async function settlePlatformGeneration(supabase, reservation, providerResult) {
  const { status, resultUrl, costUsd } = parseProviderPayload(providerResult);
  
  if (status === 'success') {
    // Deduct credit permanently and log transaction
    await supabase.rpc('complete_generation_reservation', {
      p_reservation_id: reservation.id,
      p_provider_result_url: resultUrl,
      p_provider_cost_usd: costUsd
    });
  } else {
    // Release hold—no credit deducted
    await supabase.rpc('release_generation_reservation', {
      p_reservation_id: reservation.id,
      p_failure_reason: providerResult.errorMessage
    });
  }
}

```

**Source**: [`api/_lib/generation.js#L60-L84`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/_lib/generation.js) (settlement helper); [[`api/generation/callback.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/generation/callback.js)](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/generation/callback.js) (endpoint)

### Reservation Completion Functions

| Function | Action | Credit Impact |
|----------|--------|---------------|
| `complete_generation_reservation` | Marks success, stores result URL | Decrements `credit_balance` by 1, creates `generation` transaction |
| `release_generation_reservation` | Marks failed/cancelled | No balance change, reservation archived |

This atomic settlement pattern prevents credit loss from provider failures, network timeouts, or duplicate callbacks.

**Source**: [`supabase/migrations/20260512090000_google_account_center.sql#L70-L90`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/supabase/migrations/20260512090000_google_account_center.sql)

## Admin Credit Management

### Granting and Adjusting Credits

Administrators manipulate balances through the `grant_user_credits` function, used for:

- **Purchases** – Stripe webhook triggers positive adjustment
- **Membership grants** – Recurring subscription credits
- **Manual adjustments** – Support compensations or corrections
- **Refunds** – Negative adjustments for disputed charges

```javascript
// From api/admin/credits/adjust.js
const { error } = await supabase.rpc('grant_user_credits', {
  p_user_id: targetUserId,
  p_amount: amount,           // positive or negative
  p_type: 'admin_adjustment', // or 'purchase', 'membership_grant', 'refund'
  p_description: reason,
  p_related_entity_type: 'admin_panel',
  p_related_entity_id: adminId
});

```

Every grant creates a corresponding `credit_transactions` row, maintaining full auditability even for manual interventions.

**Source**: [`supabase/migrations/20260509090000_membership_billing.sql#L205`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/supabase/migrations/20260509090000_membership_billing.sql) (function definition); [[`api/admin/credits/adjust.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/admin/credits/adjust.js)](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/admin/credits/adjust.js) (endpoint)

## End-to-End Generation Flow

Here's the complete sequence from user action to credit settlement:

1. **User submits prompt** → `POST /api/generate-image`
2. **Reserve credit** → `reserve_generation_usage` RPC executes
3. **Queue with provider** → APImart task created with reservation ID
4. **Async processing** → Image generates (seconds to minutes)
5. **Provider callback** → `POST /api/generation/callback`
6. **Find reservation** → `findPlatformGeneration` helper locates pending row
7. **Settle outcome** → `complete_generation_reservation` or `release_generation_reservation`

```javascript
// Complete API handler example: api/generate-image.js
import { supabase } from '../_lib/supabase.js';

export default async function handler(req, res) {
  const { user } = await authenticateRequest(req);
  const { prompt, caseId, forceCredit } = req.body;
  
  // Atomic reservation
  const { data: reservation, error } = await supabase.rpc('reserve_generation_usage', {
    p_user_id: user.id,
    p_case_id: caseId || null,
    p_prompt: prompt,
    p_force_credit: forceCredit || false
  });
  
  if (error) {
    if (error.message.includes('insufficient_credits')) {
      return res.status(402).json({ error: 'Insufficient credits' });
    }
    throw error;
  }
  
  // Forward to provider
  await createApimartTask(reservation.id, prompt);
  
  res.status(202).json({ 
    reservationId: reservation.id,
    status: 'pending',
    estimatedSeconds: 30
  });
}

```

**Source**: [[`api/generate-image.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/generate-image.js)](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/generate-image.js)

## Key Design Decisions

### Why Reservations Instead of Immediate Deduction

The **reservation pattern** solves three problems:

- **Provider reliability** – Failed generations don't consume paid credits
- **Idempotency** – Duplicate callbacks from APImart are handled gracefully via reservation status checks
- **Concurrency safety** – PostgreSQL row-level locks prevent race conditions when multiple generations start simultaneously

### Why Split Balance and Transaction Tables

- `profiles.credit_balance` enables O(1) availability checks
- `credit_transactions` provides immutable history for disputes and analytics
- The split allows caching balance reads while keeping writes transactional

### Extensibility for Future Providers

Adding a new image provider requires only:

1. New `provider` enum value in `generation_reservations`
2. Provider-specific task submission logic in the API endpoint
3. Payload parser in the callback handler

The core credit reservation and settlement functions remain unchanged.

## Summary

- **Credits are reserved, not immediately spent** – The `reserve_generation_usage` function creates a pending reservation without deducting balance
- **Settlement is atomic** – Successful completions trigger `complete_generation_reservation` to deduct credit; failures call `release_generation_reservation` with no charge
- **All movements are logged** – The `credit_transactions` table captures every purchase, grant, generation, and adjustment with full metadata
- **Admin operations use the same primitives** – `grant_user_credits` powers purchases, subscriptions, refunds, and manual adjustments
- **Architecture is provider-agnostic** – New image generation backends integrate without touching credit logic

## Frequently Asked Questions

### What happens if a generation fails after I start it?

Your credit is **not deducted**. The `release_generation_reservation` function marks the reservation as failed without modifying your `credit_balance`. You can retry immediately or contact support if failures persist.

### How do free generations interact with paid credits?

The `reserve_generation_usage` function automatically uses your **free generation slot first** (tracked in `profiles.free_generations_used`). Only when no free slots remain will it check `credit_balance >= 1` and deduct a paid credit.

### Can admins manipulate credit balances arbitrarily?

Yes, through the `grant_user_credits` RPC and the [`api/admin/credits/adjust.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/admin/credits/adjust.js) endpoint. However, all adjustments create **permanent audit records** in `credit_transactions` with `type='admin_adjustment'` and the admin's identity attached.

### What prevents duplicate credit deductions from provider callbacks?

The `generation_reservations.status` column acts as a state machine. Both `complete_generation_reservation` and `release_generation_reservation` verify `status='pending'` before proceeding, making subsequent idempotent calls harmless.