# GPT Image 2 / Sora 2 — API 接入文档

**适配对象**：OpenAI SDK（Python / Node / Go）、LiteLLM、LangChain、one-api、new-api，及任何 OpenAI 兼容客户端。

**版本**：2026-05-22，新增 Sora 2 视频生成端点（`/v1/videos/generations`、`/v1/videos/edits`），已通过 `openai==2.32.0` SDK 完整 8 项 pydantic 校验测试。

---

## 1. 基本信息

### Base URL

```
https://api.img.dengche.cc/v1
```

> 💡 SDK 调用必须用 `api.img.dengche.cc`（DNS 直连源站，绕过 CDN）。`img.dengche.cc` 是浏览器访问的网站入口，CDN 防护会拦截 SDK 的 User-Agent，会报 `403 Your request was blocked`。

### 鉴权

所有接口需要 `Authorization: Bearer <TOKEN>` 请求头。

- **Token 获取**：在本站「个人中心」生成/复制你的 Secret。
- **单 token 有独立积分额度**，超额返 402。
- **Token 无效**：返 401 `invalid_key`。

---

## 2. 模型

支持的模型按 OpenAI 惯例可以用多个别名，行为一致：

### 图像模型

**OpenAI Image 系（默认）**：
| 传入的 `model` | 实际路由到 | 说明 |
|---|---|---|
| `gpt-image-1` | gpt-image-2 | 官方 OpenAI 名，推荐 |
| `gpt-image-2` | gpt-image-2 | 原生名 |
| `dall-e-3` / `dall-e-2` | gpt-image-2 | 兼容老客户端 |

**Google Gemini 3.1（Nano Banana 2）**：
| 传入的 `model` | 实际路由到 | 说明 |
|---|---|---|
| `nano-banana` | nano-banana-3 | 推荐 |
| `nano-banana-2` | nano-banana-3 | Adobe UI 名 |
| `nano-banana-3` | nano-banana-3 | Adobe 内部版本号 |
| `gemini-3-pro` / `gemini-3.1` | nano-banana-3 | Google 名 |

**Google Gemini 2.5（Nano Banana 1，老版本）**：
| 传入的 `model` | 实际路由到 | 说明 |
|---|---|---|
| `gemini-2.5` / `gemini-2.5-flash` | gemini-2.5 | 老版 Nano Banana |
| `nano-banana-1` | gemini-2.5 | 同上 |

**Sora 2 视频**：见 §5.5。

> 💡 其余 Firefly 模型（`flux-kontext-pro` / `imagen-4` / `image5` 等）也都能通过传入精确名字调用，详细清单见 `GET /v1/firefly/models`，但下面的字段说明只对 GPT Image 2 和 Nano Banana 系做了优化（其他模型可能 422，自行试探）。

### 各图像模型的特性对比

| 维度 | `gpt-image-1` (gpt-image-2) | `nano-banana` (nano-banana-3) |
|---|---|---|
| 厂家 | OpenAI | Google Gemini 3.1 |
| 分辨率 | 1K / 2K / 4K（最大 8.29MP） | 256 - 4096，13 种比例 |
| 画质档位 | `low` / `medium` / `high` | **无**（传 quality 会 422） |
| 参考图（图生图） | ✅ 最多 6 张 | ✅ 最多 6 张 |
| 平均速度 | medium ~10s，high ~30s | ~10-20s |
| 风格 | 写实、商品、海报均匀 | 设计、插画、文字渲染强 |

---

## 3. `POST /v1/images/generations` — 图像生成

纯文本生成，最常用的接口。

### 请求

```http
POST /v1/images/generations
Authorization: Bearer <TOKEN>
Content-Type: application/json
```

### 请求字段

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|:---:|---|---|
| `model` | string | 是 | — | `gpt-image-1` / `nano-banana` 或同等别名（见第 2 节） |
| `prompt` | string | 是 | — | 文本描述 |
| `n` | integer | | 1 | 生成数量，1–10 |
| `size` | string | | `"auto"` | `"1024x1024"` / `"1024x1536"` / `"1536x1024"` / `"auto"` |
| `quality` | string | | `"auto"` | `"low"` / `"medium"` / `"high"` / `"auto"`。Nano Banana 不支持 quality 概念，反代会自动忽略；GPT Image 2 才生效。 |
| `response_format` | string | | 见下 | 见"响应格式"小节 |

