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

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, the providerFieldsForTask() function transforms the provider's Unix timestamp into an ISO format suitable for database storage:

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:

// 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 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:

// 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:

// 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, 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 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 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.

The generation_reservations table includes a provider_result_expires_at column populated by providerFieldsForTask() in 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 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 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.

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 →