概览
异步任务式图片生成:创建任务(立即返回 202 和任务 ID)→ 后台队列调用上游生成 → 轮询任务状态 → 通过 file_url 拿结果图。所有写操作都不阻塞等待出图。
基本信息
| 项 | 值 |
|---|---|
| Base URL | http://localhost:8000(自动显示为你当前访问的域名) |
| 鉴权 | X-API-Key: <key> 或 Authorization: Bearer <key>,二者等价 |
| 请求格式 | application/json 或 multipart/form-data(两个创建端点都支持) |
| 时间格式 | ISO 8601 UTC,如 2026-06-30T01:16:08.087Z |
| 错误响应体 | {"error": {"code": 401, "message": "..."}} |
端点总览
| 端点 | 说明 |
|---|---|
| POST /api/v1/tasks/generations | 创建生成任务(带参考图时自动按编辑执行) |
| POST /api/v1/tasks/edits | 创建图片编辑任务(必须至少一张参考图) |
| GET /api/v1/tasks | 列出当前 key 的任务(offset 或游标分页) |
| GET /api/v1/tasks/{id} | 查询任务详情(轮询用) |
| GET /api/v1/tasks/{id}/images/{index} | 鉴权下载结果图 |
| GET /outputs/<name> | 结果图公开静态地址(不可猜文件名) |
| GET /healthz | 健康检查(无鉴权) |
| GET /metrics | Prometheus 指标(无鉴权) |
快速开始
三步:创建任务 → 轮询到终态 → 下载 file_url。创建与轮询必须使用同一个 key(任务按 key 归属隔离)。
curl
# 1) 创建任务,拿到 id
ID=$(curl -s -X POST http://localhost:8000/api/v1/tasks/generations \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"model":"gpt-image-2","prompt":"a red apple","size":"1024x1024"}' \
| python -c "import sys,json;print(json.load(sys.stdin)['id'])")
# 2) 轮询直到 status 变为 succeeded / failed
curl -s -H "X-API-Key: $KEY" http://localhost:8000/api/v1/tasks/$ID
Python · 文生图
import time, requests
BASE, KEY = "http://localhost:8000", "<你的key>"
h = {"X-API-Key": KEY}
r = requests.post(f"{BASE}/api/v1/tasks/generations", headers=h,
json={"model": "gpt-image-2", "prompt": "a red apple", "size": "1024x1024"})
tid = r.json()["id"]
while True:
t = requests.get(f"{BASE}/api/v1/tasks/{tid}", headers=h).json()
if t["status"] in ("succeeded", "failed"):
break
time.sleep(2)
if t["status"] == "succeeded":
url = t["result"][0]["file_url"] # 已是完整 URL,下载无需鉴权
open("out.png", "wb").write(requests.get(url).content)
else:
# 注意:失败时响应里没有 result 键,请勿直接 t["result"]
print("failed:", t["error_status"], t["error"])
Python · 图生图(纯 JSON)
带参考图的请求发到 generations 也可以:服务端检测到图后自动按编辑(edits)执行。参考图可以直接给 URL,也可以给 base64。
r = requests.post(f"{BASE}/api/v1/tasks/generations", headers=h, json={
"model": "gpt-image-2",
"prompt": "给人物加一顶帽子",
"size": "1024x1024",
"image_urls": ["https://cdn.example.com/raw.jpg"], # 或 data:image/...;base64,
})
print(r.json()["endpoint"]) # "edits" —— 带图请求自动按编辑执行
Python · 图生图(multipart 上传本地文件)
# 参考图放任意文件字段(惯用 image[],可多张);其余参数放 data
files = [("image[]", ("raw.jpg", open("raw.jpg", "rb"), "image/jpeg"))]
data = {"model": "gpt-image-2", "prompt": "给人物加一顶帽子", "size": "1024x1024"}
r = requests.post(f"{BASE}/api/v1/tasks/edits", headers=h, files=files, data=data)
Idempotency-Key 请求头重试创建,同一幂等键不会重复入队,见「通用约定 · 幂等创建」。鉴权与任务归属
每个 /api/v1/* 请求都要带 key:请求头 X-API-Key: <key>,或等价的 Authorization: Bearer <key>。
key 如何被接受
按服务端部署配置,以下任一条件满足即可通过鉴权:
| 方式 | 行为 |
|---|---|
| 服务 key | key 在服务端白名单(SERVICE_API_KEYS)内。此时 key 只用于鉴权和任务归属,不会转发给上游;任务用服务端配置的默认上游执行。 |
| 网关用户 key | key 不在白名单,但服务端配置了网关(SUB2API_BASE_URL)。仅对 /api/v1/tasks* 生效:任务经网关执行,你的 key 作为上游 key 计费。 |
| 自带上游 | 请求同时带 X-Upstream-Base-URL 和 X-Upstream-API-Key(或对应 JSON 字段):任务用你自己的上游地址和 key 执行。两者都以请求头提供时可以不带 X-API-Key(任务归属按上游 key 计算);只用 JSON 字段的话仍需带 X-API-Key。详见「创建生成任务 · 自带上游」。 |
都不满足时返回 401(缺 key 或 invalid API key)。开发模式(服务端未配置白名单)下任何非空 key 都被接受;若服务端没有配置自己的上游 key,你的 key 会被转发给上游做鉴权。
任务归属
- 任务按
sha256(key)分组:同一个 key 在任何设备/工具上都能看到同一份任务列表,换 key 查询别人的任务返回404。 - 明文 key 不会出现在任何响应里。
key 的存储安全
失败时的错误信息
上游返回错误时,任务 error 字段写入的是脱敏后的概括语(如 upstream authentication failed),不是上游原始响应;上游 HTTP 状态码写进 error_status。上游返回 401/403 时不重试、直接判失败。完整对照表见「数据结构」。
管理端点
/api/v1/upstreams*(上游 provider 管理)在服务端配置了管理 key(ADMIN_API_KEY)后仅接受该 key,普通业务 key 访问返回 401。普通 API 使用者无需关心。
通用约定
接口(/api/v1/*)的错误响应统一为 {"error": {"code": <数字或稳定错误标识>, "message": "..."}};分辨率校验会返回 resolution_size_mismatch / unsupported_model_resolution,其余常规错误通常使用 HTTP 状态码;静态文件路径(/outputs / /inputs)的 404 为纯文本。
状态码
| 码 | 含义 |
|---|---|
| 202 | 任务已创建并入队(幂等命中时返回既有任务,同样 202) |
| 200 | 查询成功 |
| 400 | 请求非法:body 为空 / JSON 或 multipart 解析失败 / body 超过大小上限(默认 50MB)/ edits 不带参考图 / 参考图超过 16 张 / status 或 cursor 查询参数非法 / 自带上游参数不配对 |
| 401 | 缺少 key,或 key 未通过校验(含管理端点缺管理 key) |
| 404 | 任务不存在、不属于当前 key、任务 ID 非法(三种情况响应完全一致,不泄露存在性);图片 index 越界同样返回 404(文案不同) |
| 405 | HTTP 方法不对(如对创建端点发 GET) |
| 410 | 结果文件已被清理(或该结果从未缓存到本地) |
| 422 | 显式分辨率档位低于目标尺寸要求,或没有匹配当前接口、模型和档位的上游路由 |
| 429 | 创建任务触发限流,带响应头 Retry-After: 60 |
| 500 | 服务端内部错误(统一 internal server error,不泄露细节) |
| 503 | 数据库不可用(/healthz),或管理端点未配置 provider 仓库 |
限流
创建任务(及同步 /v1/images/* 端点)按调用方 key 限流,默认每 key 每分钟 10 次(滑动窗口,部署可调)。超限返回 429 + Retry-After: 60。查询类接口(列表/详情/下载)不限流。
幂等创建
创建任务时带请求头 Idempotency-Key(或 JSON 字段 idempotency_key,头优先,超过 200 字节截断):同一调用方 key 下重复提交相同幂等键时直接返回既有任务的 {id, status, endpoint, created_at}(仍是 202,status 可能已是 running/succeeded/failed),不会重复入队。该字段不会转发给上游。
任务生命周期
pending → running → succeeded / failed
状态说明
| 状态 | 含义 |
|---|---|
| pending | 已入队,等待 worker 认领;重试退避等待期间也会回到 pending |
| running | 某个 worker 正持有任务并调用上游 |
| succeeded | 生成成功,result 含结果图 |
| failed | 重试耗尽或遇到不可重试错误,error / error_status 写明原因 |
重试策略
| 项 | 默认行为 |
|---|---|
| 次数 | 最多重试 3 次(共 4 次尝试,部署可调) |
| 退避 | 指数退避:2s 起每次翻倍,上限 30s |
| 不重试 | 上游 401 / 403(鉴权失败)、请求超时(默认 300s)→ 直接 failed |
| 总是重试 | 上游 429、5xx、网络错误 |
| 其余 4xx | 由部署配置决定(默认重试) |
创建生成任务
?stream=1 可选两个端点都同时接受 application/json 和 multipart/form-data(按 Content-Type 自动识别)。带参考图的请求一律按编辑执行、发往上游 /images/edits——发给 generations 也会自动转;反之 edits 不带图直接 400。所以通常只需记住:有图 = 编辑,无图 = 文生图。
forwarded_request(面板里的「实际上传参数」)中查看。请求字段(JSON)
| 字段 | 类型 | 说明 |
|---|---|---|
| model | string | 模型名,如 gpt-image-2 |
| prompt | string | 提示词(必填) |
| size | string | 宽x高(如 1024x1024),或比例写法(如 16:9,配合 resolution 换算) |
| resolution | string | 可选档位 1k/2k/4k:size 为比例时按档位换算成具体宽高(16 像素对齐,基数 1024/2048/2880)后删除,不发给上游 |
| quality / output_format / n / background … | — | 原样透传,按上游文档为准 |
| stream | bool | true 时 worker 以 Accept: text/event-stream 请求上游(对调用方仍是异步轮询);edits 也可用 ?stream=1 |
| image_urls / reference_images / images | array | 参考图,三种字段名等价,见下节 |
| idempotency_key | string | 幂等键(也可用请求头),不转发上游 |
| upstream_base_url / upstream_api_key | string | 自带上游,见下节,不转发上游 |
1k,1537~2560 为 2k,2561~6144 为 4k,超过 6144 直接拒绝。方向不影响档位,所以 1024x1536 与 1536x1024 都是 1k,2048x1024 是 2k,2880x2880 是 4k。显式 resolution 可以高于尺寸的最低档位,但不能低于它。endpoint + model + resolution_tier 完全匹配时转发,并按该上游路由改写 model。没有匹配项返回 422 unsupported_model_resolution,不会自动降档。route.priority 从高到低执行;相同时才比较上游的默认状态和上游优先级。建议 cjy 的 1k/2k 路由设为 100、goeasy 设为 50;goeasy 的 4k 路由设为 100、cjy 设为 50。两个上游在同一档位都映射到对应的 gpt-image-2-1k / -2k / -4k。主路由出现可重试错误时会切到同档位备用路由,不会降档。宽x高 时,服务端会在 prompt 末尾自动追加一行「【以下是这张图的参数,宽x高,横图/竖图/方图】」(已有该标记则不重复),帮助上游按目标比例出图。参考图规则
- JSON:
image_urls/reference_images/images:[{"image_url":...}]三种写法等价;元素可为 http(s) URL(直接透传上游)、data:image/...;base64,或裸 base64。 - 裸 base64 有门槛:长度 ≥ 256 字符且解码后 ≥ 64 字节,否则该元素被静默忽略。
- multipart:所有文件字段都会被当作参考图收集(字段名不限,惯用
image[]);按文件内容嗅探格式,仅接受 png / jpeg / webp / gif。 - 单任务上限 16 张,超出返回
400。 - base64 / 上传文件会落盘为
/inputs公开 URL 再发上游,最终统一改写为images:[{"image_url":"完整URL"}]。服务本身需以公网地址可达(或部署方配置PUBLIC_INPUT_BASE_URL),上游才拉得到图。
自带上游
可为单个任务指定自己的上游(不走服务端默认上游/网关):
- 请求头
X-Upstream-Base-URL或 JSON 字段upstream_base_url:上游地址。会被规范化——去掉 query/fragment 和结尾的/images/generations|edits,路径为空时自动补/v1。 - 请求头
X-Upstream-API-Key或 JSON 字段upstream_api_key:上游 key。地址与 key 需配对提供:只给 key 不给地址 →400;只给地址不给 key,在配置了服务白名单的部署下也 →400(开发模式下会回落用调用方 key)。 - 生产环境拒绝解析到内网/回环地址的上游。
请求示例
文生图(JSON)
curl -X POST http://localhost:8000/api/v1/tasks/generations \
-H "X-API-Key: <你的key>" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2",
"prompt": "a red apple on a wooden table, studio light",
"size": "1024x1024",
"quality": "high",
"output_format": "png"
}'
图生图(JSON,自动转 edits)
curl -X POST http://localhost:8000/api/v1/tasks/generations \
-H "X-API-Key: <你的key>" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2",
"prompt": "给人物加一顶帽子",
"size": "16:9",
"resolution": "2k",
"image_urls": ["https://cdn.example.com/raw.jpg"]
}'
图片编辑(multipart 上传本地文件)
curl -X POST http://localhost:8000/api/v1/tasks/edits \
-H "X-API-Key: <你的key>" \
-F "model=gpt-image-2" \
-F "prompt=给人物加一顶帽子" \
-F "size=1024x1024" \
-F "image[][email protected];type=image/jpeg"
响应 202
{
"id": "c69550f6-f94f-4e3d-89dc-7cbf5ca429fb",
"status": "pending",
"endpoint": "generations",
"created_at": "2026-06-30T01:16:08.087Z"
}
只有这四个字段(精简摘要,完整字段需再查详情)。endpoint 反映任务实际执行方式:带参考图时为 "edits",即便你调的是 generations 端点。
查询任务
列出任务
仅返回当前 key 的任务,按创建时间倒序。
| 查询参数 | 默认 | 说明 |
|---|---|---|
| status | — | 过滤:pending/running/succeeded/failed,其他值 400 |
| limit | 20 | 1~100;数字越界时回落为 20 但响应回显原始值;非数字直接按 20 处理并回显 20 |
| offset | 0 | 分页偏移 |
| cursor | — | 出现该参数即切换为游标分页,见下 |
offset 模式响应
{
"items": [ { /* TaskOut,见「数据结构」 */ } ],
"total": 42,
"limit": 20,
"offset": 0
}
游标模式(推荐深分页)
首页传空值 ?cursor=,后续页传上一次响应的 next_cursor(不透明字符串)。响应不含 total/offset;next_cursor 为空字符串表示没有更多数据。cursor 非法返回 400。
{
"items": [ ... ],
"limit": 20,
"next_cursor": "eyJjIjoi…"
}
任务详情
轮询此端点直到 status 为终态。任务不存在、不属于当前 key、或 ID 非法 → 统一 404。
响应 200(succeeded 示例)
{
"id": "c69550f6-f94f-4e3d-89dc-7cbf5ca429fb",
"endpoint": "edits",
"status": "succeeded",
"retry_count": 0,
"request_summary": { "endpoint": "edits", "model": "gpt-image-2", "size": "1024x1024",
"prompt_chars": 42, "input_images": 1, "stream": false },
"forwarded_request": { /* 实际发给上游的 JSON(含完整 prompt 与托管图 URL) */ },
"result": [ { "file_url": "https://api.你的域名.com/outputs/3f9a…e7b2.png", "source": "b64_json" } ],
"upstream_response": { /* 脱敏后的上游响应快照(base64 替换为占位符) */ },
"error": null,
"error_status": null,
"created_at": "2026-06-30T01:16:08.087Z",
"started_at": "2026-06-30T01:16:08.096Z",
"finished_at": "2026-06-30T01:16:12.112Z",
"updated_at": "2026-06-30T01:16:12.112Z"
}
failed 示例
{
"id": "…", "status": "failed", "retry_count": 3,
"error": "upstream authentication failed",
"error_status": 401,
...
}
result 键(整体省略,不是 null),upstream_response 同理;request_summary 和 forwarded_request 创建时即写入,各状态下都会返回。取值请用 t.get("result") 之类的安全写法。error / error_status 两个键则始终存在。获取结果图
鉴权下载
?download=1 可选校验任务归属后直出图片字节。index 是 result 数组下标(从 0 起)。?download=1 触发浏览器下载(attachment)。
| 情形 | 返回 |
|---|---|
| index 非数字 / 为负 / 越界,或任务无结果 | 404 |
| 文件已被 TTL 清理,或该结果从未缓存到本地 | 410 |
公开静态地址
即 result[].file_url 指向的地址,无需鉴权,可直接 <img src> 加载。文件名是加密随机的 32 位十六进制串(128 位熵),作为不可猜测的能力令牌。
- 只能精准 URL 访问:请求目录、以
/结尾的路径或不存在的文件一律404,没有目录列举。 /inputs/<name>(参考图托管地址)规则相同。
文件生命周期
/outputs URL 返回 404,鉴权下载端点返回 410。请及时转存需要长期保留的图。另外任务成功后,上传的参考图(/inputs 文件)会被立即删除。特殊情况:若上游直接返回图床 URL 且服务端未回源缓存,file_url 会是上游图床的外部地址(不指向本服务),其有效期由上游决定。
健康检查与监控
健康检查
无需鉴权。数据库不可用时返回 503(status 为 degraded、db 为 down)。
{ "status": "ok", "db": "ok", "queue_pending": 0, "version": "go-rewrite" }
Prometheus 指标
无需鉴权,text/plain Prometheus 文本格式。
| 指标 | 说明 |
|---|---|
| imagegen_queue_pending | 待处理任务数 |
| imagegen_workers_configured | 配置的 worker 并发数 |
| imagegen_tasks_total{status="…"} | 各状态任务总数(pending/running/succeeded/failed) |
数据结构
TaskOut
| 字段 | 类型 | 说明 |
|---|---|---|
| id | uuid | 任务 ID |
| endpoint | string | generations / edits(带图请求自动为 edits) |
| status | string | pending / running / succeeded / failed |
| retry_count | int | 已重试次数 |
| request_summary | object | 请求摘要,见下;空时整个键省略 |
| forwarded_request | object | 归一化后实际发给上游的请求体(含完整 prompt、托管图 URL;已剔除 upstream_base_url / upstream_api_key / idempotency_key);空时省略 |
| result | array | 结果项数组,见下;未成功时整个键省略(不是 null) |
| upstream_response | object | 脱敏后的上游响应快照:base64 数据替换为 <base64 N chars> 占位符;SSE 流式时为 {"stream":true,"images":N};未成功时省略 |
| error | string|null | 失败原因(服务端归一化文案,见下表);键始终存在 |
| error_status | int|null | 失败时的上游 HTTP 状态码(网络错误/超时为 null);键始终存在 |
| created_at / started_at / finished_at / updated_at | string|null | ISO8601 时间戳 |
forwarded_request 含你的完整提示词(以及服务端自动追加的尺寸标注行)。result 项
| 字段 | 类型 | 说明 |
|---|---|---|
| file_url | string | 结果图完整 URL。通常指向本服务 /outputs/<32位hex>.png(按访问域名自动拼全);上游直接回图床 URL 且未回源缓存时为上游外部地址 |
| revised_prompt | string? | 上游改写/扩写后的提示词(若上游返回) |
| source | string? | 结果来源形态:url / b64_json / data_uri |
request_summary
| 字段 | 说明 |
|---|---|
| endpoint | 实际执行端点(generations / edits) |
| model / size / resolution_tier / n / response_format | 按请求出现;size 为换算后的最终值,resolution_tier 为后端解析出的路由档位(resolution 不会转发上游) |
| prompt_chars | 提示词字符数(Unicode 计数),不含明文 prompt |
| input_images | 参考图数量(恒出现) |
| stream | 是否要求上游走 SSE(恒出现) |
error 文案对照
| 上游情形 | error 文案 |
|---|---|
| 401 / 403 | upstream authentication failed |
| 404 | upstream endpoint not found; check upstream Base URL… |
| 429 | upstream rate limit exceeded |
| 超时 | upstream request timed out; increase UPSTREAM_TIMEOUT… |
| 200 但无图 | upstream returned no images |
| 无匹配路由 | no enabled upstream route supports endpoint ..., model ..., resolution ...,任务的 error_status 为 422 |
| 其他 | upstream request failed |