**接受但不处理的字段**（传了不报错，值被忽略）：

| 字段 | 说明 |
|---|---|
| `output_format` | OpenAI 的 `png`/`jpeg`/`webp` — 当前输出固定 png |
| `output_compression` | JPEG 压缩等级 0-100 — 忽略 |
| `moderation` | `auto`/`low` — 忽略（服务端有自己的审核） |
| `user` | 终端用户 id — 透传忽略 |

**扩展字段**（OpenAI 无，用于高级场景）：

| 字段 | 类型 | 说明 |
|---|---|---|
| `aspect_ratio` | string | `"wide"` / `"square"` / `"tall"` / `"3:2"` / `"4k-wide"` 等，优先级高于 `size` |
| `style` | string | 逗号分隔的风格预设 id |
| `background` | bool | **异步长任务模式**，详见第 5 节。`true` 时立即返回 `job_id`，SDK 用 `extra_body` 传 |

### 画质说明

| `quality` | 说明 |
|---|---|
| `low` / `medium` / `auto` | 日常场景够用，速度快 |
| `high` | 最精细，慢（约 30% 超过 100s），池满时返 503 `upstream_saturated` |

### 响应格式

**重要**：默认行为对齐 OpenAI 真实 `gpt-image-1`：

- **没传 `response_format`** → 返回 `b64_json`（SDK 默认路径）
- **显式传 `response_format: "url"`** → 返回 `url`（预签名 URL，1 小时有效）
- **显式传 `response_format: "b64_json"`** → 返回 `b64_json`

### 响应（成功）

`200 OK`

```json
{
  "created": 1777007464,
  "data": [
    { "b64_json": "iVBORw0KGgoAAA..." }
  ],
  "model": "gpt-image-2",
  "usage": {
    "input_tokens": 0,
    "output_tokens": 0,
    "total_tokens": 0,
    "input_tokens_details": { "text_tokens": 0, "image_tokens": 0 },
    "credits_charged": 1
  }
}
```

- `data[]`：1 到 n 个项，每项是 `{b64_json: "..."}` 或 `{url: "https://..."}`
- `usage.credits_charged`：本次扣除的积分

### 响应（错误）

`4xx / 5xx`

```json
{ "detail": "[<status> <code>] <message>" }
```

**常见错误码**：

| HTTP | `code`（detail 里） | 含义 |
|---|---|---|
| 400 | `bad_body` | 请求体 JSON 不合法 |
| 400 | `unknown_model` | `model` 字段不认识 |
| 400 | `missing_prompt` | 缺 prompt |
| 401 | `invalid_key` | Bearer token 无效 / 过期 / 未传 |
| 402 | `insufficient_credits` | 本 token 积分不够 |
| 422 | `bad_size` | 尺寸不合规（要求每边 16 的倍数、最长边 ≤ 4096、比例 ≤ 3:1、总像素 655k–8.3M） |
| 422 | `quality_not_entitled` | `quality=high` 请求但资源不可用 |
| 503 | `upstream_saturated` | `quality=high` 请求，但资源池满，**稍后重试或降到 medium**。`Retry-After` 头会指示建议等待秒数 |
| 504 | `submit_network` / `timeout` | 上游网络异常或超时 |

---

## 4. `POST /v1/images/edits` — 参考图编辑

用一张或多张参考图 + prompt 生成新图。

### 请求

```http
POST /v1/images/edits
Authorization: Bearer <TOKEN>
Content-Type: multipart/form-data
```

### Multipart 字段

| 字段 | 类型 | 必填 | 说明 |
|---|---|:---:|---|
| `image` | file | 是 | 参考图，重复该字段名可传多张（最多 6 张）。也接受 `image[0]` `image[1]` ... |
| `prompt` | string | 是 | 编辑指令 |
| `model` | string | | 同 generations |
| `n` | integer | | 生成数量 |
| `size` | string | | |
| `quality` | string | | |
| `response_format` | string | | 同 generations（SDK 默认 b64_json） |
| `reference_usage` | string | | `"subject"` / `"composition"` / `"style"`，默认 `"subject"` |
| `mask` | file | | **接受但忽略**（当前不支持） |
| `output_format` / `output_compression` / `moderation` / `user` | | | 接受并忽略 |

### 响应

和 generations 一致：

```json
{
  "created": 1777007464,
  "data": [{ "b64_json": "..." }],
  "model": "gpt-image-2",
  "usage": { ... }
}
```

