文档中心内部

图像接口概览

土土金图像接口是一套 OpenAI 兼容的 Images API,模型 gpt-image-2,一个文生图端点、一个图生图端点。这一页讲清开工前必须知道的前提,并带你跑通第一张图。

这是什么

协议形态是 OpenAI 兼容的 Images API —— 你手上任何能调 OpenAI Images 的代码或 SDK,把 base URL 和 key 换掉基本就能直接跑。接口地址只有一个:https://api.tutujin.com/v1,模型名固定 gpt-image-2

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 message 里出现其它服务商的域名或品牌名,也不要把响应里返回的图片 URL 的域名写死进代码。

鉴权

每个请求带一个 header 就行,没有别的握手步骤:

Authorization: Bearer <TUTUJIN_API_KEY>

Key 形如 sk-xxxxxxxx找管理员要,文档里不会有。拿到之后放环境变量,不要硬编码进源码:

config.js
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 在 .gitignore 里

Key 写进 .env,然后确认 .env 真的被 git 忽略了。跑一句 git check-ignore -v .env,有输出就说明已忽略;没输出就是没忽略,先补上再继续。Key 一旦跟着 commit 推上去,就得整体轮换,所有在用它的服务都要跟着改。

开工前必须知道的三件事

这三条不是细节,是决定你客户端整体怎么搭的前提。先看完再动手,能省掉后面大半天的排查。

一 · 这是同步接口,没有任务 id

一次请求 = 一张图,请求返回的时候图已经生成完毕了,直接从响应体里取。没有 webhook、没有轮询、没有任务 id、也没有"查询任务状态"这类端点。所以不要按异步任务队列那套去设计客户端 —— 接口里没有那些东西可给你查。

二 · 正常耗时 50–110 秒,2K / 4K 更慢

这是正常水平,不是出故障了。它对架构有几个直接影响:网关、反向代理、前端请求的超时都要放宽到分钟级;Serverless 函数的最长执行时长要够;不要把这个请求直接挂在用户点击后面同步等待,用户会以为页面死了。稳妥的做法是自己这一侧做成异步任务,把等待放到后台。

三 · Node 的 fetch 没有默认超时 —— 这条最容易翻车

Node 内置的 fetch 不带任何默认超时。上游一旦卡住不返回,请求会永久挂在那儿:不报错、不返回、连接不释放,日志里干干净净什么都看不到,直到并发被占满、整个服务跟着一起僵。每一个 fetch 都必须传 signal: AbortSignal.timeout(...) —— 包括后面下载生成图那一次。

五分钟跑通第一张图

在写任何封装之前,先用一条命令确认「key 能用、网络能通、接口有反应」。这一步只花几分钟,能把"是我代码写错了"和"是链路不通"彻底分开。Node 20+ 直接整行复制:

最小可运行验证 · 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 / 403key 不对、没权限,或者环境变量没读到
402余额不足,找管理员
命令直接抛 TimeoutError300 秒都没返回,见错误处理与避坑

一个完整的文生图最小示例

下面这段是可以直接放进项目的形状。它比"能跑"多做了三件事:设了超时、检查了状态码并把响应正文带出来、两种返回形式都兼容。这三样任缺一个,出问题时都会变得很难查。

text-to-image.mjs · Node 20+
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 永远要传,而且只能是 "1536x864" 这样的像素串;传比例别名或者干脆省略,接口不会报错,只会悄悄给你一张不是你要的尺寸的图。第二,响应可能是 url,也可能是 b64_json,两种都得接住。

返回的图片 URL 是临时的

响应里那个 url 存活时间以小时计,域名也不固定。拿到的当下就下载转存到你自己的存储(对象存储或本地磁盘都行),不要存进数据库当长期地址,也不要直接把它发给终端用户 —— 今天入库、明天用户点开就是一张破图。上面的示例已经这么做了。

接下来看哪一页

你要做的事去这里
查两个端点的完整参数、图生图怎么传参考图、尺寸有哪些硬限制 接口参考
哪些错该重试、哪些重试也没用,以及五个"不报错但结果是错的"坑 错误处理与避坑
正式接入前,先扫一遍避坑页

这个接口最难缠的地方不是报错,是不报错size 传了比例别名、多参考图只有第一张生效、n 参数根本不存在 —— 这几种情况都照常返回 200、照常计费,只是结果不对。花十分钟先看完错误处理与避坑,比事后对着一堆漂亮的 200 日志猜半天划算得多。

土土金内部文档 · 本页最后更新 2026-09-09

内容有误或步骤跑不通,请把页面地址和截图发给内部技术支持。