产品 · 2026-05-13
异步图像生成与编辑,正式上线 TheRouter
通过一个 OpenAI 兼容的 API 调用 GPT-Image、DALL·E、Imagen、Grok-Image: 提交任务、轮询状态、从 S3 下载。没有 30 秒代理超时,不需要重写前端。
为什么要走异步
前沿图像模型 —— openai/gpt-image-2、openai/gpt-image-1.5、google/imagen-4、xai/grok-image-1 —— 单张通常需要 30–180 秒。 这超过了几乎所有反向代理的容忍度:CloudFront 上限 60 秒, Cloudflare 免费档默认 100 秒,AWS ALB 空闲超时 60 秒。 浏览器 fetch 虽然默认不超时,但用户会先关掉标签页。
文本模型可以硬扛,但图像生成不能依赖单条长连接 HTTP, 必须切到任务系统。我们把它做了出来:OpenAI 兼容(不用自定义 SDK), 覆盖所有图像模型,「成功」的定义是「字节已经躺在 S3 里、可以拿到预签名 URL」。 本文是上线公告 + 完整教程。
你能拿到什么
- 统一提交接口:
POST /v1/images/generations?async=true无论模型,都在 1–2 秒内返回HTTP 202、 带job_id和polling_url。 - 统一轮询接口:
GET /v1/jobs/:id返回状态 JSON。 五种状态:queued → in_progress → succeeded, 终态还有failed/cancelled/expired。 - 两种下载方式:
unsigned_urls[0]是 7 天有效期的 S3 预签名 URL,可直接拉;GET /v1/jobs/:id/content网关返回 302 重定向, 每次访问签名新的 URL,适合需要审计的场景。 - 原子取消 + 退款:
DELETE /v1/jobs/:id在queued状态下原子取消, 全额退回预留 credits。 - 透明计费:提交时按估算预扣,
succeeded时按上游实际成本结算(多退少补),failed或cancelled全额退还。 不存在「扣了钱没拿到图」的中间态。 - 图像编辑同形态:
POST /v1/images/edits?async=true接受源图 + prompt, 走完全相同的任务生命周期。
快速上手
1) 提交任务
curl -X POST "https://api.therouter.ai/v1/images/generations?async=true" \
-H "Authorization: Bearer $THEROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-image-2",
"prompt": "白色大理石桌上一颗极简风格的红苹果,柔和自然光",
"size": "1024x1024",
"quality": "high"
}'响应(HTTP 202):
{
"id": "img_uxRZK4KwxkWaaHXRfwP0fWUDST",
"object": "image_job",
"status": "queued",
"model": "openai/gpt-image-2",
"created_at": 1778637249,
"polling_url": "https://api.therouter.ai/v1/jobs/img_uxRZK4KwxkWaaHXRfwP0fWUDST",
"expires_at": 1779242049
}拿到 202 后立刻把 job.id 持久化下来。客户端进程死掉也没关系, 只要 ID 还在,从任何地方都可以恢复轮询。
2) 轮询状态
curl "https://api.therouter.ai/v1/jobs/img_uxRZK4KwxkWaaHXRfwP0fWUDST" \ -H "Authorization: Bearer $THEROUTER_API_KEY"
worker 处理完后:
{
"id": "img_uxRZK4KwxkWaaHXRfwP0fWUDST",
"object": "image_job",
"status": "succeeded",
"model": "openai/gpt-image-2",
"created_at": 1778637249,
"completed_at": 1778637444,
"polling_url": "https://api.therouter.ai/v1/jobs/...",
"content_url": "https://api.therouter.ai/v1/jobs/.../content",
"unsigned_urls": [
"https://therouter-media-prod.s3.us-east-2.amazonaws.com/...&X-Amz-Signature=..."
],
"image_count": 1,
"usage": {
"prompt_tokens": 0,
"completion_tokens": 0,
"cost_credits": 9
},
"error": null
}3) 下载图像
unsigned_urls[0] 是带 AWS 签名的 S3 URL,7 天内有效, 不需要再带 Authorization。
curl -L -o apple.png "<unsigned_urls[0]>"
或者带 Bearer token 访问 GET /v1/jobs/:id/content, 网关每次返回一个新的签名 302 跳转,适合需要审计每次下载的场景。
完整客户端教程
Python
import os, time, requests
API = "https://api.therouter.ai"
KEY = os.environ["THEROUTER_API_KEY"]
H = {"Authorization": f"Bearer {KEY}"}
# 1) 提交
r = requests.post(
f"{API}/v1/images/generations?async=true",
headers={**H, "Content-Type": "application/json"},
json={
"model": "openai/gpt-image-2",
"prompt": "白色大理石桌上一颗极简风格的红苹果",
},
timeout=30,
)
r.raise_for_status()
job = r.json()
print("submitted:", job["id"])
# 2) 轮询
deadline = time.time() + 300
while time.time() < deadline:
s = requests.get(job["polling_url"], headers=H, timeout=15).json()
print("status:", s["status"])
if s["status"] == "succeeded":
with open("out.png", "wb") as f:
f.write(requests.get(s["unsigned_urls"][0], timeout=60).content)
print("saved out.png")
break
if s["status"] in ("failed", "cancelled", "expired"):
raise SystemExit(s.get("error") or s["status"])
time.sleep(5)
else:
raise SystemExit("polling timeout")TypeScript(Bun / Node 20+)
const API = "https://api.therouter.ai";
const KEY = process.env.THEROUTER_API_KEY!;
const H = { Authorization: `Bearer ${KEY}` };
const submit = await fetch(`${API}/v1/images/generations?async=true`, {
method: "POST",
headers: { ...H, "Content-Type": "application/json" },
body: JSON.stringify({
model: "openai/gpt-image-2",
prompt: "白色大理石桌上一颗极简风格的红苹果",
}),
});
if (!submit.ok) throw new Error(`submit failed: ${submit.status}`);
const job = await submit.json();
console.log("submitted:", job.id);
const deadline = Date.now() + 300_000;
while (Date.now() < deadline) {
const s = await (await fetch(job.polling_url, { headers: H })).json();
console.log("status:", s.status);
if (s.status === "succeeded") {
const buf = await (await fetch(s.unsigned_urls[0])).arrayBuffer();
await Bun.write("out.png", buf);
break;
}
if (["failed", "cancelled", "expired"].includes(s.status)) {
throw new Error(s.error?.message ?? s.status);
}
await new Promise((r) => setTimeout(r, 5_000));
}图像编辑 — 同样的形态
编辑接口用 multipart,但其余流程完全一致:?async=true 提交、拿到 polling_url、 轮询、下载。
curl -X POST "https://api.therouter.ai/v1/images/edits?async=true" \ -H "Authorization: Bearer $THEROUTER_API_KEY" \ -F "model=openai/gpt-image-2" \ -F "prompt=把这个场景改成水彩画风格" \ -F "size=1024x1024" \ -F "image=@input.png"
现在可用的图像模型
openai/gpt-image-2—— 最新(2026-04-21),支持文本 + 图像输入,原生推理openai/gpt-image-1.5—— 速度快、质量稳定的默认选择openai/gpt-image-1—— 生产级稳健款openai/gpt-image-1-mini—— 价格最低,适合缩略图 / 草图google/imagen-4—— Google 旗舰google/imagen-4-ultra—— Google 最高质量档xai/grok-image-1—— xAI 图像模型
所有模型都用同一套「提交 → 轮询 → 下载」流程。 切换模型只需要改一个字符串。响应中的 model 字段始终回显你传入的别名, 我们不会向客户端暴露上游供应商。
轮询节奏、重试和注意事项
- 首次轮询在提交后约 5 秒。
in_progress阶段保持 5–10 秒一次。 超过 60 秒后可放宽到 15–30 秒。硬上限是 300 秒 —— 超过会被服务端标记为expired。 failed一定会带error.message。 只有错误明确属于瞬时类(网络 5xx、reservation 503)时再重试。 不要无脑重试 —— image-2 单张约 9 credits。cancelled和failed都全额退款。 不存在「扣了钱没图」的半中间态。- 超过 7 天的任务会变成
expired,S3 资产被清理。 长期保存需要在窗口期内自己拷贝字节。
底层做了什么
多数调用方不用关心,但简单说:网关把图像任务写进 MySQL(queued 行), 投递到 Redis 上的 BullMQ 队列,由 gateway pod 内嵌的 worker 池消费。 worker 调用对应的 provider service(每个上游一个容器),解析响应, 把字节上传到 S3,把行翻转为 succeeded。 S3 前缀按 tenant 隔离,多租户天然安全。 credits 的预留 / 消费 / 退款是每次状态翻转的原子事务 —— 不存在「钱已扣、图没出」的窗口。
同一套形态也已经覆盖音频和视频:aud_ 开头的音频任务和 vid_ 开头的视频任务 共享同一个 /v1/jobs/:id 轮询接口、同一套取消语义、 同一套计费不变量。一套心智模型,三种模态。
完整指南
全量参数、状态、错误速查表、最佳实践见专门的指南文档:
开始使用
在 dashboard.therouter.ai 获取 API key,从 模型目录 挑一个图像模型, 粘贴上面任意一段代码即可。新账号注册赠送 credits,足够端到端跑几张高质量测试图。
反馈和 bug 报告请发邮件到 hello@therouter.ai。