---

## 5. 长任务：`background: true` 异步模式

> **什么时候用**：当前主 URL 无 100s 超时，一般情况下**不需要**走异步。仅当你自己的客户端 / 反向代理有 100s 响应限制时，才用 `extra_body={"background": True}` 提交后轮询。

### 使用方法（OpenAI SDK）

```python
from openai import OpenAI
import time, base64

client = OpenAI(
    base_url="https://api.img.dengche.cc/v1",
    api_key="<your-token>",
    timeout=10.0,      # submit 阶段很快
)

# 1. 提交，立即拿到 job_id
submit = client.images.generate(
    model="gpt-image-1",
    prompt="photorealistic 4K landscape, mountain reflection in lake at sunset",
    quality="high",
    n=1,
    extra_body={"background": True},
)
# submit.id == "job_abc123..."
# submit.status == "queued"
job_id = submit.id

# 2. 轮询，直到 succeeded / failed
while True:
    job = client._client.get(f"/v1/images/jobs/{job_id}").json()
    if job["status"] == "succeeded":
        image_b64 = job["result"]["data"][0]["b64_json"]
        break
    if job["status"] == "failed":
        raise RuntimeError(job["error"]["message"])
    time.sleep(2)

raw_png = base64.b64decode(image_b64)
```

### 提交后的 Job 对象

```json
{
  "id": "job_19dbd83b3b6_f4bdc319",
  "object": "image.job",
  "status": "queued",
  "created": 1777007000,
  "updated": 1777007000,
  "request": {
    "endpoint": "/v1/images/generations",
    "model": "gpt-image-2",
    "prompt_preview": "photorealistic 4K ...",
    "size": "3840x2160",
    "n": 1,
    "price": 5
  }
}
```

### 轮询 `GET /v1/images/jobs/{id}`

```json
{
  "id": "job_...",
  "object": "image.job",
  "status": "succeeded",
  "created": 1777007000,
  "updated": 1777007180,
  "completed": 1777007180,
  "request": { ... },
  "result": {
    "created": 1777007180,
    "data": [{ "b64_json": "..." }],
    "model": "gpt-image-2",
    "usage": { ... }
  }
}
```

- **失败时**：`status: "failed"`，`error: { "message": "..." }`
- **Job 保留 1 小时**，之后 GC 返 404
- 轮询建议 2–3 秒一次

---

## 5.5. Sora 2 视频生成

模型 ID 用 `sora-2`（也接受 `sora` / `sora2` / `sora-2-pro` 等别名，全部路由到同一个 Adobe 后端 Sora 2 模型）。

> **配额机制**：Adobe 给每个免费账号 **2 次** Sora 2 调用额度，独立于图像生成的 `firefly_limited_taste` 池。也就是说，图像生成额度已用完（被标记为 exhausted / disabled）的账号，**它的 Sora 2 额度仍然可用**。反代会优先挑还有剩余 sora2 额度的账号。
>
> **耗时**：4 秒视频约 60–90 秒，12 秒视频约 2–3 分钟。强烈建议异步轮询模式（`background: true` 或 OpenAI 风格端点的轮询）以免触发客户端超时。
>
> **音频**：Adobe 默认开启音频，输出 MP4 自带音轨。
>
> **分辨率**：固定 720p，可选 16:9（1280×720）或 9:16（720×1280）。
>
> **帧率**：固定 24 FPS。

### 两套接口可选 —— 建议优先使用 OpenAI 标准

| 接口风格 | 路径 | 适配场景 |
|---|---|---|
| **OpenAI Sora 官方规范**（推荐） | `POST /v1/videos` / `GET /v1/videos/{id}` / `GET /v1/videos/{id}/content` | **new-api（type=55 Sora 渠道）**、官方 OpenAI Python/Node SDK、任何按 OpenAI 文档对接的客户端 |
| 自家旧规范（仿 image API） | `POST /v1/videos/generations` / `POST /v1/videos/edits` / `GET /v1/videos/jobs/{id}` | 老客户端、本站前端，2026-04-22 之前的部署文档；新项目不建议用 |

两套都共用同一个底层 Adobe Sora 2 池、同一份计费（5 积分/视频）、同一份配额。下面 §5.5.1–§5.5.4 是 **OpenAI 标准**；§5.5.5 是旧规范保留参考。

### 5.5.1 `POST /v1/videos` — 创建视频（OpenAI 标准）

