diff --git a/.gitignore b/.gitignore index d45d27f79e..8c7e67f7e2 100644 --- a/.gitignore +++ b/.gitignore @@ -135,6 +135,7 @@ docs/* !docs/PAYMENT.md !docs/PAYMENT_CN.md !docs/ADMIN_PAYMENT_INTEGRATION_API.md +!docs/ASYNC_IMAGE_TASKS.md !docs/legal/ !docs/legal/*.md .serena/ diff --git a/README.md b/README.md index b06839745f..0d1fe23aee 100644 --- a/README.md +++ b/README.md @@ -688,6 +688,12 @@ Simple Mode is designed for individual developers or internal teams who want qui --- +## Asynchronous Image Tasks + +Long-running OpenAI/Grok image generation and editing can be submitted through `/v1/images/generations/async` or `/v1/images/edits/async`, then polled at `/v1/images/tasks/{task_id}` without holding a CDN connection open. See [Asynchronous Image Tasks](docs/ASYNC_IMAGE_TASKS.md) for request and response examples. + +--- + ## Grok / xAI Support Sub2API supports both Grok subscription accounts through xAI OAuth and standard xAI API-key accounts. Both account types forward OpenAI-compatible Responses traffic to xAI. diff --git a/docs/ASYNC_IMAGE_TASKS.md b/docs/ASYNC_IMAGE_TASKS.md new file mode 100644 index 0000000000..69db7b4932 --- /dev/null +++ b/docs/ASYNC_IMAGE_TASKS.md @@ -0,0 +1,111 @@ +# Asynchronous Image Tasks + +Asynchronous image tasks let clients submit long-running OpenAI-compatible image requests without keeping one HTTP connection open. This avoids proxy/CDN response timeouts such as Cloudflare 524 while preserving the existing image routing, billing, moderation, concurrency, and failover behavior. + +## Endpoints + +The authenticated gateway exposes both `/v1` paths and their existing no-prefix aliases: + +```text +POST /v1/images/generations/async +POST /v1/images/edits/async +GET /v1/images/tasks/{task_id} +``` + +The aliases are `/images/generations/async`, `/images/edits/async`, and `/images/tasks/{task_id}`. + +Only OpenAI and Grok groups are supported. Requests use the same JSON or multipart payload as the corresponding synchronous endpoint. Streaming image requests are rejected because a polled task returns one final JSON result. + +## Submit a task + +```bash +curl -i https://api.example.com/v1/images/generations/async \ + -H 'Authorization: Bearer sk-...' \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "gpt-image-1", + "prompt": "A lighthouse during a winter storm", + "size": "1536x1024" + }' +``` + +The server stores the initial task in Redis and responds with `202 Accepted`: + +```json +{ + "id": "imgtask_0123456789abcdef", + "task_id": "imgtask_0123456789abcdef", + "object": "image.generation.task", + "status": "processing", + "created_at": 1784092800, + "expires_at": 1784179200, + "poll_url": "/v1/images/tasks/imgtask_0123456789abcdef" +} +``` + +`Location` contains the polling path and `Retry-After: 3` provides the recommended polling interval. + +## Poll a task + +Use the same API key that submitted the task: + +```bash +curl https://api.example.com/v1/images/tasks/imgtask_0123456789abcdef \ + -H 'Authorization: Bearer sk-...' +``` + +While work is in progress: + +```json +{ + "id": "imgtask_0123456789abcdef", + "task_id": "imgtask_0123456789abcdef", + "object": "image.generation.task", + "status": "processing", + "created_at": 1784092800, + "expires_at": 1784179200 +} +``` + +On success, `result` is the unmodified JSON body from the synchronous image API, so URL and base64 response formats both remain supported: + +```json +{ + "id": "imgtask_0123456789abcdef", + "task_id": "imgtask_0123456789abcdef", + "object": "image.generation.task", + "status": "completed", + "http_status": 200, + "image_url": "https://...", + "result": { + "created": 1784092923, + "data": [{"url": "https://..."}] + }, + "created_at": 1784092800, + "completed_at": 1784092923, + "expires_at": 1784179323 +} +``` + +For URL responses, `image_url` mirrors the first `data[].url` for simple clients. On failure, the task reaches `failed` and exposes the original OpenAI-compatible error object where available: + +```json +{ + "id": "imgtask_0123456789abcdef", + "task_id": "imgtask_0123456789abcdef", + "object": "image.generation.task", + "status": "failed", + "http_status": 502, + "error": { + "type": "api_error", + "message": "Upstream request failed" + }, + "created_at": 1784092800, + "completed_at": 1784092923, + "expires_at": 1784179323 +} +``` + +All submit and poll responses include `Cache-Control: no-store`, preventing a CDN from caching the `processing` state. Tasks and results expire 24 hours after their latest state update. A task executes for at most 30 minutes. + +Task ownership is scoped to both user and API key. Unknown task IDs and IDs owned by another key both return `404`, avoiding task-existence disclosure. Polling remains available when the completed generation used the key's remaining balance; normal authentication, disabled-key, user, IP, and group checks still apply.