# How to Handle Image URL Expiration and Storage in Production with Awesome-GPT-Image-2

> Learn to handle image URL expiration and storage in production. Persist CDN timestamps, clean caches, and implement fallbacks with Awesome-GPT-Image-2 for robust image management.

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

---

**To handle image URL expiration and storage in production, persist CDN expiration timestamps in your database alongside the URL, implement automatic cleanup of client-side caches, and provide graceful fallbacks when temporary links become invalid.**

Managing temporary CDN URLs from third-party image generation providers requires careful coordination between server-side persistence and client-side state management. When APIMart returns short-lived image URLs, the `freestylefly/awesome-gpt-image-2` repository demonstrates robust patterns for tracking asset lifetime and preventing broken links in production environments.

## Understanding the Temporary CDN Challenge

APIMart stores generated images on a temporary CDN that automatically deletes files after a set period. When generation completes, the provider returns an object containing `image` (the public URL) and `expiresAt` (a Unix timestamp in seconds). Without persisting this metadata, your application would serve broken links after the CDN cleans up the asset.

The system must balance **server-side persistence** with **client-side expiration handling** to ensure users never encounter stale references while respecting the temporary nature of provider storage.

## Persisting Expiration Metadata Server-Side

### Extracting Provider Fields

In [`api/_lib/generation.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/_lib/generation.js), the `providerFieldsForTask()` function transforms the provider's Unix timestamp into an ISO format suitable for database storage:

```js
export function providerFieldsForTask(task) {
  const expiresAt = task?.expiresAt
    ? new Date(Number(task.expiresAt) * 1000).toISOString()
    : null;
  return {
    provider_cost_usd: Number.isFinite(Number(task?.cost)) ? Number(task.cost) : null,
    provider_result_url: task?.image || null,
    provider_result_expires_at: expiresAt
  };
}

```

This function captures three critical fields: the URL in `provider_result_url`, the expiration in `provider_result_expires_at`, and the cost in `provider_cost_usd`.

### Updating Generation Reservations

When the generation settles, the `settlePlatformGeneration` function persists these fields to the `generation_reservations` table:

```js
// api/_lib/generation.js – called after APIMart returns a task
await client
  .from('generation_reservations')
  .update(providerFieldsForTask(task))   // stores URL + expiresAt
  .eq('id', reservation.id)
  .eq('status', 'pending');

```

After updating the row, the system marks the reservation as completed via the `complete_generation_reservation` stored procedure, ensuring atomic transaction safety.

### Formatting Stored Generations

The helper `formatStoredGeneration` reads `provider_result_expires_at` from the database and computes a **seconds-epoch** value (`expiresAt`) for frontend consumption. This normalization allows the client to compare timestamps against `Date.now()` without complex parsing.

## Managing Client-Side Cache Expiration

### Browser Storage Risks

The frontend caches generated test results in `localStorage` via [`src/apimartClient.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/src/apimartClient.js) to enable instant UI rendering after page reloads. However, because browser storage persists beyond CDN lifetimes, the system must actively purge expired entries.

### Automatic Cleanup Routine

The `cleanupExpiredGeneratedTests()` function iterates through cached entries and removes those whose `expiresAt` timestamp has passed:

```js
// src/apimartClient.js
export function cleanupExpiredGeneratedTests(storage, nowMs = Date.now()) {
  const saved = readObject(GENERATED_TESTS_STORAGE_KEY, storage);
  const now = nowMs;
  let changed = false;
  for (const [caseId, entry] of Object.entries(saved)) {
    if (entry.expiresAt && entry.expiresAt * 1000 <= now) {
      delete saved[caseId];
      changed = true;
    }
  }
  if (changed) writeObject(GENERATED_TESTS_STORAGE_KEY, saved, storage);
}

```

### Retrieving Valid Generations

Before returning cached data, `getSavedGeneration()` invokes the cleanup routine to ensure stale URLs are never displayed:

```js
// src/apimartClient.js
export function getSavedGeneration(caseId, storage, nowMs = Date.now()) {
  cleanupExpiredGeneratedTests(storage, nowMs);
  const saved = readObject(GENERATED_TESTS_STORAGE_KEY, storage);
  return saved[caseId] ?? null;
}

```

This pattern guarantees that any retrieval operation first validates the cache against the current time, preventing broken image references in the UI.

## Production-Ready Implementation Strategy

### Database Schema Design

Store the `provider_result_expires_at` column as a timestamp with timezone to ensure accurate CDN deletion tracking. This field enables the backend to determine URL validity without making external requests to the provider.

### Secure Credential Handling

Never expose raw APIMart API keys to the client. According to the source code in [`src/apimartClient.test.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/src/apimartClient.test.js), only masked key suffixes are stored in browser storage. This approach prevents secret leakage while maintaining debugging capabilities.

### Graceful Degradation Patterns

When [`api/generation/status.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/generation/status.js) detects that a generation has settled but the URL may have expired, it returns the stored fields including `expiresAt`. If the image is already expired when the UI requests it, the client falls back to a "generation pending" state and re-queries the provider rather than displaying a broken image.

### Transaction Safety

The completion flow uses the `complete_generation_reservation` stored procedure after updating provider fields. This atomic operation prevents race conditions during high-concurrency scenarios where multiple webhooks might attempt to settle the same reservation simultaneously.

## Summary

- **Persist expiration timestamps** in `provider_result_expires_at` to track CDN asset lifetime in the `generation_reservations` table
- **Normalize timestamp formats** using `providerFieldsForTask()` for database storage and `formatStoredGeneration()` for frontend consumption
- **Implement client-side cleanup** via `cleanupExpiredGeneratedTests()` to purge stale `localStorage` entries before they reach the UI
- **Provide fallback states** in [`api/generation/status.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/generation/status.js) to re-query providers when cached URLs expire before user retrieval
- **Protect API credentials** by storing only masked suffixes in browser storage, keeping raw keys server-side exclusively

## Frequently Asked Questions

### Why do image URLs from APIMart expire?

APIMart uses temporary CDN storage that automatically deletes generated images after a specified duration to manage storage costs. The provider returns an `expiresAt` Unix timestamp indicating the exact moment of deletion, requiring applications to treat these URLs as volatile assets rather than permanent resources.

### How does the database prevent serving expired image links?

The `generation_reservations` table includes a `provider_result_expires_at` column populated by `providerFieldsForTask()` in [`api/_lib/generation.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/_lib/generation.js). This ISO-formatted timestamp preserves the exact expiration moment, allowing the backend to determine URL validity and prevent the insertion of stale references into permanent storage.

### What happens when a user attempts to view an expired cached image?

The client-side `getSavedGeneration()` function in [`src/apimartClient.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/src/apimartClient.js) automatically invokes `cleanupExpiredGeneratedTests()` before returning data. If the entry expired, it is deleted from `localStorage` and the function returns `null`, triggering the UI to fall back to a pending state and potentially re-query [`api/generation/status.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/generation/status.js) for fresh data.

### Is it safe to store generation metadata in browser localStorage?

The implementation stores only public URLs, expiration timestamps, and masked API key suffixes—never raw authentication credentials. Sensitive data remains server-side, while the client retains only non-sensitive metadata necessary for display purposes, ensuring compliance with security best practices for third-party API integration.