OpenAI Sora API 的标准提交入口。所有字段都走 multipart/form-data，**包括纯文本调用**——这是 OpenAI 自己的协议要求（因为支持 `input_reference` file 字段）。

```http
POST /v1/videos
Authorization: Bearer <TOKEN>
Content-Type: multipart/form-data
```

#### Multipart 字段

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|:---:|---|---|
| `prompt` | string | 是 | — | 文本描述 |
| `model` | string | | `sora-2` | 模型 ID |
| `seconds` | string | | `"4"` | OpenAI 规范用字符串：`"4"` / `"8"` / `"12"`。也接收 int |
| `size` | string | | `1280x720` | `1280x720` 或 `720x1280` |
| `input_reference` | file | | — | 首帧 JPEG（图生视频）。等价别名：`image` / `image_first` / `first_frame` |
| `negative_prompt` | string | | 内置 | 负面提示词（非 OpenAI 字段，本站扩展） |
| `seed` | integer | | 随机 | 复现用 |

#### 响应（OpenAI Sora video 对象）

提交后立即返回 video 对象，**状态从 `queued` 开始**：

```json
{
  "id": "job_19e5...",
  "object": "video",
  "created_at": 1779388675,
  "status": "queued",
  "progress": 0,
  "model": "sora-2",
  "size": "1280x720",
  "seconds": "4",
  "quality": "standard",
  "remixed_from_video_id": null,
  "error": null
}
```

`status` 枚举（**对齐 OpenAI 真实 Sora API**）：
- `queued`：排队中
- `in_progress`：正在生成
- `completed`：生成完成，可下载
- `failed`：失败，看 `error.message`

完成后字段会多出 `expires_at`（unix 秒，预签名 URL 失效时间，约 created_at + 1h）。

### 5.5.2 `GET /v1/videos/{id}` — 查询状态

```bash
curl https://api.img.dengche.cc/v1/videos/job_19e5xxx \
  -H "Authorization: Bearer <token>"
```

返回同一个 video 对象，`status` 反映当前进度。建议每 5 秒轮询一次直到 `status === "completed"` 或 `"failed"`。

### 5.5.3 `GET /v1/videos/{id}/content` — 下载 MP4

视频生成完成后，从这里拿真实的 MP4 字节流。

```http
GET /v1/videos/{id}/content[?variant=video]
Authorization: Bearer <TOKEN>
```

- `variant`：仅 `video` 可选（Adobe 不暴露 spritesheet / thumbnail），可省略
- 响应：`Content-Type: video/mp4`，body 是 MP4 二进制
- 状态码：
  - `200`：拿到视频
  - `404 video_not_found`：id 无效或已被 GC（>1h）
  - `425 video_not_ready`：还没生成完，继续轮询
  - `400 video_failed`：生成失败

服务端会代理拉取 Adobe 预签名 URL 再吐给你，对客户端来说就是直接下载 MP4。

### 5.5.4 `DELETE /v1/videos/{id}` — 取消 / 忘记

OpenAI 规范要求；本站实现是 no-op（Adobe 不支持中途取消）。返回 `{"id":..., "object":"video.deleted", "deleted":true}`。

### 5.5.5 旧规范（保留兼容）—— `POST /v1/videos/generations` + `/v1/videos/jobs/{id}`

```http
POST /v1/videos/generations
Authorization: Bearer <TOKEN>
Content-Type: application/json
```

#### 请求字段

| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|:---:|---|---|
| `model` | string | | `"sora-2"` | 模型 ID（`sora-2` / `sora` / `sora2` / `sora-2-pro`，全部映射到 sora-2） |
| `prompt` | string | 是 | — | 文本描述 |
| `duration` | integer | | `4` | 时长（秒），可选 `4` / `8` / `12` |
| `aspect_ratio` | string | | `"16:9"` | `"16:9"` / `"9:16"` / `"wide"` / `"tall"` |
| `size` | string | | — | 显式分辨率，目前只接受 `"1280x720"` 或 `"720x1280"` |
| `negative_prompt` | string | | 内置 | 负面提示词，默认 `"cartoon, vector art, & bad aesthetics & poor aesthetic"` |
| `seed` | integer | | 随机 | 随机种子，复现用 |
| `background` | bool | | `false` | 异步任务模式（**强烈推荐 `true`**，否则同步等待 60–180s） |
| `response_format` | string | | `"url"` | 当前固定返回 `url`（视频文件 base64 太大不实用） |

