API 文档
画境图像生成开放接口。用一个 API Key 即可在脚本、后端服务或第三方工具里调用文生图与图生图,额度与网页端共用同一个账户。
价格、模型、参数
接模型先看这几张表:价格、扣费、模型、请求参数、1K/2K。单价打开本页后会按当前站点配置刷新。程序里请再调 GET /v1/pricing,不要把数字写死。
文档地址就是 /docs/。本站只出图,视频还没上线。三个模型走同一套参数,不按模型加价。
价格
余额单位是「分」。1 元 = 100 分,所以 0.5 分 = 0.005 元,1 分 = 0.01 元。下面数字是当前站点价格。
| 类型 | 单价(分 / 张) | 折合人民币 | 对应接口 |
|---|---|---|---|
| 文生图 | 1 | 0.01 元 | POST /v1/images/generations |
| 图生图 | 2.5 | 0.025 元 | POST /v1/images/edits |
| 2K高清修复4K(4K 修复加价) | 1.5 | 0.015 元 | POST /v1/images/generations-hd4k 等 |
| 查余额 / 模型 / 流水 / 健康检查 | 0 | 0 元 | 其余接口不扣费 |
扣费:先按 3 张预扣,再按实际张数退回
一次请求可能出多张,系统不能等出完再扣,否则并发会把余额扣成负数。所以是先按最多张数扣,出完再把多扣的退回来。默认最多 3 张。
| 步骤 | 发生什么 | 举例(文生图,实际出 1 张) |
|---|---|---|
| 1. 预扣 | 请求一进来,先扣 单价 × 最多出图张数 |
1 分 × 3 张 = 先扣 3 分 |
| 2. 出图 | 上游实际出了几张,就算几张 | 实际出了 1 张 |
| 3. 结算 | 多扣的立刻退回。响应里的 cost 是实花,balance 是结算后余额 |
退回 2 分,本次实花 1 分 |
| 失败 | 提示词被拒、超时、没节点、进程重启,预扣都会全额退 | 实花 0 分 |
所以流水里一次成功生成会看到两条:一条预扣、一条退差额。把两条加起来才是净花费。
可用模型
请求里的 model 只接受下表 id。三个模型参数相同、价格相同,差别主要在出图像素。不填则用站点默认(当前 gpt-image-2.5)。GPT Image 2.5 当前默认走 Manus 的 low 质量档。
| 请求里写 model | 界面显示名 | 2K 原图大概多大 | 说明 |
|---|---|---|---|
gpt-image-2.5 |
GPT Image 2.5 | 长边约 1920–2560,总像素约 370 万 | 当前 Manus 公共模型名。默认使用 low 质量档。 |
nano-banana-2 |
Nano Banana 2 | 默认 2048 × 2048 PNG | 口语里的 Banana 2 / nano 2。 |
nano-banana-pro |
Nano Banana Pro | 默认 2048 × 2048 PNG | 口语里的 Banana Pro。旧值 nano-banana 会自动转到这里。 |
不要传这些
| 有人会写成 | 结果 | 正确写法 |
|---|---|---|
nano |
报 unsupported_model |
nano-banana-2 或 nano-banana-pro |
gpt image / gpt-image-1.5 |
报错(id 必须完全一致) | gpt-image-2.5 |
sd2 / sd2.5 / Stable Diffusion |
本站没有这些模型 | 改用上表三个之一 |
| 视频、语音、对话 Agent | 还没上线 | 只调用文生图 / 图生图 |
三个模型共用的请求参数
| 参数 | 必填 | 取值 | 说明 |
|---|---|---|---|
prompt |
必填 | 文字,最长 4000 字 | 主体、场景、光线、镜头写清楚。服务端会自动加「生成图片:」前缀,你不用自己加。 |
model |
可选 | gpt-image-2.5 / nano-banana-2 / nano-banana-pro |
不填用站点默认。填了不支持的值直接报错,不会悄悄换成别的模型。 |
ratio |
可选 | 1:1 16:9 9:16 4:3 3:4 |
留空由模型自己决定。这是倾向性引导,不是硬裁切,出图可能略有偏差。 |
resolution |
可选 | 1k / 2k,默认 2k |
见下一张表。传 4k 不会报错,按 2k 出原图。要 4096 请走 2K高清修复4K 接口,不要传 resolution=4k。 |
delivery / n |
仅 4K 接口 | 2k / 4k / both;n 为 1~预扣上限 |
只出现在 POST /v1/images/generations-hd4k 和 /v1/images/edits-hd4k。活动期才开放。 |
| 参考图 | 仅图生图必填 | 文件、data URL 或 https 图片地址,当前固定 1 张 | 只出现在 POST /v1/images/edits。每次请求只能上传 1 张参考图,超过 1 张返回 too_many_reference_images。 |
resolution:1K 和 2K
| 传这个 | 拿到什么 | 格式 | 什么时候用 |
|---|---|---|---|
2k |
原图,约 2K–2.5K | 无损 PNG | 要成品就用这个,也是默认值 |
1k |
长边压到 1024 的预览 | 有损 webp,体积大约是原图的 1/40 | 列表缩略、快速看效果。价格和 2K 相同 |
4k |
已下线(普通出图接口) | — | 上游原生出图就到 2K–2.5K。继续传会回落到 2k |
| 2K高清修复4K | 先出 2K,再按原图比例把长边修到 4096(不改构图) | PNG | 活动期专属,走独立接口,不是 resolution=4k |
各比例大概尺寸(resolution=2k)
GPT Image 2.5(默认 low 档)实测。总像素大致固定,选宽幅不会让你多拿到画面,只是把同样多的像素铺成更宽的形状。Nano Banana 2 / Pro 默认是 2048 × 2048。
| ratio | 2K 原图 | 长边 | 1K 预览 |
|---|---|---|---|
1:1 | 1920 × 1920 | 1920 | 1024 × 1024 |
4:3 | 2176 × 1632 | 2176 | 1024 × 768 |
16:9 | 2560 × 1440 | 2560 | 1024 × 576 |
3:4 | 1632 × 2176 | 2176 | 768 × 1024 |
9:16 | 1440 × 2560 | 2560 | 576 × 1024 |
其它上限
| 限制 | 默认 | 说明 |
|---|---|---|
| 单次最多出图 | 3 | 也是预扣的倍数 |
| 图生图参考图 | 1 | 每次请求固定 1 张 |
| 同时进行的生成任务 | 9999 | 单用户并发,默认 9999 |
| 异步任务排队上限 | 9999 | 排队 + 进行中合计,默认 9999 |
| 单号同时出图 | 1 | 号池有空闲时一号一图;全忙才叠到同一号 |
| 客户端超时 | 320 秒以上 | 同步接口通常 1–5 分钟。异步提交立刻返回,轮询 /v1/images/tasks/:id 即可 |
网页端和 API 共用同一套价格、模型和参数。登录工作台也能直接出图;要接自己的系统,往下看鉴权和接口说明。
概览
所有开放接口都在 /v1 路径下,请求与响应均为 JSON(图生图额外支持 multipart 上传)。接口风格接近 OpenAI Images API,并兼容 CPA、NewAPI、Sub2 常见的 Bearer / x-api-key 鉴权、/v1/chat/completions 图片调用和 /v1beta、/api/v1 路径别名。
中转兼容提示:Base URL 可以填本站根地址、/v1、/v1beta 或 /api/v1;客户端若重复拼接成 /v1/v1/... 也会自动归一化。图片模型仍只支持本文档列出的模型,未知模型不会静默切换。
Base URL
https://your-domain.example/v1本页所有示例里的地址已经自动替换成你当前访问的域名,复制下来就能直接跑,不用再手动改。
接口一览
5 分钟上手
- 创建密钥。登录网页端,进入「API 密钥」标签页,填个备注后点「创建密钥」。密钥形如
mk-加 48 位十六进制字符,只显示这一次,请立刻保存。备注旁边的下拉可以把这把密钥绑定到一个模型,想按模型分开算账就各建一把。 - 确认有额度。调
GET /v1/balance看看余额;没有的话用兑换码或在网页端充值。 - 发第一个请求。下面这条命令会生成一张图并返回可直接下载的 URL。
curl -X POST https://your-domain.example/v1/images/generations \
-H "Authorization: Bearer mk-你的密钥" \
-H "Content-Type: application/json" \
-d '{
"prompt": "一只在草地上奔跑的柴犬,逆光,浅景深,35mm 胶片质感",
"ratio": "16:9",
"resolution": "2k"
}'返回:
{
"ok": true,
"images": ["https://.../a1b2c3.png"],
"resolution": "2k",
"model": "gpt-image-2.5",
"cost": 1,
"image_count": 1,
"balance": 999,
"session_id": "a8f3d2e1"
}生成很慢,这是正常的。单次生成通常 1-5 分钟。同步接口会一直挂着直到出图,客户端超时请设到 320 秒以上。不想阻塞就用异步接口:提交立刻返回任务 id,再按 2-3 秒间隔轮询结果。
鉴权
每个 /v1 请求都要带上 API Key,两种写法任选其一,效果完全相同:
Authorization: Bearer mk-xxxxxxxxxxxxxxxx
x-api-key: mk-xxxxxxxxxxxxxxxx密钥的性质
- 格式为
mk-+ 48 位十六进制字符,共 51 个字符。 - 服务端只保存 SHA-256 摘要,不存明文。密钥丢了没有任何找回途径,只能删掉重建。
- 一个账户可以创建多个密钥,便于按用途区分和单独吊销。列表里只显示前 10 位加掩码,例如
mk-a1b2c3d********。 - 删除(吊销)后立即失效,正在使用它的程序会开始收到
401 api_key_revoked。 - 密钥代表账户全部权限,可以花掉账户余额。不要写进前端代码、不要提交进 Git 仓库。
按模型分开的密钥
创建密钥时可以绑定一个模型,这把密钥之后就只能调那一个模型。用途是把不同模型的流量、花费和调用记录拆开:一把给 GPT Image 2.5,一把给 Nano Banana 2 或 Pro,互不影响,其中一把泄露也只影响一条线。
| 密钥类型 | 请求不带 model | 请求带别的 model |
|---|---|---|
| 不限模型(默认) | 用站点默认模型 | 照请求的模型执行 |
绑定 gpt-image-2.5 | 用 gpt-image-2.5 | 403 model_not_allowed |
绑定 nano-banana-2 | 用 nano-banana-2 | 403 model_not_allowed |
绑定 nano-banana-pro | 用 nano-banana-pro | 403 model_not_allowed |
绑定关系在 GET /v1/me 的 key.model 里能查到;GET /v1/models 对绑定过的密钥只会列出它能用的那一个模型,照着这个列表选模型就不会踩到 model_not_allowed。绑定只能在创建时指定,事后改不了——需要换模型就再建一把。
老密钥(这个功能上线之前创建的)一律算「不限模型」,行为和以前完全一致,不用改代码。
密钥不能用于管理后台。/api/admin/* 只接受管理员的浏览器会话,API Key 无法访问,也无法查看或修改其他用户的数据。
鉴权失败
HTTP/1.1 401 Unauthorized
{ "error": { "code": "invalid_api_key", "message": "无效的 API Key" } }密钥格式对但已被删除时返回的是 api_key_revoked,用来和「打错字 / 用了别站的密钥」区分开。两种都不要重试。
额度与计费
账户余额和所有价格对外统一以「分」为单位(响应里的 balanceUnit: "fen")。1 元 = 100 分。允许出现 0.5 这样的半分,因为内部按半分存储以避免四舍五入误差。
扣费流程
为了防止并发把余额扣成负数,计费分两步:先按最多张数预扣,出完再按实际张数退差额。完整对照表见开头的扣费表。
| 步骤 | 公式 | 举例(文生图 1 分/张,最多 3 张,实际出 1 张) |
|---|---|---|
| 预扣 | 单价 × max_output_images | 先扣 3 分 |
| 结算 | 单价 × 实际张数,多扣的退回 | 退 2 分,实花 1 分 |
响应里的 cost 是实际花费,balance 是结算后的余额。不要看预扣瞬间的余额。
失败一定会全额退款。不管是提示词被拒、没有可用节点、上游超时还是服务端崩溃重启,预扣的额度都会退回。服务启动时会扫描所有未结算的预扣并退还,所以进程被杀掉也不会吞钱。
哪些接口花钱
| 接口 | 计费 |
|---|---|
POST /v1/images/generations | 按文生图单价 × 实际出图张数 |
POST /v1/images/edits | 按图生图单价 × 实际出图张数(通常比文生图贵) |
POST /v1/images/generations-async / edits-async | 与对应同步接口相同;提交时不扣费,真正出图时再预扣 |
POST /v1/images/generations-hd4k / edits-hd4k | 2K 出图单价 × 张数 + 4K 修复加价 × 张数(选只要 2K 时不加修复费)。活动期才开放 |
| 其余所有接口 | 免费,只受速率限制 |
实时单价通过 GET /v1/pricing 或 GET /v1/balance 获取,不要在代码里写死——管理员随时可以调价,站点搞活动时价格还会临时下调。
通用约定
请求
- 除图生图的 multipart 形式外,请求体一律为 JSON,需要带
Content-Type: application/json。 - 普通接口请求体上限 128 KB;
/v1/images/edits与/v1/images/edits-async放宽到 30 MB(要塞 base64 图片)。 - 未知字段会被忽略,不会报错。
成功响应
查询类和同步出图带 ok: true,HTTP 状态码 200。异步提交返回 202,任务成败看响应里的 status。
错误响应
/v1 下的错误结构统一,error 恒为对象,永远有 code 和 message 两个字段:
{
"error": {
"code": "insufficient_credits",
"message": "余额不足:最多需要 3 分,当前 1 分",
"hint": "(可选)仅部分错误会带,给出可操作的修复建议"
},
"balance": 1
}两个生成接口在出错时会额外返回 balance,方便你直接判断是不是余额问题,不用再多发一次查询。
请按 code 判断错误类型,不要匹配 message。message 是给人看的中文提示,措辞会随版本变化;code 才是稳定契约。
时间与金额字段
| 字段形态 | 含义 |
|---|---|
created_at / last_used_at 等 | Unix 毫秒时间戳(整数)。JavaScript 里 new Date(v) 直接可用。 |
cost / balance / delta | 以「分」为单位,可能带一位小数。 |
cost_units / delta_units | 内部半分单位的整数值,一般不用关心。 |
图片 URL 的有效期
返回的是站内代理地址 /img/...,不是上游 CDN 直链。请尽快下载转存到自己的存储;长期引用本站 URL 可能因缓存过期而失效。
速率限制
限流按 API Key 计算(没带密钥时退化为按 IP)。触发后返回 429:
{ "error": { "code": "rate_limited", "message": "API 调用过于频繁,请降低速率" } }| 范围 | 默认额度 | 环境变量 |
|---|---|---|
全部 /v1 接口 | 9999 次 / 分钟 | RL_API |
POST /v1/redeem(叠加) | 30 次 / 10 分钟 | RL_REDEEM |
响应头带标准的 RateLimit-* 字段(draft-7),可以据此做退避。这些额度由部署方在环境变量里配置,自建实例可以自行调高。
被限流挡掉的请求也会记进调用历史,所以到底是哪个时段、哪把密钥撞上了限流,翻 GET /v1/usage 就能看出来,不用自己在客户端埋点。
并发上限
限流之外还有同时进行的生成任务上限,这是另一套机制:
- 单用户默认 9999 路并发生成。实时值见
GET /v1/pricing的limits.max_concurrent_per_user。 - 全站默认 9999 路并发生成。同步接口只有撞上这个天花板才返回
503 busy;异步会留在队列里等空位。 - 异步排队 + 进行中默认最多 9999 个,见
limits.max_open_async_tasks。只有碰到这个数字才会429 too_many_tasks。
批量任务建议走异步接口轮询。实际能同时跑多少,还取决于号池里有多少空闲节点。
文生图
根据文字提示词生成图片。同步接口,等到出图才返回,通常耗时 1-5 分钟。
请求参数(JSON)
| 参数 | 类型 | 说明 |
|---|---|---|
prompt 必填 |
string | 图片描述。描述越具体结果越稳定,建议写清主体、场景、光线、镜头和风格。服务端会自动加上「生成图片:」前缀以确保走生图模式,你不需要自己加。最长 4000 字,超了返回 prompt_too_long。 |
ratio 可选 |
string | 画面比例,可传 1:1、16:9、9:16、4:3、3:4 或任意合法的 宽:高。服务端会把比例作为生成意图传给上游,并在响应的 requested_ratio / normalized_ratio 中分别返回请求值和上游实际三档画幅。 |
size / output_size / dimensions 可选 |
string | 兼容 CPA、NewAPI、Sub2 常见字段,例如 1008x1344。服务端接受 64–16384 的宽高且总像素不超过 64MP;上游原生仍只有 1024x1024、1536x1024、1024x1536,不会把任意尺寸伪装成原生输出。响应会带 requested_size、normalized_size、upstream_size。 |
resolution 可选 |
string |
输出分辨率,默认 2k,非法值静默回落到 2k。
4k 已下线:上游原生出图就到 2K-2.5K,那一档只是让 CDN 把同一张原图重新编码后发一遍,尺寸一个像素都没变、还多了一道有损压缩,画质反而比 2k 差。继续传 4k 不会报错,会回落到 2k,也就是拿到更好的那一张。
|
model 可选 |
string | 生图模型,可选 gpt-image-2.5、nano-banana-2 或 nano-banana-pro。旧值 gpt-image / gpt-image-2.0 / image2.0 会自动兼容到 gpt-image-2.5;旧值 nano-banana 会当作 nano-banana-pro。留空使用站点默认模型;填了不支持的值会直接报错而不是回落,避免你以为用上了实际没用上。密钥绑定了模型时留空即用绑定的那个,填别的会返回 403 model_not_allowed。 |
关于比例和自定义尺寸:上游协议没有独立的比例字段,服务端会把请求比例追加到提示词末尾,因此比例是倾向性引导而非硬裁切。当传入 size=1008x1344 这类自定义尺寸时,服务端保留请求意图,同时选择最接近的真实上游画幅;不会返回不存在的 1008x1344 原生输出。
各比例的实际出图尺寸
下面是实测数据(resolution=2k)。注意总像素是固定的,约 370 万——模型按固定像素预算出图,再按比例摊开,所以选宽幅并不会让你多拿到画面,只是把同样多的像素铺成更宽的形状。
| ratio | 原图尺寸 | 长边 | 总像素 | 1k 档对应尺寸 |
|---|---|---|---|---|
1:1 | 1920 × 1920 | 1920 | 369 万 | 1024 × 1024 |
4:3 | 2176 × 1632 | 2176 | 355 万 | 1024 × 768 |
16:9 | 2560 × 1440 | 2560 | 369 万 | 1024 × 576 |
3:4 / 9:16 | 对应横版的转置 | 1632 / 1440 | 同上 | 768 × 1024 / 576 × 1024 |
上面是 GPT Image 2.5 的 low 档实测。Nano Banana 2 / Pro 默认原生 2048 × 2048 PNG。提示词里明确写 4K / 4096 时,交付文件可以到 4096×4096,但模型侧仍按 2048 出图,多半是沙箱放大,不是模型原生 4K。换算成印刷尺寸,按 300 DPI 计算长边约 16~21 厘米,做屏幕内容、社媒图和网页配图绰绰有余;要出大幅面海报则需要自行做超分。
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
ok | boolean | 恒为 true |
images | string[] | 图片直链数组,顺序即生成顺序。至少一张,否则本次会按失败处理并退款。 |
resolution | string | 实际使用的分辨率 |
model | string | 实际使用的模型 id |
cost | number | 本次实际扣费(分),已按出图张数结算完毕 |
image_count | number | 出图张数,等于 images.length |
balance | number | 结算后的账户余额(分) |
session_id | string | 本次生成的会话标识,排查问题时可提供给管理员 |
示例
curl -X POST https://your-domain.example/v1/images/generations \
-H "Authorization: Bearer mk-你的密钥" \
-H "Content-Type: application/json" \
--max-time 330 \
-d '{
"prompt": "极简主义产品摄影:一只哑光黑陶瓷咖啡杯放在浅灰水泥台面上,柔和顶光,阴影干净,高级商业质感",
"ratio": "1:1",
"resolution": "2k",
"model": "gpt-image-2.5"
}'可能的错误
| 状态码 | code | 含义与处理 |
|---|---|---|
| 400 | missing_prompt | prompt 为空或只有空白字符 |
| 400 | prompt_too_long | prompt 超过 4000 字,不扣费 |
| 400 | unsupported_model | model 不在支持列表里,先查 /v1/models |
| 400 | prompt_rejected | 提示词未通过内容审核,不扣费。响应带 hint 说明如何修改 |
| 401 | invalid_api_key | 密钥不存在 |
| 401 | api_key_revoked | 密钥已被删除,重新创建一把 |
| 402 | insufficient_credits | 余额不足以覆盖预扣金额,先充值 |
| 403 | model_not_allowed | 这把密钥绑定的是别的模型,不扣费。响应带 hint 指出该换哪把密钥 |
| 429 | rate_limited | 超出速率限制,退避后重试 |
| 429 | too_many_requests | 你的并发生成数已达上限,等前面的任务完成 |
| 503 | no_channel | 暂无空闲生成节点,已全额退款,稍后重试 |
| 503 | busy | 全站并发已满,已全额退款,稍后重试 |
| 500 | generation_failed | 上游生成失败或超时,已全额退款 |
内容审核
提示词在扣费和调用上游之前会先过一遍本地检查,命中直接拒绝,不产生任何费用。规则刻意收得很窄,需要两个独立信号同时出现才会命中,正常创作类提示词不会被误伤。拦截的是:涉及未成年人的性化内容、真实人物的裸露内容、露骨色情、武器爆炸物制作方法、毒品制作方法,以及站点管理员自定义的禁用词。
{
"error": {
"code": "prompt_rejected",
"message": "提示词未通过内容审核(露骨色情内容)",
"hint": "提示词可能触发了内容审核。建议去掉真人姓名、品牌标识、暴力或成人相关描述后重试。"
},
"balance": 1000
}本地检查放行不代表一定能出图,上游还有自己的审核。上游拒绝时同样返回 prompt_rejected,同样不扣费。
图生图
带参考图生成新图片。同步接口,耗时与文生图相当。支持 multipart 文件上传和 JSON 两种传图方式。
方式一:multipart/form-data(推荐)
直接上传本地文件,不用做 base64 编码,请求体也小得多。
| 字段 | 类型 | 说明 |
|---|---|---|
image 必填 | file | 参考图文件。字段名也可以用 images 或 image[],效果相同。 |
prompt 必填 | string | 希望如何改动或参考这张图 |
ratio / resolution / model 可选 | string | 与文生图完全一致 |
curl -X POST https://your-domain.example/v1/images/edits \
-H "Authorization: Bearer mk-你的密钥" \
--max-time 330 \
-F "image=@./reference.png" \
-F "prompt=保持人物姿势不变,把背景换成黄昏海滩,增加暖色边缘光" \
-F "resolution=2k"方式二:JSON
适合图片来自远程 URL 或已经是 base64 的场景。以下字段任选一个:
| 字段 | 类型 | 说明 |
|---|---|---|
image | string | data URL,形如 data:image/png;base64,iVBORw0... |
image_url | string | 公网可访问的图片 URL,服务端会代为下载 |
images | string[] | 多张 data URL(受参考图数量上限约束) |
image_urls | string[] | 多张图片 URL(同上) |
filename / filenames 可选 | string / string[] | 指定上传文件名,不填会自动生成 |
mime 可选 | string | 指定 MIME 类型,一般不需要,服务端会从图片内容嗅探 |
curl -X POST https://your-domain.example/v1/images/edits \
-H "Authorization: Bearer mk-你的密钥" \
-H "Content-Type: application/json" \
--max-time 330 \
-d '{
"image_url": "https://example.com/reference.jpg",
"prompt": "转换成水彩插画风格,保留构图",
"ratio": "4:3"
}'参考图数量当前固定为 1 张。每次图生图请求只能上传 1 张参考图,超过 1 张会返回 too_many_reference_images。
图片要求
- 支持 PNG、JPEG、WebP、GIF。服务端按文件头嗅探真实类型,改扩展名骗不过去。
- 单张不超过 20 MB;JSON 方式整个请求体不超过 30 MB。
- 用
image_url时目标必须是公网地址。指向127.0.0.1、内网段、云元数据地址(169.254.169.254)等一律被拒绝,这是防 SSRF 的硬性限制,DNS 解析后的真实 IP 也会检查。
响应字段
比文生图多三个字段,其余相同:
| 字段 | 类型 | 说明 |
|---|---|---|
mode | string | 恒为 "image_to_image" |
reference_count | number | 实际使用的参考图数量 |
finalText | string | 模型返回的文字说明,可能为空字符串 |
{
"ok": true,
"mode": "image_to_image",
"images": ["https://.../edited.png"],
"finalText": "已将背景替换为黄昏海滩。",
"resolution": "2k",
"model": "gpt-image-2.5",
"reference_count": 1,
"cost": 2.5,
"image_count": 1,
"balance": 996.5,
"session_id": "b7c1e9f4"
}额外的错误
除文生图那张表里的全部错误外,还可能出现:
| 状态码 | code | 含义 |
|---|---|---|
| 400 | missing_image | 没有提供任何参考图 |
| 400 | too_many_reference_images | 参考图数量超过 max_reference_images |
| 400 | invalid_image | 不是可识别的图片格式、下载失败,或 URL 指向内网地址 |
| 400 | LIMIT_FILE_SIZE | 单张参考图超过 20 MB |
| 413 | payload_too_large | JSON 请求体超过 30 MB |
异步出图
参数与同步文生图完全相同,但请求立刻返回任务 id,不等出图。适合 HTTP 超时较短、或要一次提交多张的对接方。
没有 webhook。提交后请轮询 GET /v1/images/tasks/:id,建议间隔 2-3 秒,直到 status 变成 succeeded 或 failed。图生图对应 POST /v1/images/edits-async,multipart / JSON 字段与同步图生图相同。
curl -X POST https://your-domain.example/v1/images/generations-async \
-H "Authorization: Bearer mk-你的密钥" \
-H "Content-Type: application/json" \
-d '{"prompt": "一只在草地上奔跑的柴犬", "resolution": "2k"}'HTTP 202:
{
"ok": true,
"id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
"status": "queued",
"kind": "text",
"model": "gpt-image-2.5",
"ratio": "",
"resolution": "2k",
"created_at": 1730000000000,
"started_at": null,
"finished_at": null
}| 字段 | 说明 |
|---|---|
id | 任务 id,后续查询用它 |
status | queued 排队 / running 出图中 / succeeded 成功 / failed 失败 |
kind | text 文生图,edit 图生图 |
提交时只做参数校验、内容审核和余额软检查,不预扣。真正占并发槽和预扣发生在任务开始跑的时候,规则与同步接口相同。失败全额退款。
每个账号同时处于 queued + running 的任务默认最多 9999 个。并发生成默认 9999 路;槽满或暂时没有空闲节点时,任务留在队列,不会因此标失败。
| 状态码 | code | 含义 |
|---|---|---|
| 400 | missing_prompt 等 | 与同步接口相同的参数/审核错误,不会创建任务 |
| 402 | insufficient_credits | 提交时余额已不够覆盖预扣 |
| 429 | too_many_tasks | 该账号未完成的异步任务已达上限 |
查询异步任务
用提交时拿到的 id 查询状态。只能查自己账号的任务,别人的 id 与不存在一样返回 404 not_found。查询本身不扣费。
curl https://your-domain.example/v1/images/tasks/task_xxxxxxxxxxxxxxxxxxxxxxxx \
-H "Authorization: Bearer mk-你的密钥"成功时除任务字段外,还带与同步接口同形的 images(站内 /img/...)、cost、balance、session_id:
{
"ok": true,
"id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
"status": "succeeded",
"kind": "text",
"model": "gpt-image-2.5",
"ratio": "",
"resolution": "2k",
"created_at": 1730000000000,
"started_at": 1730000000500,
"finished_at": 1730000120000,
"images": ["/img/xxxxxxxx"],
"cost": 1,
"image_count": 1,
"balance": 997,
"session_id": "g_a1b2c3d4e5f6g7h8"
}失败时 HTTP 仍是 200,看 status: "failed" 和 error:
{
"ok": true,
"id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
"status": "failed",
"kind": "text",
"error": {
"code": "no_channel",
"message": "服务暂时不可用(暂无空闲生成节点),已退还余额"
}
}进程重启后:进行中的任务会标为 interrupted;排队中的文生图会接着跑;图生图参考图只存在内存里,重启后排队中的图生图会变成 task_expired,需要重新提交。
2K高清修复4K
先用生图模型出 2K 原图,再经 Manus Agent 沙箱按原图宽高比把长边放大到 4096(不改构图/取景,不走图生图再生图)。仅在 Manus 活动期开放,活动结束接口返回 503 feature_unavailable。
计费 = 2K 出图单价 × 实际张数 + 4K 修复单价 × 实际修复张数(选「只要 2K」时不收修复费)。4K 修复默认 0.015 元/张(1.5 分)。号池未满时一号一图;号池全忙才允许同一号叠跑(上限见 max_inflight_per_token,当前 1)。
| 字段 | 类型 | 说明 |
|---|---|---|
prompt 必填 | string | 同文生图 |
ratio / model 可选 | string | 同文生图 |
delivery 可选 | string | 2k 只要 2K · 4k 只要 4K · both 都要(默认) |
n 可选 | number | 预扣/期望张数,1~max_output_images,默认取上限 |
curl -X POST "{BASE}/v1/images/generations-hd4k" \
-H "Authorization: Bearer mk-xxx" \
-H "Content-Type: application/json" \
-d '{"prompt":"赛博朋克城市夜景,写实","ratio":"16:9","delivery":"both","n":1}'{
"ok": true,
"mode": "hd4k",
"images": ["https://.../4k.png"],
"images_2k": ["https://.../2k.png"],
"images_4k": ["https://.../4k.png"],
"delivery": "both",
"resolution": "hd4k",
"model": "gpt-image-2.5",
"cost": 2.5,
"image_count": 1,
"balance": 997.5,
"session_id": "a1b2c3d4"
}图生图版本:POST /v1/images/edits-hd4k,参数与图生图相同,另加 delivery / n。
查询余额
查询当前余额和实时价格。生成前先调这个可以避免因余额不足白等一次请求。
curl https://your-domain.example/v1/balance \
-H "Authorization: Bearer mk-你的密钥"{
"ok": true,
"credits": 998.5,
"pricing": {
"text": { "halfFen": 2, "fen": 1, "yuan": 0.01 },
"edit": { "halfFen": 5, "fen": 2.5, "yuan": 0.025 },
"upscale4k": null,
"maxOutputImages": 3,
"balanceUnit": "fen",
"promo": false
}
}| 字段 | 说明 |
|---|---|
credits | 当前余额(分) |
pricing.text | 文生图单价。fen 是对外单位,yuan 是元,halfFen 是内部半分单位 |
pricing.edit | 图生图单价,结构同上 |
pricing.upscale4k | 活动期 2K高清修复4K 的 4K 修复加价。非活动期为 null |
pricing.maxOutputImages | 单次最多出图张数,也是预扣的倍数 |
pricing.promo | 站点是否处于活动期。为 true 时价格和各项上限可能临时放宽 |
账户信息
查询密钥所属账户的基本信息。可用来验证密钥是否有效、属于哪个账户。
curl https://your-domain.example/v1/me \
-H "Authorization: Bearer mk-你的密钥"{
"ok": true,
"username": "alice@example.com",
"email": "alice@example.com",
"credits": 998.5,
"credit_unit": "fen",
"created_at": 1754870400000,
"key": { "label": "生产环境-banana", "model": "nano-banana-2" }
}email 在纯用户名注册的账户上为 null。key 描述的是当前这把密钥:model 为 null 表示不限模型,否则就是它绑定的模型。
使用兑换码
用兑换码给当前账户充值。兑换码由管理员发放,一码只能用一次。
请求参数(JSON)
| 参数 | 类型 | 说明 |
|---|---|---|
code 必填 | string | 兑换码。大小写不敏感,首尾空格会自动去掉。 |
curl -X POST https://your-domain.example/v1/redeem \
-H "Authorization: Bearer mk-你的密钥" \
-H "Content-Type: application/json" \
-d '{"code": "FL53HIJDSTQF"}'{ "ok": true, "added": 100, "balance": 1098.5 }| 状态码 | code | 含义 |
|---|---|---|
| 400 | missing_code | code 为空 |
| 400 | invalid_code | 兑换码不存在 |
| 400 | code_already_used | 该兑换码已被使用过 |
| 429 | rate_limited | 兑换过于频繁(防爆破,默认 10 分钟 30 次) |
并发提交同一个兑换码只会有一次成功入账,其余返回 code_already_used,不会重复加钱。
模型列表
列出这把密钥能用的生图模型和默认模型。
{
"ok": true,
"models": [
{ "id": "gpt-image-2.5", "label": "GPT Image 2.5" },
{ "id": "nano-banana-2", "label": "Nano Banana 2" },
{ "id": "nano-banana-pro", "label": "Nano Banana Pro" }
],
"default_model": "gpt-image-2.5",
"key_model": null
}生成接口的 model 参数只接受这里列出的 id。label 是给界面显示用的名字。
key_model 是当前密钥绑定的模型,不限模型时为 null。绑定过的密钥这里只会返回那一个模型,default_model 也跟着变成它:
{
"ok": true,
"models": [ { "id": "nano-banana-2", "label": "Nano Banana 2" } ],
"default_model": "nano-banana-2",
"key_model": "nano-banana-2"
}价格与限制
一次性拿到所有价格和参数上限。写客户端时建议启动时调一次并缓存几分钟,而不是把这些值硬编码。
{
"ok": true,
"pricing": {
"text": { "halfFen": 2, "fen": 1, "yuan": 0.01 },
"edit": { "halfFen": 5, "fen": 2.5, "yuan": 0.025 },
"upscale4k": { "halfFen": 3, "fen": 1.5, "yuan": 0.015, "label": "2K高清修复4K" },
"maxOutputImages": 3,
"balanceUnit": "fen",
"promo": true
},
"limits": {
"max_output_images": 3,
"max_reference_images": 1,
"max_concurrent_per_user": 9999,
"max_open_async_tasks": 9999,
"max_repair_per_token": 1,
"max_inflight_per_token": 1,
"resolutions": ["1k", "2k"],
"ratios": ["1:1", "16:9", "9:16", "4:3", "3:4"],
"upscale4k": true,
"deliveries": ["2k", "4k", "both"]
}
}| 字段 | 说明 |
|---|---|
limits.max_output_images | 单次最多出图张数,预扣金额 = 单价 × 该值 |
limits.max_reference_images | 图生图单次最多 1 张参考图 |
limits.max_concurrent_per_user | 你能同时跑几个生成任务,默认 9999 |
limits.max_open_async_tasks | 异步接口同时处于排队+进行中的任务上限,默认 9999 |
limits.resolutions | resolution 参数的合法取值 |
limits.ratios | ratio 参数的合法取值,另外允许留空 |
limits.upscale4k | 是否开放 2K高清修复4K。为 false 时对应接口返回 503 feature_unavailable |
limits.deliveries | 4K 接口 delivery 的合法取值:2k / 4k / both |
limits.max_inflight_per_token | 每个上游号同时跑的任务上限。号池还有空闲号时一号一图;号池全忙才允许叠到同一号 |
limits.max_repair_per_token | 与 max_inflight_per_token 相同,兼容旧字段,默认 1 |
健康检查
服务存活探针。注意路径不在 /v1 下,也不需要密钥,适合给负载均衡或监控用。
{ "ok": true, "uptime": 86400.5, "inflight": 2 }| 字段 | 说明 |
|---|---|
uptime | 进程已运行秒数 |
inflight | 当前全站正在进行的生成任务数。接近全站上限(默认 9999)时新请求会收到 503 busy |
生成记录
按时间倒序返回本账户的历史生成记录,包含失败的任务。
查询参数
| 参数 | 说明 |
|---|---|
limit 可选 | 返回条数,默认 30,最大 100。非法或超范围的值会回落到默认/上限,不报错。 |
curl "https://your-domain.example/v1/generations?limit=5" \
-H "Authorization: Bearer mk-你的密钥"{
"ok": true,
"generations": [
{
"id": 42,
"prompt": "一只在草地上奔跑的柴犬",
"ratio": "16:9",
"resolution": "2k",
"model": "gpt-image-2.5",
"cost": 1,
"cost_units": 2,
"status": "success",
"session_id": "a8f3d2e1",
"duration_ms": 42318,
"duration_sec": 42.3,
"created_at": 1754870400000,
"images": ["https://.../a1b2c3.png"]
}
],
"speed": {
"count": 40,
"avg_duration_ms": 38200,
"avg_duration_sec": 38.2,
"by_resolution": {
"1k": { "label": "1K", "count": 5, "avg_duration_ms": 12100, "avg_duration_sec": 12.1 },
"2k": { "label": "2K", "count": 30, "avg_duration_ms": 28400, "avg_duration_sec": 28.4 },
"hd4k": { "label": "4K", "count": 5, "avg_duration_ms": 61000, "avg_duration_sec": 61.0 }
}
}
}status 为 "success" 或 "failed"。失败记录的 cost 恒为 0(已退款),images 为空数组。model 是这次实际使用的模型,这个字段上线之前的老记录为 null。duration_sec 是这次出图的耗时(秒,一位小数),从开始分配节点到出图结束;上线前的老记录为 null。speed 是本账户成功出图的平均时速:avg_duration_sec 为全部平均,by_resolution 按 1k / 2k / hd4k(4K)分类。
历史记录里的图片直链大概率已经失效,因为上游 CDN 链接有有效期。这个接口用于对账和审计,不要拿来当图床。
调用历史
返回 /v1 下的逐次调用记录,成功和失败都记。默认只看当前这把密钥,用来核对某一路服务、某一个模型到底调了多少、错在哪。
查询参数
| 参数 | 说明 |
|---|---|
limit 可选 | 返回条数,默认 50,最大 200 |
scope 可选 | 默认 key,只返回当前密钥的记录;传 account 返回本账户所有密钥的记录 |
curl "https://your-domain.example/v1/usage?limit=20" \
-H "Authorization: Bearer mk-你的密钥"{
"ok": true,
"scope": "key",
"summary": {
"days": 7,
"total": 128,
"succeeded": 124,
"failed": 4,
"images": 131,
"cost": 141.5,
"cost_units": 283,
"avg_duration_ms": 38120,
"avg_duration_sec": 38.1,
"by_resolution": {
"1k": { "label": "1K", "count": 8, "avg_duration_ms": 12100, "avg_duration_sec": 12.1 },
"2k": { "label": "2K", "count": 110, "avg_duration_ms": 28400, "avg_duration_sec": 28.4 },
"hd4k": { "label": "4K", "count": 10, "avg_duration_ms": 61000, "avg_duration_sec": 61.0 }
}
},
"calls": [
{
"id": 9012,
"api_key_id": 7,
"api_key": "mk-a1b2c3d********",
"key_label": "生产环境-banana",
"method": "POST",
"path": "/v1/images/generations",
"model": "nano-banana-2",
"status": 200,
"ok": true,
"error_code": null,
"cost": 1,
"cost_units": 2,
"image_count": 1,
"duration_ms": 42318,
"duration_sec": 42.3,
"created_at": 1754870400000
},
{
"method": "POST",
"path": "/v1/images/generations",
"model": "gpt-image-2.5",
"status": 403,
"ok": false,
"error_code": "model_not_allowed",
"cost": 0,
"image_count": 0,
"duration_ms": 3,
"duration_sec": 0,
"created_at": 1754870300000
}
]
}| 字段 | 说明 |
|---|---|
summary | 近 7 天的汇总:调用次数、成功/失败数、出图张数、总花费(分)。平均时速见 avg_duration_sec(全部)和 by_resolution(1K / 2K / 4K) |
ok | HTTP 状态码是否 2xx,等价于 status >= 200 && status < 300 |
error_code | 失败时的 error.code,成功为 null |
model | 本次用的模型;被 model_not_allowed 拒掉时记的是它想调用的那个,方便定位是哪段代码传错了。不涉及模型的接口为 null |
cost | 这次调用的实际花费(分)。不计费的接口恒为 0 |
duration_sec | 服务端处理耗时(秒,一位小数),含等待上游出图的时间。兼容字段 duration_ms 是同一数值的毫秒 |
by_resolution | 成功出图按分辨率分类的平均时速。键为 1k / 2k / hd4k,值为 avg_duration_sec 和 count |
被限流挡掉的 429 也会记进来,所以速率调优可以直接看这里。记录保留 30 天,更早的会被自动清理——需要长期留存请自行定期拉走。
用已删除的密钥发请求同样会记在它名下(401 api_key_revoked),方便定位「哪台机器上还有个旧脚本在跑」。完全不认识的密钥无从归属,不会记录。
额度流水
按时间倒序返回额度变动明细,包括注册赠送、充值、兑换、扣费和退款。
查询参数
| 参数 | 说明 |
|---|---|
limit 可选 | 返回条数,默认 50,最大 200 |
{
"ok": true,
"ledger": [
{ "delta": -3, "delta_units": -6, "reason": "文生图 预扣最多 3 张", "balance_after": 997, "created_at": 1754870400000 },
{ "delta": 2, "delta_units": 4, "reason": "按实际输出张数退回差额", "balance_after": 999, "created_at": 1754870460000 },
{ "delta": 100, "delta_units": 200,"reason": "兑换码充值 FL53HIJDSTQF", "balance_after": 1099, "created_at": 1754870500000 }
]
}delta 为正表示入账,为负表示扣费;balance_after 是该笔变动之后的余额。一次生成通常对应两条记录——一条预扣、一条退差额,这是计费流程的正常表现,不是重复记账。
无限画布
登录网页端后,工作台顶部的「无限画布」标签页。这是一块没有边界的平面,用来把多轮尝试铺开摆在一起看,而不是像生成历史那样挤成一列。它是网页端功能,没有对应的开放接口;画布上出图和在「开始生成」页出图走的是同一套计费,单价一样。
基本操作
| 操作 | 怎么做 |
|---|---|
| 平移 | 在空白处按住拖动 |
| 缩放 | 滚轮或触控板双指,以光标为锚点;也可以点工具栏的 − / +。范围 10%~400% |
| 看全部 | 点「回到中心」,会自动缩放到刚好装下画布上所有图并居中 |
| 全屏 | 点「全屏」铺满整个窗口,Esc 退出 |
| 移动 / 缩放单张图 | 拖图片本体移动;选中后拖右下角的小方块等比缩放。松手才写库,拖动过程不会一直发请求 |
| 删除 | 悬停图片点「删除」,或选中后按 Delete / Backspace |
就地生成
在画布任意空白处双击,原地弹出输入框,填提示词、选模型比例和分辨率,出的图就落在你双击的位置。生成过程中该位置显示进度占位,不阻塞你继续在别处双击排下一张——多个任务可以同时跑(默认并发 9999,见 /v1/pricing 的 limits.max_concurrent_per_user)。一次出多张时会沿横向依次排开,不会叠在一起。
Ctrl(Mac 上 Cmd)加 Enter 可以直接提交,Esc 关掉输入框。
基于已有图片再生成
悬停任意图片,右上角出现「再生成」,点开的输入框会带上这张图作为底图,模型和比例默认沿用原图。这走的是图生图那条路,等价于把原图当参考图重新出一张,但不需要你先下载再上传——服务端本地就存着这张图的字节,直接拿来用。
由此产生的新图会带一个「改自上一张」的角标,点角标会把视野移到它的来源图并高亮。多轮迭代因此会在画布上自然形成一条看得见的链路,这也是画布相对生成历史的主要价值:你能看出第 7 张是从第 3 张分叉出来的。
导入历史图片
点工具栏「导入历史图片」,弹窗里以缩略图列出你最近的生成记录,勾选想要的,或者用「全选」一次带走。已经在画布上的会置灰并标注「已在画布」,不会重复导入。确认后按网格铺在当前视口左上角附近。
图片会过期,记得下载
画布上的图和生成历史引用的是同一批文件,同样受保存期约束(当前 3 天,到期从服务器彻底删除)。到期后画布上对应的卡片会变成「图片已过期」的占位,这是不可恢复的。所以画布适合承载当次或近几天的创作过程,不要当成长期作品库。
悬停图片点「下载」即可存到本地,这是唯一能长期保住图的办法。
另外每个账号的画布最多放 500 张图,超出会提示先删一些。
错误码总表
按 error.code 处理,不要匹配 message。
| 状态码 | code | 含义 | 建议处理 |
|---|---|---|---|
| 400 | missing_prompt | 缺少提示词 | 修正请求 |
| 400 | prompt_too_long | 提示词超过 4000 字 | 截断后重试 |
| 400 | missing_code | 缺少兑换码 | 修正请求 |
| 400 | missing_image | 图生图未提供参考图 | 修正请求 |
| 400 | unsupported_model | 模型不在支持列表 | 查 /v1/models |
| 400 | prompt_rejected | 提示词未通过审核 | 不要重试,改提示词 |
| 400 | invalid_image | 图片无法识别、下载失败或指向内网 | 换一张图或换公网 URL |
| 400 | too_many_reference_images | 参考图超量 | 减少张数 |
| 400 | invalid_code | 兑换码不存在 | 核对兑换码 |
| 400 | code_already_used | 兑换码已使用 | 换一个码 |
| 400 | invalid_json | 请求体不是合法 JSON | 检查序列化 |
| 400 | LIMIT_FILE_SIZE | 单张图片超过 20 MB | 压缩后重传 |
| 401 | invalid_api_key | 密钥不存在 | 不要重试,检查密钥 |
| 401 | api_key_revoked | 密钥已被删除 | 不要重试,重新创建一把 |
| 402 | insufficient_credits | 余额不足 | 不要重试,先充值 |
| 403 | model_not_allowed | 密钥绑定了别的模型 | 不要重试,换对应模型的密钥 |
| 413 | payload_too_large | 请求体超限 | 改用 multipart 上传 |
| 429 | rate_limited | 超出速率限制 | 指数退避后重试 |
| 429 | too_many_requests | 你的并发生成数已满 | 等待在跑的任务完成,或改用异步接口排队 |
| 429 | too_many_tasks | 未完成的异步任务已达上限 | 等已提交的任务结束再提交 |
| 404 | not_found | 异步任务不存在或不属于当前账号 | 核对任务 id 和密钥 |
| 500 | generation_failed | 生成失败,已退款 | 可重试 |
| 500 | internal_error | 服务端内部错误 | 可重试,持续出现请联系管理员 |
| 503 | no_channel | 暂无空闲生成节点,已退款 | 稍后重试 |
| 503 | busy | 全站并发已满,已退款 | 稍后重试 |
| 503 | feature_unavailable | 2K高清修复4K 仅在活动期开放 | 活动结束或换普通出图接口 |
重试建议
- 可以重试:
generation_failed、no_channel、busy、internal_error、rate_limited、too_many_requests。建议指数退避,首次等 5 秒起步。 - 不要重试:所有 400 类错误、
invalid_api_key、api_key_revoked、model_not_allowed、insufficient_credits。重试只会白白消耗速率配额。
完整示例
依赖 requests(pip install requests)。包含超时设置、错误分类和自动重试。
import time
import requests
BASE = "https://your-domain.example"
API_KEY = "mk-你的密钥"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# 生成最长 5 分钟,超时必须给够,否则会掐断服务端还在正常处理的请求
TIMEOUT = (10, 330) # (连接超时, 读取超时)
RETRYABLE = {"generation_failed", "no_channel", "busy", "internal_error", "rate_limited"}
class ApiError(Exception):
def __init__(self, code, message):
super().__init__(f"[{code}] {message}")
self.code = code
def generate(prompt, ratio="", resolution="2k", model=None, max_retries=3):
payload = {"prompt": prompt, "ratio": ratio, "resolution": resolution}
if model:
payload["model"] = model
for attempt in range(max_retries):
response = requests.post(
f"{BASE}/v1/images/generations",
headers={**HEADERS, "Content-Type": "application/json"},
json=payload,
timeout=TIMEOUT,
)
data = response.json()
if response.ok:
return data
error = data.get("error", {})
code = error.get("code", "unknown")
if code not in RETRYABLE or attempt == max_retries - 1:
raise ApiError(code, error.get("message", "未知错误"))
time.sleep(5 * (2 ** attempt)) # 5s, 10s, 20s
def edit(image_path, prompt, **kwargs):
with open(image_path, "rb") as handle:
response = requests.post(
f"{BASE}/v1/images/edits",
headers=HEADERS,
files={"image": handle},
data={"prompt": prompt, **kwargs},
timeout=TIMEOUT,
)
data = response.json()
if not response.ok:
error = data.get("error", {})
raise ApiError(error.get("code", "unknown"), error.get("message", ""))
return data
if __name__ == "__main__":
balance = requests.get(f"{BASE}/v1/balance", headers=HEADERS, timeout=30).json()
print(f"当前余额 {balance['credits']} 分,文生图单价 {balance['pricing']['text']['fen']} 分")
result = generate("雪后的京都清水寺清晨,薄雾,暖色灯笼", ratio="16:9")
print(f"花费 {result['cost']} 分,余额 {result['balance']} 分")
for index, url in enumerate(result["images"]):
image = requests.get(url, timeout=60)
with open(f"output_{index}.png", "wb") as handle:
handle.write(image.content)
print(f"已保存 output_{index}.png")Node.js 18+ 自带 fetch,无需额外依赖。注意必须显式放宽超时,否则默认设置会提前中断。
const fs = require("node:fs");
const BASE = "https://your-domain.example";
const API_KEY = "mk-你的密钥";
const RETRYABLE = new Set(["generation_failed", "no_channel", "busy", "internal_error", "rate_limited"]);
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function call(path, options = {}) {
// 生成最长 5 分钟,给到 330 秒留出余量
const response = await fetch(BASE + path, {
...options,
headers: { Authorization: `Bearer ${API_KEY}`, ...options.headers },
signal: AbortSignal.timeout(330000),
});
const data = await response.json();
if (!response.ok) {
const error = new Error(data.error?.message || "请求失败");
error.code = data.error?.code || "unknown";
throw error;
}
return data;
}
async function generate(prompt, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await call("/v1/images/generations", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt, ...options }),
});
} catch (error) {
if (!RETRYABLE.has(error.code) || attempt === maxRetries - 1) throw error;
await sleep(5000 * 2 ** attempt);
}
}
}
async function edit(imagePath, prompt, options = {}) {
const form = new FormData();
form.append("image", new Blob([fs.readFileSync(imagePath)]), imagePath.split(/[\\/]/).pop());
form.append("prompt", prompt);
for (const [key, value] of Object.entries(options)) form.append(key, value);
return call("/v1/images/edits", { method: "POST", body: form });
}
(async () => {
const { credits, pricing } = await call("/v1/balance");
console.log(`当前余额 ${credits} 分,文生图单价 ${pricing.text.fen} 分`);
const result = await generate("赛博朋克风格的东京街头,雨夜,霓虹倒影", { ratio: "16:9", resolution: "2k" });
console.log(`花费 ${result.cost} 分,余额 ${result.balance} 分`);
for (const [index, url] of result.images.entries()) {
const image = await fetch(url);
fs.writeFileSync(`output_${index}.png`, Buffer.from(await image.arrayBuffer()));
console.log(`已保存 output_${index}.png`);
}
})().catch(error => {
console.error(`失败 [${error.code}]: ${error.message}`);
process.exit(1);
});需要 curl 和 jq。演示生成后直接下载所有图片。
#!/usr/bin/env bash
set -euo pipefail
BASE="https://your-domain.example"
API_KEY="mk-你的密钥"
# 先看余额
curl -sS "$BASE/v1/balance" -H "Authorization: Bearer $API_KEY" | jq
# 生成图片。--max-time 必须大于服务端 5 分钟的生成上限
response=$(curl -sS -X POST "$BASE/v1/images/generations" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
--max-time 330 \
-d '{"prompt": "北欧极简客厅,自然光,原木家具", "ratio": "4:3"}')
if [ "$(echo "$response" | jq -r '.ok // false')" != "true" ]; then
echo "失败: $(echo "$response" | jq -r '.error.code'): $(echo "$response" | jq -r '.error.message')" >&2
exit 1
fi
echo "花费 $(echo "$response" | jq -r '.cost') 分,余额 $(echo "$response" | jq -r '.balance') 分"
# 下载全部结果
echo "$response" | jq -r '.images[]' | while IFS= read -r url; do
filename="output_$(date +%s)_$RANDOM.png"
curl -sS -o "$filename" "$url"
echo "已保存 $filename"
done常见问题
价格、1K/2K、模型参数在哪看?
就在本页最上面的表格。价格、扣费(先扣 3 张再退)、三个模型、请求参数、1K/2K、比例尺寸都在那儿。程序里请再调 GET /v1/pricing 拿实时值。
有没有 SD2、SD2.5,或者视频?
没有。现在只有 gpt-image-2.5、nano-banana-2、nano-banana-pro。旧值 gpt-image 会自动兼容到 gpt-image-2.5。口语里的 nano 请写成完整 id,不要只传 nano。视频还没上线。
三个模型的参数一样吗?价格一样吗?
生图参数一样:prompt / model / ratio / resolution,图生图再加参考图。计费分文生图、图生图,活动期还有 2K高清修复4K 的修复加价。不按模型加价。
2K高清修复4K 怎么调用?活动结束还能用吗?
活动期调 POST /v1/images/generations-hd4k(文生图)或 POST /v1/images/edits-hd4k(图生图)。delivery 选 2k / 4k / both,n 选出图张数。计费 = 2K 出图单价 + 4K 修复加价(只要 2K 时不加修复费)。活动结束这两个接口返回 503 feature_unavailable,普通 1K/2K 出图不受影响。实时价格看开头表格或 GET /v1/pricing 的 pricing.upscale4k。
请求一直卡着不返回,是挂了吗?
大概率没挂。生成是同步的,正常就要 1-5 分钟。请确认客户端读取超时设到了 320 秒以上。如果你在 nginx 之类的反向代理后面,代理的 proxy_read_timeout 也要一起调大,否则会被代理提前掐断。单个节点卡住或一直没进度时,服务端会自动换号重试,不必自己连打。
能异步提交、轮询取结果吗?
可以。调 POST /v1/images/generations-async(文生图)或 POST /v1/images/edits-async(图生图),拿到 id 后按 2-3 秒间隔轮询 GET /v1/images/tasks/:id,直到 status 为 succeeded 或 failed。没有 webhook。同步接口仍然可用,超时请继续设到 320 秒以上。
用 OpenAI / NewAPI 的聊天接口怎么调?
现在支持图片模型的 POST /v1/chat/completions 兼容入口。把文字消息合并成提示词即可文生图;消息里的 image_url、image 部分会进入图生图。接口返回标准 choices,同时附带图片 data 数组。需要更完整的图片参数时,也可以直接调用 /v1/images/generations 或 /v1/images/edits。
为什么请求刚发出去余额就掉了一大截?
那是预扣。默认按最多 3 张先扣,出图后按实际张数把多余的退回来。以响应里的 cost 和 balance 为准。
失败了会退款吗?
会,而且是全额。所有失败路径都退款,包括进程被杀这种极端情况——服务下次启动时会扫描未结算的预扣并退还。
图片链接过一段时间就 403 / 404 了?
返回的是站内代理地址 /img/...,不是上游 CDN 直链。请尽快下载转存到你自己的对象存储;长期引用本站 URL 可能因缓存过期而失效。
ratio 或 size 设了但出图比例不太对?
上游协议没有独立的比例参数,服务端会把请求比例作为提示词意图传过去,并选择最接近的真实上游画幅,因此不是硬裁切。CPA、NewAPI、Sub2 常见的 size、output_size、dimensions 字段都支持,例如 1008x1344;响应会返回 requested_*、normalized_* 和 upstream_* 字段,分别说明请求值、归一化值和真实上游画幅。
图生图能一次传多张参考图吗?
每个图生图任务当前只能上传 1 张参考图;超过 1 张会返回 too_many_reference_images。
密钥能创建几个?弄丢了怎么办?
数量不限,建议按用途分开建,方便单独吊销。明文只在创建时显示一次,服务端只存摘要,丢了无法找回,删掉重建即可。
怎么把几个模型的用量分开统计?
给每个模型各建一把绑定该模型的密钥,然后用 GET /v1/usage 分别看各自的调用次数和花费。这么做还顺带上了一道保险:绑定 nano-banana-2 的密钥不可能因为哪里传错参数就跑去调 gpt-image-2.5。
已经在用的密钥能改绑定的模型吗?
不能,绑定只在创建时确定。这是有意的——中途改绑定会让历史记录变得没法解释:同一把密钥前后调的是两个模型,按密钥做的用量统计就对不上了。换模型请新建一把,旧的确认没人用了再删。
调用历史能留多久?能导出吗?
默认保留 30 天,之后自动清理。要长期留存就定期拉 GET /v1/usage?scope=account 存到自己那边;单次最多返回 200 条,按 created_at 自行去重即可。
API 能管理其他用户或改站点配置吗?
不能。API Key 只能操作它所属的那个账户。管理类接口 /api/admin/* 只认管理员的浏览器会话。
为什么 /v1/ledger 里一次生成有两条记录?
一条是预扣,一条是按实际张数退回差额,属于正常的两阶段记账。把两条加起来才是这次生成的净花费。