How Sub2API Handles Asynchronous Image Generation: Task-Based Architecture and Implementation

Sub2API handles asynchronous image generation by accepting requests via a dedicated async endpoint, creating a background task with a unique ID, and allowing clients to poll a separate status endpoint to retrieve the final image or error once processing completes.

The Wei-Shaw/sub2api repository implements a non-blocking image generation system designed to manage long-running upstream API calls without keeping HTTP connections open. This architecture decouples request acceptance from actual image generation, enabling better resource utilization and improved client responsiveness when working with providers like OpenAI's image models.

Core Architecture Components

The asynchronous image generation system relies on four tightly integrated components that manage the full lifecycle from submission to result retrieval.

AsyncImageHandler serves as the HTTP interface for both submission and polling. Located in [backend/internal/handler/image_task_handler.go](https://github.com/Wei-Shaw/sub2api/blob/main/backend/internal/handler/image_task_handler.go), this handler validates incoming requests, triggers security audits, and manages the background goroutine that executes the upstream call.

ImageTaskService provides the persistence layer and state machine for tasks. Defined in [backend/internal/service/image_task.go](https://github.com/Wei-Shaw/sub2api/blob/main/backend/internal/service/image_task.go), it exposes Create, Get, Complete, and Fail operations while governing task TTL (time-to-live) and execution timeouts.

OpenAIGatewayHandler handles the actual upstream communication. Implemented in [backend/internal/handler/openai_images.go](https://github.com/Wei-Shaw/sub2api/blob/main/backend/internal/handler/openai_images.go), this component issues the real /v1/images/generations requests to providers and streams responses back to the async handler.

Router Registration exposes the endpoints via [backend/internal/server/routes/gateway.go](https://github.com/Wei-Shaw/sub2api/blob/main/backend/internal/server/routes/gateway.go), mapping POST /images/generations/async for submissions and GET /images/tasks/:task_id for status checks.

The Async Request Flow

Understanding how requests move through the system requires examining three distinct phases: submission, background processing, and result retrieval.

Submitting the Request

When a client sends a generation request to POST /images/generations/async, the AsyncImageHandler.Submit method performs the following operations:

  1. Parses the JSON body for prompt, model, and optional response_format parameters
  2. Invokes checkSecurityAuditBeforeSubmit to validate the request against security policies
  3. Verifies that async features are enabled via h.enabled() and h.pollable() checks
  4. Calls tasks.Create to generate a UUID (task_id) and persist a pending record
  5. Returns 202 Accepted with the task ID immediately
  6. Launches a background goroutine via h.run to process the upstream request

The immediate 202 response allows clients to release the HTTP connection while the heavy lifting occurs asynchronously.

Background Processing

Once detached from the main request thread, the background goroutine invokes the OpenAIGatewayHandler to execute the actual image generation against the configured upstream provider. According to the source code in backend/internal/handler/image_task_handler.go, this process handles two outcomes:

  • Success: The handler invokes tasks.Complete, storing the image URL or base64 data as a JSON blob in the result field and updating the status to completed
  • Failure: The handler invokes tasks.Fail, persisting the error payload and upstream status code (such as 429 for rate limits) with a status of failed

A 10-minute execution timeout (configurable) automatically aborts hanging upstream calls, marking tasks as failed to prevent resource exhaustion.

Polling for Results

Clients retrieve results by calling GET /images/tasks/:task_id, which invokes AsyncImageHandler.Get. This endpoint queries the ImageTaskService and returns different responses based on task state:

  • Pending: Returns HTTP 202 with {"status": "pending"} (client should retry)
  • Completed: Returns HTTP 200 with the stored result payload containing image data or URLs
  • Failed: Returns the original upstream error code (e.g., 429) with the stored error details

This polling mechanism ensures clients never block waiting for slow upstream providers while maintaining access to final results.

Configuration and Timeouts

Sub2API provides robust controls for managing async task lifecycle and availability.

Time-to-Live (TTL): The ImageTaskService automatically expires completed or failed tasks after 24 hours by default, preventing storage bloat.

Feature Toggles: The system checks ImageStorageSettingService (defined in backend/internal/service/image_storage_settings.go) to determine if async generation is enabled. When disabled, the submission endpoint returns 404 Not Found.

Pollability Guards: The h.pollable() check ensures that polling endpoints are only accessible when the async feature set is active, maintaining security boundaries.

Practical Implementation Examples

Submitting an Async Image Request

POST /images/generations/async HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <api-key>

{
  "model": "gpt-image-2",
  "prompt": "A futuristic city at sunset, ultra-realistic",
  "size": "1024x1024"
}

Response:

{
  "task_id": "imgtask_01H9Z4K3V2G7L8M9N0OP"
}

Polling for Completion

GET /images/tasks/imgtask_01H9Z4K3V2G7L8M9N0OP HTTP/1.1
Host: api.example.com
Authorization: Bearer <api-key>

While processing:

{
  "status": "pending"
}

When completed:

{
  "status": "completed",
  "result": {
    "data": [
      {
        "url": "https://cdn.example.com/abcd1234.png"
      }
    ]
  }
}

On upstream failure:

{
  "status": "failed",
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit reached for gpt-image-2"
  }
}

Summary

Sub2API implements asynchronous image generation through a robust task-oriented architecture that provides several key benefits:

  • Non-blocking requests via immediate 202 responses and background goroutines
  • Reliable state management through the ImageTaskService with configurable TTL and timeout safeguards
  • Flexible retrieval via polling endpoints that expose both successful results and upstream errors
  • Operational control through runtime feature toggles in ImageStorageSettingService
  • Clean integration with existing security audit and rate-limiting infrastructure

Frequently Asked Questions

How does Sub2API prevent async tasks from running indefinitely?

According to the implementation in backend/internal/service/image_task.go, the ImageTaskService enforces a default 10-minute execution timeout on background goroutines. If the upstream provider fails to respond within this window, the task is automatically marked as failed, preventing resource exhaustion and zombie processes.

Can I disable asynchronous image generation without restarting Sub2API?

Yes. The system uses ImageStorageSettingService (referenced in backend/internal/service/image_storage_settings.go) to check feature availability at runtime. When async generation is disabled via configuration, the AsyncImageHandler returns 404 Not Found for submission requests and disables polling endpoints, allowing operators to toggle functionality without service restarts.

What happens to completed image tasks after they finish processing?

The ImageTaskService applies a 24-hour TTL (time-to-live) to all task records by default. Completed tasks remain queryable via GET /images/tasks/:task_id during this window, after which they are automatically purged from storage. This design balances result accessibility with storage efficiency for high-throughput deployments.

How does Sub2API handle rate limiting from upstream providers during async generation?

When the OpenAIGatewayHandler encounters a rate limit (HTTP 429) or other upstream error, it propagates this error to the background goroutine in image_task_handler.go. The handler then invokes tasks.Fail, storing the original error code and message. When the client polls for the result, they receive the upstream status code (e.g., 429) along with the error details, allowing proper client-side retry logic with exponential backoff.

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 →