#### 同步响应（`background: false`）

```json
{
  "created": 1779388754,
  "data": [
    {
      "url": "https://pre-signed-firefly-prod.s3-accelerate.amazonaws.com/images/<uuid>?...",
      "format": "mp4"
    }
  ],
  "model": "sora-2",
  "duration": 4,
  "size": "1280x720",
  "fps": 24,
  "usage": {
    "credits_charged": 100,
    "credits_remaining": 9900
  },
  "account": "<adobe-account-uid>"
}
```

- `data[0].url`：1 小时有效的预签名 MP4 URL（约 1.5–4 MB / 4 秒）
- `duration` / `size` / `fps`：实际输出参数
- `usage.credits_charged`：本次扣除的积分。**扁平定价：每个视频 5 积分**，不区分时长（4 / 8 / 12 秒同价；Adobe 那边每号配额是按"调用次数"计的，不按秒）

#### 异步响应（`background: true`，推荐）

提交后立即返回 Job 对象：

```json
{
  "id": "job_19e4bd475ce_a987a2eb",
  "object": "image.job",
  "status": "queued",
  "created": 1779388675,
  "request": {
    "endpoint": "/v1/videos/generations",
    "model": "sora-2",
    "prompt_preview": "a calm ocean wave...",
    "duration": 4,
    "size": "1280x720",
    "price": 100
  }
}
```

然后轮询 `GET /v1/videos/jobs/{id}`（同 `/v1/images/jobs/{id}` 的语义），直到 `status == "succeeded"`，再从 `result.data[0].url` 取视频地址。

### 5.5.6 旧规范 · `POST /v1/videos/edits` — 图生视频（首帧）

用静态图片作为视频的起始帧，配合 prompt 生成动态视频。Adobe 实测**仅接受 JPEG 格式**（PNG 会被拒）。

> ⚠️ **关于「末帧」**：经查 Adobe Firefly 前端 model registry（`supportsFirstFrame: true, supportsLastFrame` 未声明），**sora-2 只支持首帧，不支持末帧**。强行同时上传首+末两帧会被 Adobe 拒掉：`400 Only one reference image with usage 'general' is supported to guide video generation`。反代会提前在 422 层拦下并返 `last_frame_unsupported`。如果未来 Adobe 接入 Pika 2.2（前端写了 `supportsFirstFrame + supportsLastFrame`）我们再开放双帧。

> ⚠️ **重要约束**：参考帧的像素尺寸必须**严格等于**目标视频的分辨率（`16:9` → 1280×720，`9:16` → 720×1280）。Adobe 会拒绝尺寸不符的图片并返回 `400 Inpaint image must match the requested width and height`。请在客户端用 Pillow / sharp / ImageMagick 先缩放到目标分辨率再上传。

```http
POST /v1/videos/edits
Authorization: Bearer <TOKEN>
Content-Type: multipart/form-data
```

#### Multipart 字段

| 字段 | 类型 | 必填 | 说明 |
|---|---|:---:|---|
| `image` / `image[0]` / `image_first` / `first_frame` | file | 是 | **首帧** JPEG（Adobe 字段 `promptReference: 1`） |
| `image[1]` / `image_last` / `last_frame` | file | 否 | **末帧 JPEG —— sora-2 当前不支持，反代会返 422**。预留给将来接入支持 last frame 的模型（如 Pika 2.2） |
| `prompt` | string | 是 | 视频内容指令 |
| `model` | string | | 同上 |
| `duration` | integer | | `4` / `8` / `12` |
| `aspect_ratio` | string | | `"16:9"` / `"9:16"` |
| `size` | string | | 显式分辨率 |
| `negative_prompt` | string | | |
| `seed` | integer | | |
| `background` | bool | | 同步阻塞或异步轮询 |

> 只给末帧不给首帧会返 `400 missing_first_frame`；给 sora-2 上传末帧会返 `422 last_frame_unsupported`。

响应结构与 5.5.1 相同，额外多一个 `reference_blobs` 字段（Adobe 内部 blob ID 数组，调试用）。

### 5.5.7 代码示例

#### Python · OpenAI 标准路径（推荐，与 new-api 完全兼容）

