图像接口概览
发一句提示词,等一两分钟,拿到一张图。
调用一次是什么样子?
一次 HTTP 请求换一张图。没有轮询、webhook、任务 id。
✍️发提示词POST 一个 JSON
→
⏳等 50–110 秒连接挂着
这是正常的
这是正常的
→
🖼️图在响应里直接取
同步接口:返回时图已经生成完。
先知道这三件事
50–110秒
出一张图要等这么久,2K / 4K 更慢。这是正常的,不是卡住了。网关、代理、Serverless 超时都放宽到分钟级,别让用户干等。
1 张
一次请求出一张。
n 参数不存在,要 4 张就发 4 次,单独计费。∞
Node 内置的
fetch 没有默认超时。不传 signal: AbortSignal.timeout(...),上游一卡住就永久挂着:不报错、不返回、连接不释放,直到并发占满、服务僵掉。每个 fetch 都要传,包括下载图那次。两个端点
地址只有一个 https://api.tutujin.com/v1,模型固定 gpt-image-2。OpenAI 兼容 Images API,现成 SDK 换 base URL 和 key 即可。
POST
https://api.tutujin.com/v1/images/generations
POST
https://api.tutujin.com/v1/images/edits
| 用途 | 端点 | 请求体 |
|---|---|---|
| 文生图(纯提示词) | /v1/images/generations |
application/json |
| 图生图(带参考图 / 改图) | /v1/images/edits |
multipart/form-data |
对外口径
对外统一称「土土金图像接口 / Tutujin Image API」。代码、注释、日志、报错、commit 里不要出现其它服务商域名或品牌名。
鉴权
一个 header,没别的握手。
Authorization: Bearer <TUTUJIN_API_KEY>Key 形如 sk-xxxxxxxx,找管理员要。放环境变量,不要硬编码。
export const TUTUJIN = {
baseUrl: 'https://api.tutujin.com/v1',
apiKey: process.env.TUTUJIN_API_KEY, // 绝不硬编码,绝不进 git
model: 'gpt-image-2',
timeoutMs: 300_000, // 5 分钟。正常 50–110s,2K/4K 更久
};
动手前先确认 .env 真的被 git 忽略了
Key 写进 .env,跑 git check-ignore -v .env 确认:有输出才算忽略。Key 一旦推上去就得整体轮换,所有在用它的服务都要跟着改。
五分钟跑通第一张图
先用一条命令分清「代码写错」还是「链路不通」。Node 20+ 整行复制:
TUTUJIN_API_KEY=你的key node -e "
fetch('https://api.tutujin.com/v1/images/generations', {
method:'POST',
headers:{Authorization:'Bearer '+process.env.TUTUJIN_API_KEY,'Content-Type':'application/json'},
body:JSON.stringify({model:'gpt-image-2',prompt:'a red apple',size:'1024x1024',quality:'high',response_format:'url'}),
signal:AbortSignal.timeout(300000)
}).then(async r=>console.log(r.status, (await r.text()).slice(0,500)))
"
它会安静地跑一两分钟 —— 那是在出图,不是卡住 —— 然后打印状态码和一段 JSON。
| 打印出来的东西 | 说明 |
|---|---|
200 + 一段带 data 的 JSON | 链路全通,可以开始写正式代码 |
401 / 403 | key 不对、没权限,或者环境变量没读到 |
402 | 余额不足,找管理员 |
命令直接抛 TimeoutError | 300 秒都没返回,见错误处理与避坑 |
一个能直接用的最小示例
比「能跑」多做三件事:设超时、非 2xx 带出响应正文、两种返回形式都兼容。缺一件出问题都难查。
const BASE = 'https://api.tutujin.com/v1';
const KEY = process.env.TUTUJIN_API_KEY;
export async function textToImage({ prompt, size = '1024x1024' }) {
const resp = await fetch(`${BASE}/images/generations`, {
method: 'POST',
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-image-2',
prompt, // 建议英文;中文也能理解,英文更稳
size, // 必须是 "宽x高" 像素串,而且永远要传
quality: 'high',
response_format: 'url', // 期望返回 URL,但也可能给 b64_json
}),
signal: AbortSignal.timeout(300_000), // ← 少了这行会永久挂住
});
// 非 2xx 一定要把响应正文带出来,否则后面无从判断是审核拦截还是参数错
if (!resp.ok) {
const text = await resp.text();
throw new Error(`图像接口返回 ${resp.status}: ${text.slice(0, 500)}`);
}
const json = await resp.json();
const d = json?.data?.[0];
if (!d) throw new Error('响应里没有 data');
// 两种返回形式都要无条件兼容,别赌只会返回其中一种
if (typeof d.b64_json === 'string' && d.b64_json) {
return Buffer.from(d.b64_json, 'base64');
}
if (typeof d.url === 'string' && /^https?:\/\//.test(d.url)) {
// 这个 URL 是临时的,拿到当下就下载转存
const r = await fetch(d.url, { signal: AbortSignal.timeout(60_000) });
if (!r.ok) throw new Error(`下载生成图失败: ${r.status}`);
return Buffer.from(await r.arrayBuffer());
}
throw new Error('data[0] 里既没有 url 也没有 b64_json');
}
尺寸只认像素串
✕ 这么传
size"size": "16:9"不报错,悄悄给你一张 2048×2048 的方图。省略也一样:图生图会塌成 1024×1024。
✓ 只认宽×高
"size": "1536x864"像素串,如 "1536x864",而且每次都要传。
返回的两种形式
📥收到 200取
取不到就报错
data[0]取不到就报错
→
?两种可能
都要接住
url 或 b64_json都要接住
→
💾立刻转存存到自己的存储
别赌它只返回其中一种。
返回的
url 存活以小时计域名也不固定。拿到当下就下载转存,别入库当长期地址、别直接发给终端用户、也别把域名写死进代码。
接下来看哪一页
正式接入前先扫一遍避坑页
最难缠的不是报错,是不报错:多参考图可能只有第一张生效,照常 200、照常计费。错误处理与避坑列全了。