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

> Discover how Sub2API manages asynchronous image generation using background tasks and status endpoints. Learn about its task-based architecture and implementation for efficient processing.

- Repository: [Wesley Liddick/sub2api](https://github.com/Wei-Shaw/sub2api)
- Tags: internals
- Published: 2026-08-23

---

**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)](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)](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)](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)](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`](https://github.com/Wei-Shaw/sub2api/blob/main/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`](https://github.com/Wei-Shaw/sub2api/blob/main/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

```http
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:**

```json
{
  "task_id": "imgtask_01H9Z4K3V2G7L8M9N0OP"
}

```

### Polling for Completion

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

```

**While processing:**

```json
{
  "status": "pending"
}

```

**When completed:**

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

```

**On upstream failure:**

```json
{
  "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`](https://github.com/Wei-Shaw/sub2api/blob/main/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`](https://github.com/Wei-Shaw/sub2api/blob/main/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`](https://github.com/Wei-Shaw/sub2api/blob/main/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.