```python
import requests, time

BASE = "https://api.img.dengche.cc/v1"
KEY  = "<your-token>"
H = {"Authorization": f"Bearer {KEY}"}

# 1. 提交（multipart，即使纯文生也是 multipart——OpenAI 协议要求）
files = {
    "prompt":  (None, "a calm ocean wave at sunset, cinematic"),
    "model":   (None, "sora-2"),
    "seconds": (None, "4"),
    "size":    (None, "1280x720"),
}
v = requests.post(f"{BASE}/videos", headers=H, files=files, timeout=30).json()
print("submitted:", v["id"], v["status"])   # status=queued

# 2. 轮询 GET /v1/videos/{id} 直到 completed / failed
while True:
    time.sleep(5)
    v = requests.get(f"{BASE}/videos/{v['id']}", headers=H, timeout=10).json()
    print(v["status"], v.get("progress"))
    if v["status"] == "completed": break
    if v["status"] == "failed":
        raise RuntimeError(v.get("error", {}).get("message"))

# 3. 下载 MP4（服务端代理拉取，直接给你字节流）
mp4 = requests.get(f"{BASE}/videos/{v['id']}/content", headers=H).content
with open("out.mp4", "wb") as f:
    f.write(mp4)
```

#### Python · OpenAI 标准 · 图生视频（首帧）

```python
# 首帧 + prompt → 视频。首帧 JPG 必须正好是目标分辨率（1280×720 或 720×1280）
with open("frame.jpg", "rb") as fp:
    files = {
        "prompt":          (None, "the scene comes to life, gentle motion"),
        "model":           (None, "sora-2"),
        "seconds":         (None, "4"),
        "size":            (None, "1280x720"),
        "input_reference": ("frame.jpg", fp, "image/jpeg"),
    }
    v = requests.post(f"{BASE}/videos", headers=H, files=files, timeout=30).json()
print(v["id"])
```

#### new-api（QuantumNous/new-api）整体接入指南

不同模型在 new-api 里要配成不同的 **Channel Type**。已对照 new-api 源码（`constant/channel.go` + `relay/channel/*/`）逐一验证：

| 模型 | new-api Channel Type | Base URL | Model 字段 | 端点 |
|---|---|---|---|---|
| **GPT Image 2** | `1` OpenAI | `https://api.img.dengche.cc` | `gpt-image-1` | `/v1/images/generations` `/v1/images/edits` |
| **Nano Banana 2** | `1` OpenAI（**推荐**） | `https://api.img.dengche.cc` | `nano-banana` 或 `nano-banana-3` | `/v1/images/generations` `/v1/images/edits` |
| **Sora 2 视频** | `55` Sora | `https://api.img.dengche.cc` | `sora-2`（或 `sora-2-pro`） | `/v1/videos` `/v1/videos/{id}` `/v1/videos/{id}/content` |

**通用要点**：
- Base URL **不带** `/v1` 后缀，new-api 会自己拼路径
- API Key 用本站个人中心生成的 Secret
- 模型名透传：你在 new-api 里设的 model 字段会原样下发到本站，本站做映射（`gpt-image-1` → gpt-image-2、`nano-banana` → nano-banana-3 等）

**为什么 Nano Banana 不走 Channel Type 24 (Gemini)**？
- new-api 的 Gemini 渠道只对 `imagen-*` 模型走 `/v1/images/generations` 路径，其他 Gemini 系列走 `/v1beta/models/{model}:generateContent` 这套原生 Gemini chat 协议
- 本站是 OpenAI 兼容反代，没实现 Gemini 原生协议；Channel Type 1 (OpenAI) 透传 model 名就够用了

**Sora 2 路径全通**（已对照 new-api `relay/channel/task/sora/adaptor.go` 验证）：
- `POST /v1/videos`：**支持 multipart 和 JSON 两种 Content-Type**（new-api 视情况转发）
- 状态枚举：`queued / in_progress / completed / failed`（与 new-api 的 status map 完全对齐）
- `seconds` 字段：接受字符串 `"4"` 或整数 `4` 都行
- `progress`：整数 0-100
- `GET /v1/videos/{id}/content`：服务端代理拉 Adobe 预签名，返回 `Content-Type: video/mp4`
- 额外实现 `POST /v1/videos/{id}/remix`（返 501，因为 Adobe 后端不支持 remix）和 `GET /v1/videos`（返空 list）以避免 new-api 路由 404

#### Python · 旧规范（如需对接老客户端）

