Sub2API Batch Image MVP provides asynchronous Gemini image batch generation through a unified API surface backed by Redis workers, PostgreSQL state, and provider-specific batch backends.
Supported providers:
gemini_apivertexAPI users do not see Gemini file names, Vertex job names, GCS paths, signed URLs, API keys, or service account material. Downloads are proxied through Sub2API in this MVP.
POST /v1/images/batches
GET /v1/images/batches/{id}
GET /v1/images/batches/{id}/items
GET /v1/images/batches/{id}/items/{custom_id}/content
GET /v1/images/batches/{id}/download
POST /v1/images/batches/{id}/cancel
DELETE /v1/images/batches/{id}/outputs
Submit request:
{
"model": "gemini-2.5-flash-image",
"provider": "gemini_api",
"items": [
{
"custom_id": "cover_001",
"prompt": "A clean product hero image...",
"output_count": 1,
"reference_images": [
{
"id": "product-front",
"type": "subject",
"mime_type": "image/png",
"data": "<base64 image bytes without a data URL prefix>"
},
{
"id": "style",
"type": "style",
"mime_type": "image/jpeg",
"file_uri": "gs://internal-managed-bucket/batch-image/refs/style.jpg"
}
]
}
],
"image_size": "1K",
"response_mime_type": "image/png"
}
reference_images is optional per item. Inline data is a base64 string decoded by the backend; file_uri is reserved for internal Google Cloud Storage references and must be a gs:// URI. Each reference image must use one of image/png, image/jpeg, or image/webp. Current model limits are:
gemini-2.5-flash-image and other Flash Image aliases: up to 3 reference images per item.gemini-3-pro-image and other Pro Image aliases: up to 14 reference images per item.output_count expansion across all items. This is an internal Sub2API guardrail for request size and cost control, not the generated-image cap and not a Pro Image per-item capability. The generated-output cap is 200 images per job.gs:// file_uri references or split the request into multiple jobs.output_count is optional per item and defaults to 1. It means "repeat this prompt and reference image set N times" rather than relying on Gemini to return multiple images from one upstream request. The backend expands each repeat into a separate provider JSONL line with suffixed custom ids such as cover_001_01, cover_001_02. Current limits are:
max_download_bytes_per_request.Public batch response:
{
"id": "imgbatch_0123456789abcdef0123456789abcdef",
"object": "image.batch",
"status": "queued",
"model": "gemini-2.5-flash-image",
"provider": "gemini_api",
"item_count": 1,
"success_count": 0,
"fail_count": 0,
"estimated_cost": 0.25,
"actual_cost": null,
"created_at": 1783123200,
"submitted_at": 1783123201,
"settled_at": null
}
Public items response:
{
"object": "list",
"data": [
{
"custom_id": "cover_001",
"status": "succeeded",
"mime_type": "image/png",
"file_extension": "png",
"image_count": 1,
"error": null
}
],
"has_more": false
}
Internal lifecycle:
created -> uploading -> submitted -> running -> indexing -> settling -> completed
Terminal and cleanup statuses:
failed
cancelled
completed -> output_deleted
Public status mapping:
created/uploading/submitted -> queued
running -> running
indexing -> processing_results
settling -> settling
completed -> completed
failed -> failed
cancelled -> cancelled
output_deleted -> output_deleted
completed -> output_deleted happens after manual output deletion or TTL cleanup.
Redis is used for wakeups, retries, worker coordination, per-job locks, and download limiting. PostgreSQL remains the source of truth.
batch_image.queue_enabled defaults to false. When it is set to true, app startup starts BatchImageWorker runtime loops for the Redis ready queue, delayed queue mover, and stale active recovery. The worker reserves jobs from the Redis ready queue and blocks there when no job is available.
Redis structures:
batch_image.queue_ready_keybatch_image.queue_delayed_keybatch_image.queue_active_keybatch_image.inflight_key_prefixbatch_image.lock_key_prefixbatch_image.idempotency_key_prefixWorkers should reserve from Redis. They are not expected to run as a database scan loop.
The worker does not perform DB scan polling. Database reads happen only after a Redis queue reservation yields a specific batch id.
MVP billing rules:
output_count > 1, but the public MVP billing model does not add a separate reference-image surcharge. User-facing estimated, held, and settled amounts are still based on the output image count and configured batch image unit price.batch_image_settlement:{batch_id}.Exact production pricing is resolved through model pricing configuration and is not defined here.
Defaults:
Manual output deletion:
DELETE /v1/images/batches/{id}/outputs
After output cleanup, downloads return 410 Gone with BATCH_IMAGE_OUTPUT_DELETED.
Cleanup never accepts user-supplied provider paths. Provider cleanup must use server-generated refs and prefix-safe deletion.
For the managed Vertex/GCS batch bucket, disable Cloud Storage soft delete or configure lifecycle carefully to avoid hidden retained storage cost.
gemini_api:
apikey upstream accounts with a configured API key.vertex:
BatchPredictionJob with managed GCS JSONL.service_account upstream accounts with valid service account JSON.1K/default only in MVP.2K or 4K.Other Gemini account/login types are not selected by the current batch image providers unless they expose equivalent API-key or service-account credentials through the same provider flow. They were not covered by the 2026-07-07 PR validation.
Operators must enable Gemini/Vertex capability in Google's official console before turning on Sub2API batch image for any group. Sub2API feature flags and group switches do not create Google-side access by themselves.
Recommended production path:
BATCH_IMAGE_ENABLED globally, enable image generation on the intended Gemini group, then enable allow_batch_image_generation for that group. Non-Gemini groups are not eligible for batch image generation, and the admin UI only shows the batch image group switch after image generation is enabled on a Gemini group.API-key path:
x-goog-api-key compatibility header still expects a Sub2API key, not a plain Google key.Official references:
These keys exist in backend/internal/config/config.go:
batch_image:
enabled: false
max_items_per_job_default: 200
max_items_per_job_trial: 50
max_output_images_per_job: 200
max_output_images_per_item: 4
max_prompt_chars_per_item: 8000
max_reference_images_per_job: 1000
max_reference_inline_bytes_per_job: 134217728
default_response_mime_type: "image/png"
default_image_size: "1K"
max_download_items_zip: 200
max_download_bytes_per_request: 536870912
max_download_duration_seconds: 600
max_download_concurrency_per_user: 1
input_retention_after_terminal_hours: 24
output_retention_after_terminal_hours: 72
output_retention_max_days: 7
cleanup_interval_minutes: 30
cleanup_batch_size: 100
queue_enabled: false
queue_ready_key: "batch_image:queue:ready"
queue_delayed_key: "batch_image:queue:delayed"
queue_active_key: "batch_image:queue:active"
inflight_key_prefix: "batch_image:queue:inflight:"
lock_key_prefix: "batch_image:queue:lock:"
idempotency_key_prefix: "batch_image:queue:idem:"
inflight_ttl_seconds: 604800
job_lock_ttl_seconds: 300
default_requeue_delay_seconds: 30
error_retry_delay_seconds: 60
lock_conflict_delay_seconds: 5
stale_active_after_seconds: 600
delayed_mover_interval_seconds: 5
recovery_interval_seconds: 300
delayed_move_limit: 100
recover_limit: 100
vertex_enabled: false
vertex_project_id: ""
vertex_location: "global"
vertex_managed_gcs_bucket: ""
vertex_managed_gcs_prefix: "batch-image/{env}/{batch_id}"
vertex_input_retention_hours: 24
vertex_output_retention_hours: 72
vertex_batch_prediction_base_url: ""
vertex_gcs_base_url: ""
Feature flags default to disabled.
batch_image.enabled.batch_image.queue_enabled when workers should consume queue jobs.Core smoke and compile commands:
go test -tags=unit ./internal/service -run 'BatchImage' -count=1
go test -tags=unit ./internal/config ./internal/service ./internal/repository -count=1
go test ./internal/config ./internal/service ./internal/repository ./internal/handler ./internal/server/routes -run '^$'
go test ./... -run '^$'
These commands should not require Docker, testcontainers, Redis, GCP, Gemini, Vertex, or GCS.
rfcs/batch-image-issue-draft.md unless maintainers explicitly want it.159_batch_image_foundation.sql, then 160_batch_image_provider_refs.sql, then later migrations.