```python
import requests, time

BASE = "https://api.img.dengche.cc/v1"
KEY  = "<your-token>"

# 1. 提交，立即拿到 job_id
resp = requests.post(
    f"{BASE}/videos/generations",
    headers={"Authorization": f"Bearer {KEY}"},
    json={
        "model": "sora-2",
        "prompt": "a calm ocean wave breaking on a sandy beach at sunset, cinematic",
        "duration": 4,
        "aspect_ratio": "16:9",
        "background": True,
    },
    timeout=10,
).json()
job_id = resp["id"]
print("submitted:", job_id)

# 2. 轮询
while True:
    j = requests.get(f"{BASE}/videos/jobs/{job_id}",
        headers={"Authorization": f"Bearer {KEY}"}, timeout=10).json()
    if j["status"] == "succeeded":
        url = j["result"]["data"][0]["url"]
        print("done:", url)
        break
    if j["status"] == "failed":
        raise RuntimeError(j.get("error", {}).get("message", "unknown"))
    print("status:", j["status"])
    time.sleep(5)

# 3. 下载 MP4
mp4 = requests.get(url).content
with open("out.mp4", "wb") as f:
    f.write(mp4)
```

#### Python · 同步图生视频

```python
import requests

resp = requests.post(
    "https://api.img.dengche.cc/v1/videos/edits",
    headers={"Authorization": "Bearer <token>"},
    files={"image": ("ref.jpg", open("ref.jpg", "rb"), "image/jpeg")},
    data={
        "prompt": "the scene comes to life with gentle motion",
        "duration": 4,
        "aspect_ratio": "16:9",
    },
    timeout=300,  # 同步模式必须给足时间
).json()

print(resp["data"][0]["url"])
```

#### curl · 文生视频（异步）

```bash
# submit
JOB=$(curl -sS https://api.img.dengche.cc/v1/videos/generations \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"model":"sora-2","prompt":"a cat dancing","duration":4,"aspect_ratio":"16:9","background":true}' \
  | jq -r .id)
echo "job: $JOB"

# poll
while true; do
  STATE=$(curl -sS "https://api.img.dengche.cc/v1/videos/jobs/$JOB" \
    -H "Authorization: Bearer <token>" | jq -r .status)
  echo "$STATE"
  [ "$STATE" = "succeeded" ] && break
  [ "$STATE" = "failed" ] && exit 1
  sleep 5
done

# download
URL=$(curl -sS "https://api.img.dengche.cc/v1/videos/jobs/$JOB" \
  -H "Authorization: Bearer <token>" | jq -r .result.data[0].url)
curl -sS "$URL" -o video.mp4
```

### 5.5.8 错误码

| HTTP | `code` | 含义 |
|---|---|---|
| 400 | `missing_prompt` | 缺 prompt |
| 400 | `unknown_model` | model 字段无法识别 |
| 400 | `missing_image` | edits 端点没传 `image` 字段 |
| 401 | `invalid_key` | Bearer token 无效 |
| 402 | `insufficient_credits` | 账户积分不足 |
| 422 | `bad_duration` | 时长不在 `[4, 8, 12]` 之内 |
| 422 | `bad_size` | 分辨率不是 `1280x720` 或 `720x1280` |
| 503 | `request_timeout` | 同步模式下生成超时；改用 `background: true` |
| 504 | `submit_network` | 上游 Adobe 网络异常 |

---

## 6. 辅助接口

### `GET /v1/health` — 健康检查

无需鉴权。

```json
{
  "status": "ok",
  "accounts_active": 636,
  "total_credits": 56340
}
```

---

## 7. 代码示例

### Python · 标准 OpenAI SDK（b64_json 默认）

```python
from openai import OpenAI
import base64

client = OpenAI(
    base_url="https://api.img.dengche.cc/v1",
    api_key="<token>",
    timeout=120.0,
)

resp = client.images.generate(
    model="gpt-image-1",
    prompt="a cat sitting on a windowsill at golden hour",
    n=1,
    size="1024x1024",
    quality="medium",
)

with open("out.png", "wb") as f:
    f.write(base64.b64decode(resp.data[0].b64_json))
```

### Python · 显式要 URL（省带宽省内存）

```python
resp = client.images.generate(
    model="gpt-image-1",
    prompt="...",
    response_format="url",
)
url = resp.data[0].url   # 预签名 URL，1h 内有效
```

### Python · Nano Banana 2（Google Gemini 3.1）

```python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.img.dengche.cc/v1",
    api_key="<token>",
    timeout=120.0,
)

# 关键点：
#   - model 用 "nano-banana"（或 nano-banana-2 / gemini-3-pro / nano-banana-3）
#   - 不要传 quality，会被 Adobe 拒（Nano Banana 没有画质档）
#   - extra_body 可以传 aspect_ratio 走 Adobe 13 种比例（4:5、5:4、8:1 等）
resp = client.images.generate(
    model="nano-banana",
    prompt="vintage Japanese poster, neon lettering, ukiyo-e meets bauhaus",
    n=1,
    size="1024x1024",
    extra_body={"aspect_ratio": "4:5"},  # 可选
)

with open("nb.png", "wb") as f:
    import base64
    f.write(base64.b64decode(resp.data[0].b64_json))
```

> ⚠️ Nano Banana 没有 `quality` 维度。OpenAI SDK 默认会传 `quality="auto"` 进 `extra_body`/body，**反代会在调 Adobe 前剥掉**，所以 SDK 默认调用是安全的。但如果你显式 `quality="high"` 这种字符串就会被 Adobe 422。

### Node.js · OpenAI SDK

```javascript
import OpenAI from "openai";
import fs from "fs";

const client = new OpenAI({
  baseURL: "https://api.img.dengche.cc/v1",
  apiKey: "<token>",
});

const resp = await client.images.generate({
  model: "gpt-image-1",
  prompt: "a serene mountain lake at dawn",
  n: 1,
  quality: "medium",
});

fs.writeFileSync("out.png", Buffer.from(resp.data[0].b64_json, "base64"));
```

### curl · 最小化请求

```bash
curl https://api.img.dengche.cc/v1/images/generations \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-1",
    "prompt": "a red apple on a white background",
    "n": 1
  }' | jq -r '.data[0].b64_json' | base64 -d > apple.png
```

### LiteLLM

```python
import litellm

resp = litellm.image_generation(
    model="gpt-image-1",
    prompt="...",
    api_base="https://api.img.dengche.cc/v1",
    api_key="<token>",
)
```

### new-api / one-api 聚合器

在管理后台配置 channel：
- Type: OpenAI
- Base URL: `https://api.img.dengche.cc`
- API Key: `<token>`
- Model: `gpt-image-1`

---

## 8. 最佳实践

1. **默认用 b64_json** —— 不要显式传 `response_format: "url"`，除非你就想让客户端直接从预签 URL 拉图（省服务端带宽）
2. **quality 从低到高探**：`low` → `medium` → `high`，能满足效果就不要用 `high`（慢 + 成本高）
3. **遇到 503 `upstream_saturated`**：资源池满，**等 60 秒再试**或降到 `medium`
4. **遇到 402 `insufficient_credits`**：你的 token 积分用完，去「充值」页补
5. **请求 HQ 4K 时**：用 `extra_body={"background": True}` 走异步，避免 100s 问题
6. **生产应用设置合理 timeout**：同步模式 120s，异步 submit 阶段 10s，轮询每次 30s
7. **幂等性**：重复相同 prompt 不会有缓存，每次都真实计费

---

## 9. 常见问题

**Q：为什么我收到的不是 `url` 而是一大串 base64？**
A：这是标准 OpenAI `gpt-image-1` 行为。加 `response_format="url"` 显式要 URL。

**Q：4K 图老 524/超时怎么办？**
A：当前主 URL 已经没有 100s 硬限制。如果你客户端自己设了 timeout，延长它（120–180s）即可；或用 `extra_body={"background": True}` 异步轮询。

**Q：`prompt` 会不会被过滤？**
A：服务端有审核，敏感内容可能被拒（返 451 `image_unsafe`）。换 prompt 重试即可。

**Q：一个 token 能多少 RPM？**
A：token 侧没硬限制，后端按资源池整体约束。实际并发瓶颈在上游。

**Q：失败了还扣积分吗？**
A：不扣。只有 `status=200` 的成功响应才扣。

**Q：URL 模式返回的图片多久失效？**
A：预签名 URL 约 60 分钟。超过后下载会 403。建议收到 URL 后尽快下载存档。

---

## 10. 联系

- 技术 / 充值问题：登录后到「个人中心」
- 服务状态：<https://api.img.dengche.cc/v1/health>

**SDK 用户无需改代码**（OpenAI 兼容层保持向后兼容）。服务端扩展字段（`aspect_ratio` / `background`）可能新增，不会破坏现有请求。
