Commit dca9de24cc1837860ee48f86aba16bf13b3fa43d

Authored by yibo
1 parent af31f0a8

h3-console: 加完整 API 文档

给要接入 MiniMax-H3 生成能力的其它项目用。覆盖全部接口(提交/查询/
取消/插队/软删除/批量操作/上传/系统状态),字段表、错误码、真实抓取的
响应样例都是当前线上实测得到的,不是照代码抄的猜测。

重点标注几个容易踩坑但不改代码没法从接口本身看出来的地方:
POST /v1/videos 校验失败也返回 202,只能靠 status 字段判断;
turbo/cfg_scale/negative_prompt 目前是 no-op;
停止 running 任务=硬重启整个服务,代价是全局15-20秒不可用;
以及本次事故本身:不要绕过 console 直连 raylight(8100)提交任务。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFw9ZBh3eVjssk6quYQ4Ki
Showing 1 changed file with 441 additions and 0 deletions
h3-console/API.md 0 → 100644
  1 +# H3 Console API 文档
  2 +
  3 +给要接入 MiniMax-H3 文生视频能力的项目用。这是 **console 层**的接口(8080 局域网 / 8081
  4 +公网 `/console` 子路径),不是底层 raylight 的接口。
  5 +
  6 +> ⚠️ **务必通过 console 提交任务,不要绕过 console 直连 raylight(内网 8100 端口)。**
  7 +> raylight 是纯计算队列,没有鉴权、没有任务持久化(进程重启就丢)、没有软删除/回收站,
  8 +> console 才是任务的唯一权威记录(sqlite)。2026-09-05 曾发生过一次事故:外部脚本直连
  9 +> 8100 提交任务,console 的后台发现逻辑和这些直连任务的簿记之间出现竞态,同一个真实生成
  10 +> 任务在 console 的任务列表里出现了两条记录。所有集成都应该只打 console 的 `/v1/*`。
  11 +
  12 +---
  13 +
  14 +## 1. 基础信息
  15 +
  16 +| 项目 | 值 |
  17 +|---|---|
  18 +| 局域网 Base URL | `http://192.168.110.67:8080` |
  19 +| 公网 Base URL | `http://qwen.wudaima.com/console`(frp 反代,注意所有路径都要带 `/console` 前缀) |
  20 +| 内容类型 | 请求/响应都是 `application/json`,上传接口是 `multipart/form-data` |
  21 +| 字符编码 | UTF-8,中文 prompt 直接传,不需要额外转义 |
  22 +| 鉴权 | 见下 |
  23 +
  24 +### 鉴权
  25 +
  26 +`/v1/*` 下所有接口都可能要求 Bearer token(页面本身 `/`、`/task.html/*` 不需要)。两种传法二选一,服务端逻辑完全等价:
  27 +
  28 +```
  29 +Authorization: Bearer <token>
  30 +```
  31 +或者拼在查询串里(给 `<video src>`、`<a download>` 这类浏览器原生请求用,因为它们加不了自定义 header):
  32 +```
  33 +GET /v1/videos/{id}/file?token=<token>
  34 +```
  35 +
  36 +token 是否强制取决于部署时是否设了 `H3_CONSOLE_AUTH_TOKEN` 环境变量;公网实例目前**已启用**,请找服务器管理员要 token。带错/不带 token 会收到:
  37 +
  38 +```json
  39 +// HTTP 401
  40 +{"detail": "缺少或错误的访问令牌(Authorization: Bearer <token> 或 ?token=)"}
  41 +```
  42 +
  43 +---
  44 +
  45 +## 2. 核心概念
  46 +
  47 +**任务 id**:console 生成自己的 12 位十六进制 id(跟底层 raylight 的任务 id 是两个不同的值),所有 `/v1/videos/{id}/*` 接口都用这个 console id,不要用别处看到的 raylight id。
  48 +
  49 +**三种生成模式**,不是显式传的参数,是 console 按你传了哪些字段自动判断的:
  50 +
  51 +| 模式 | 判断依据 |
  52 +|---|---|
  53 +| `text`(文生视频) | 没传 `first_frame_url`/`last_frame_url`,也没传任何 `reference_*` |
  54 +| `fl2va`(首尾帧) | 传了 `first_frame_url` 或 `last_frame_url` |
  55 +| `ref2va`(参考图/参考视频/参考音频) | 传了 `reference_image_urls`/`reference_video_urls`/`reference_audio_urls` 任意一个非空 |
  56 +
  57 +**任务状态机**:`queued → running → done` / `failed` / `cancelled`。另外有一个独立维度 `deleted_at`(软删除/回收站),跟 status 正交——一条 `done` 的任务一样可以被删进回收站,不影响它已经生成好的文件。
  58 +
  59 +**并发模型(重要)**:4 张 GPU 是**一个 FSDP 集群**,不是 4 个独立 worker。任何时刻只有 **1 条**任务处于 `running`,其余全部在队列里排队等这一条跑完。这不是接口限流——可以放心一次提交很多条,它们会老老实实排队,但**不要指望"提交越多并行跑得越快"**,吞吐量就是"一条接一条"。
  60 +
  61 +---
  62 +
  63 +## 3. 接口一览
  64 +
  65 +| 方法 | 路径 | 用途 |
  66 +|---|---|---|
  67 +| POST | `/v1/videos` | 提交生成任务 |
  68 +| GET | `/v1/videos` | 任务列表(分页/筛选/排序) |
  69 +| GET | `/v1/videos/{id}` | 单条任务详情(会顺带触发一次状态刷新) |
  70 +| GET | `/v1/videos/{id}/file` | 下载/播放生成的视频(支持 Range) |
  71 +| GET | `/v1/videos/{id}/comfy_history` | 排查用:原始 ComfyUI 执行记录 |
  72 +| POST | `/v1/videos/{id}/cancel` | 取消排队中的任务 / 停止运行中的任务 |
  73 +| POST | `/v1/videos/{id}/prioritize` | 插队到最前面(仅排队中的任务) |
  74 +| DELETE | `/v1/videos/{id}` | 软删除(移入回收站) |
  75 +| POST | `/v1/videos/{id}/restore` | 从回收站恢复 |
  76 +| POST | `/v1/videos/batch-cancel` | **批量**取消/停止 |
  77 +| POST | `/v1/videos/batch-delete` | **批量**软删除 |
  78 +| POST | `/v1/assets` | 上传参考图/参考视频/参考音频,换取可用的 URL |
  79 +| GET | `/v1/backends` | 健康检查 + 队列深度 + GPU 显存/利用率 |
  80 +| GET | `/v1/models` | 支持的分辨率档位(含实测帧数上限) |
  81 +| POST | `/v1/admin/reset-gpu` | 硬重启 GPU 服务(危险操作,见下) |
  82 +
  83 +---
  84 +
  85 +## 4. 提交生成任务
  86 +
  87 +### `POST /v1/videos`
  88 +
  89 +> ⚠️ **这个接口永远返回 HTTP 202**,哪怕参数校验直接失败也是 202——**必须检查响应体里的
  90 +> `status` 字段**,不能只看 HTTP 状态码判断成功与否。校验失败时 `status` 会是 `"failed"`,
  91 +> `error` 字段里是失败原因(原样透传自底层校验器,格式见下面例子,不算稳定 API,只用来人读)。
  92 +
  93 +#### 请求体字段
  94 +
  95 +| 字段 | 类型 | 默认值 | 说明 |
  96 +|---|---|---|---|
  97 +| `prompt` | string | **必填** | 生成提示词,支持中英文/超长文本 |
  98 +| `width` | int | 832 | 64–2048。跟 `height` 的组合决定能用哪些帧数(见下面「分辨率档位」) |
  99 +| `height` | int | 480 | 同上 |
  100 +| `frames` | int | 124 | 5–3000。总帧数,按 24fps 折算成秒数 |
  101 +| `steps` | int | 20 | 1–50 |
  102 +| `turbo` | bool | false | **当前是 no-op**,Raylight 还没接 Turbo LoRA,传了不生效 |
  103 +| `seed` | int | 0 | 0–2147483647 |
  104 +| `cfg_scale` | float\|null | null | **当前是 no-op**,MiniMax H3 在 Raylight 里不支持 CFG |
  105 +| `negative_prompt` | string\|null | null | **当前是 no-op**,同上 |
  106 +| `first_frame_url` | string\|null | null | fl2va 模式:首帧图 URL |
  107 +| `last_frame_url` | string\|null | null | fl2va 模式:尾帧图 URL(可选,可以只给首帧) |
  108 +| `reference_image_urls` | string[] | `[]` | ref2va 模式:参考图,数量没有上限 |
  109 +| `reference_video_urls` | string[] | `[]` | ref2va 模式:参考视频,**最多 3 个**,2–15 秒,音轨自动带上 |
  110 +| `reference_audio_urls` | string[] | `[]` | ref2va 模式:独立音频参考,**最多 3 个**,不跟参考视频绑定 |
  111 +
  112 +图片/视频/音频字段填的都是 **URL**,不是文件内容本身——本地文件先调 `POST /v1/assets` 上传换 URL(见第 8 节),或者直接给一个公网可访问的图床/视频 URL 也行(底层是 `urllib.urlopen` 直接下载,服务器要能访问到这个地址)。
  113 +
  114 +#### 分辨率档位与帧数上限(超出直接判失败,不会真的提交给 GPU)
  115 +
  116 +只有下表这 8 个(宽,高)组合是实测过真实显存上限的,其它组合**不拦**(能提交但没人验证过会不会 OOM):
  117 +
  118 +| 分辨率 | 帧数上限 | 约合秒数(24fps) |
  119 +|---|---|---|
  120 +| 832×480 / 480×832 | 1125 | ≈46.9s |
  121 +| 448×256 / 256×448 | 1200 | ≈50.0s(这个是画质上限,不是显存上限) |
  122 +| 1024×576 / 576×1024 | 743 | ≈31.0s |
  123 +| 1344×768 / 768×1344 | 412 | ≈17.2s |
  124 +
  125 +实时以 `GET /v1/models` 返回的 `presets` 为准,这份表可能会随实测结果调整。
  126 +
  127 +#### 示例:文生视频(text)
  128 +
  129 +```bash
  130 +curl -X POST http://192.168.110.67:8080/v1/videos \
  131 + -H "Authorization: Bearer $TOKEN" \
  132 + -H "Content-Type: application/json" \
  133 + -d '{
  134 + "prompt": "清晨的中式庭院,一位女子坐在长廊上喝茶……",
  135 + "width": 480, "height": 832, "frames": 243,
  136 + "steps": 20, "seed": 0
  137 + }'
  138 +```
  139 +
  140 +#### 示例:首尾帧(fl2va)
  141 +
  142 +```bash
  143 +curl -X POST http://192.168.110.67:8080/v1/videos \
  144 + -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  145 + -d '{
  146 + "prompt": "……",
  147 + "first_frame_url": "http://192.168.110.67:8080/assets/xxx__first.png",
  148 + "last_frame_url": "http://192.168.110.67:8080/assets/xxx__last.png",
  149 + "width": 480, "height": 832, "frames": 124
  150 + }'
  151 +```
  152 +
  153 +#### 示例:参考图/参考视频(ref2va)
  154 +
  155 +```bash
  156 +curl -X POST http://192.168.110.67:8080/v1/videos \
  157 + -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  158 + -d '{
  159 + "prompt": "……",
  160 + "reference_image_urls": ["http://.../a.png", "http://.../b.png"],
  161 + "reference_video_urls": ["http://.../ref.mp4"],
  162 + "width": 480, "height": 832, "frames": 240
  163 + }'
  164 +```
  165 +
  166 +#### 成功响应(202,`status` 为 queued/running/failed 之一,见下方“Job 对象”)
  167 +
  168 +#### 校验失败也是 202,靠 `status` 字段识别——真实例子
  169 +
  170 +字段范围校验失败(`frames` 超过硬上限 3000):
  171 +```json
  172 +{
  173 + "id": "7df2a3c356b1",
  174 + "status": "failed",
  175 + "error": "{\"detail\":[{\"type\":\"less_than_equal\",\"loc\":[\"body\",\"frames\"],\"msg\":\"Input should be less than or equal to 3000\",\"input\":99999,\"ctx\":{\"le\":3000}}]}",
  176 + "req": { "...": "..." },
  177 + "partition": "text",
  178 + "created_at": 1788597597.86,
  179 + "backend": null,
  180 + "deleted_at": null
  181 +}
  182 +```
  183 +
  184 +分辨率实测上限校验失败(832×480 传了 1200 帧,超过表里的 1125):
  185 +```json
  186 +{
  187 + "id": "e545a8e5aa24",
  188 + "status": "failed",
  189 + "error": "{\"detail\":[{\"type\":\"value_error\",\"loc\":[\"body\"],\"msg\":\"Value error, 832x480 实测上限是 1125 帧,1200 帧会真实CUDA OOM(或448x256画质会退化,见notes.md),不接受这个请求\",...}]}",
  190 + "req": { "...": "..." },
  191 + "partition": "text"
  192 +}
  193 +```
  194 +
  195 +`error` 字段是底层校验器原样透传的 JSON 字符串,**不是稳定的 API 契约**,只建议展示给人看/记日志,不要用程序解析它的结构去做分支逻辑;要判断"这次提交有没有被接受",只看 `status !== "failed"` 就够。
  196 +
  197 +#### 其它错误
  198 +
  199 +| HTTP 状态 | 场景 |
  200 +|---|---|
  201 +| 401 | token 缺失/错误 |
  202 +| 409 | GPU 正在重置/硬重启窗口期(约 25 秒),`{"detail": "GPU正在重置/服务重启中,还剩约N秒,请稍后重试"}` |
  203 +
  204 +---
  205 +
  206 +## 5. 查询任务
  207 +
  208 +### `GET /v1/videos` — 列表
  209 +
  210 +query 参数(全部可选):
  211 +
  212 +| 参数 | 类型 | 默认 | 说明 |
  213 +|---|---|---|---|
  214 +| `limit` | int | 20 | 每页条数 |
  215 +| `offset` | int | 0 | 偏移量,配合 `limit` 做分页 |
  216 +| `trash` | bool | false | true 只看回收站里的任务 |
  217 +| `q` | string | "" | 按任务 id 或 prompt 子串搜索(`LIKE %q%`) |
  218 +| `status` | string | "" | `queued`/`running`/`done`/`failed`/`cancelled` 精确匹配 |
  219 +| `kind` | string | "" | `text`/`fl2va`/`ref2va` 精确匹配 |
  220 +| `sort` | string | desc | `desc`(默认,按创建时间倒序)/ `asc` |
  221 +
  222 +响应:
  223 +```json
  224 +{ "jobs": [ /* Job 对象数组,见第 6 节 */ ], "total": 850 }
  225 +```
  226 +
  227 +`total` 是**当前筛选条件下**匹配的总数(不受 `limit`/`offset` 影响),用来算分页页数。
  228 +
  229 +### `GET /v1/videos/{id}` — 单条详情
  230 +
  231 +会顺带触发一次状态同步:如果任务还在本地排队没提交上去,顺手再试一次提交;如果已经提交了,去底层问一次最新状态再返回。频繁轮询(比如每 2-3 秒一次)是安全的,但没必要比这更快——状态变化本来就不会快过底层真实的执行速度。
  232 +
  233 +### `GET /v1/videos/{id}/file` — 下载/播放视频
  234 +
  235 +只有 `status="done"` 的任务能下载。原生支持 HTTP Range(浏览器 `<video>` 标签直接能用来拖进度条)。返回 `Content-Disposition: attachment; filename="h3-{id}.mp4"`。
  236 +
  237 +任务还没跑完/没有 `backend` 时返回 `409 {"detail": "任务还在本地排队,尚未分配到可用后端"}`。
  238 +
  239 +### `GET /v1/videos/{id}/comfy_history` — 排查用
  240 +
  241 +返回 ComfyUI 侧这条任务的原始执行记录(完整 workflow graph + 节点输出),排查生成问题时用,不建议在正常业务逻辑里依赖它的结构。
  242 +
  243 +---
  244 +
  245 +## 6. Job 对象字段
  246 +
  247 +`POST /v1/videos`、`GET /v1/videos/{id}`、`GET /v1/videos` 里的每个元素,返回的都是同一种结构:
  248 +
  249 +| 字段 | 类型 | 说明 |
  250 +|---|---|---|
  251 +| `id` | string | console 任务 id,所有 `/v1/videos/{id}/*` 接口都用这个 |
  252 +| `status` | string | `queued` / `running` / `done` / `failed` / `cancelled` |
  253 +| `req` | object | 你提交时传的原始请求体(回显) |
  254 +| `partition` | string | `text` / `fl2va` / `ref2va`,见第 2 节 |
  255 +| `backend` | string\|null | 分配到的底层后端地址,还没排上就是 null |
  256 +| `created_at` | float | 提交时间,unix 时间戳(秒,带小数) |
  257 +| `started_at` | float\|null | 开始执行时间 |
  258 +| `finished_at` | float\|null | 结束时间(done/failed/cancelled 才有) |
  259 +| `step` / `total_steps` | int | 当前采样进度(`running` 时有意义;`total_steps` 未必等于请求的 `steps`,初始化阶段也会打印进度) |
  260 +| `file` | string\|null | 服务器本地文件路径(内部用,不建议依赖,用 `/file` 接口下载) |
  261 +| `size_bytes` | int\|null | 生成文件大小 |
  262 +| `error` | string\|null | 失败/取消原因 |
  263 +| `comfy_prompt_id` | string\|null | ComfyUI 侧的执行 id,排查用 |
  264 +| `queue_seq` | int | 排队顺序号,越小越靠前 |
  265 +| `queue_position` | int | **仅列表接口返回**:当前排在第几位(1-based,只有 `queued` 状态才有意义) |
  266 +| `deleted_at` | float\|null | 软删除时间戳,非 null 表示在回收站里 |
  267 +| `poll` / `download` | string | 底层任务原样带回来的字段,console 已经改写成用自己的 id 拼路径,可以直接用 |
  268 +
  269 +---
  270 +
  271 +## 7. 任务管理
  272 +
  273 +### `POST /v1/videos/{id}/cancel` — 取消/停止
  274 +
  275 +行为取决于当前状态:
  276 +
  277 +- **`queued`**:直接标记 `cancelled`,从底层队列里真正撤掉,不会再消耗 GPU 去跑它。
  278 +- **`running`**:⚠️ **会硬重启整个 raylight 服务**。真实计算跑在 Raylight 的 Ray 子进程里,收不到普通的打断信号,唯一能立刻把 GPU 收回来的办法就是重启整个后端——重启期间(约 15-20 秒)**所有**任务(不只是被停的这条)都无法提交/无法查询进度,会影响其它并发调用方。不是"优雅停止",请求前想清楚代价。
  279 +- 已经是终态(`done`/`failed`/`cancelled`):`409 {"detail": "任务当前状态是 xxx,已经是终态,取消不了"}`
  280 +
  281 +### `POST /v1/videos/{id}/prioritize` — 插队
  282 +
  283 +只能对 `status="queued"` 的任务调用,把它挪到排队最前面。非 queued 状态返回 409。
  284 +
  285 +响应里的 `queue_position` 是插队后的新位置。
  286 +
  287 +### `DELETE /v1/videos/{id}` — 软删除
  288 +
  289 +移入回收站(`deleted_at` 设成当前时间)。**不影响底层任务本身**——如果这条任务还在排队/运行中,它会照常跑完,只是从 console 的主列表里消失了。真要连底层计算一起停掉,配合 `cancel` 一起调(顺序不影响结果)。
  290 +
  291 +```json
  292 +{"id": "xxx", "deleted": true}
  293 +```
  294 +
  295 +对已经在回收站里的任务重复调用,或 id 不存在:`404 {"detail": "无此任务,或已经在回收站里"}`。
  296 +
  297 +### `POST /v1/videos/{id}/restore` — 恢复
  298 +
  299 +```json
  300 +{"id": "xxx", "deleted": false}
  301 +```
  302 +
  303 +对不在回收站里的任务调用:`404 {"detail": "无此任务,或不在回收站里"}`。
  304 +
  305 +### `POST /v1/videos/batch-cancel` / `POST /v1/videos/batch-delete` — 批量操作
  306 +
  307 +**2026-09-05 新增**:给脚本化批量清理用,避免大批量提交/清理时只能一个个调用单条接口。
  308 +
  309 +请求体二选一:
  310 +
  311 +```json
  312 +// 方式一:显式给 id 列表,没有数量上限(调用方明确知道要动哪些)
  313 +{ "ids": ["id1", "id2", "id3"] }
  314 +```
  315 +```json
  316 +// 方式二:筛选条件,跟 GET /v1/videos 的 status/kind/q 语义一致,至少给一个
  317 +{ "status": "queued", "kind": "ref2va", "q": "某个关键词" }
  318 +```
  319 +
  320 +两种方式不能都不给——空 body 会被拒绝:
  321 +```json
  322 +// HTTP 400
  323 +{"detail": "必须提供 ids 列表,或至少一个筛选条件(status/kind/q),避免误操作全部任务"}
  324 +```
  325 +
  326 +筛选条件模式有安全上限:命中数超过 **300** 条直接拒绝,不会静默截断:
  327 +```json
  328 +// HTTP 400
  329 +{"detail": "筛选条件命中 320 条,超过单次批量操作上限 300,请缩小筛选范围(加 status/kind/q)或改成显式 ids 列表"}
  330 +```
  331 +
  332 +响应:逐条处理,一条失败不影响其它条,成功/失败分别列出来:
  333 +```json
  334 +{
  335 + "requested": 3,
  336 + "succeeded": ["id1", "id3"],
  337 + "failed": [{"id": "id2", "error": "任务当前状态是 done,已经是终态,取消不了"}]
  338 +}
  339 +```
  340 +
  341 +`batch-cancel` 对批量里混进的 `running` 任务一样会触发硬重启(见上面单条 cancel 的说明)——如果批量目标里可能包含正在运行的任务,先用筛选条件把 `status` 限定成 `queued`,避免误停正在跑的任务。
  342 +
  343 +---
  344 +
  345 +## 8. 上传参考素材
  346 +
  347 +### `POST /v1/assets`
  348 +
  349 +`multipart/form-data`,字段名 `file`。按扩展名分流校验:
  350 +
  351 +| 类型 | 允许扩展名 | 大小上限 |
  352 +|---|---|---|
  353 +| 图片 | jpg/jpeg/png/webp | 20MB(且必须是能被解码的有效图片,否则 400) |
  354 +| 视频 | mp4/mov/webm/mkv | 100MB |
  355 +| 音频 | mp3/wav/m4a/ogg/flac | 30MB |
  356 +
  357 +```bash
  358 +curl -X POST http://192.168.110.67:8080/v1/assets \
  359 + -H "Authorization: Bearer $TOKEN" \
  360 + -F "file=@/path/to/reference.png"
  361 +```
  362 +
  363 +响应:
  364 +```json
  365 +{"id": "31417985029a4f60", "url": "http://192.168.110.67:8080/assets/31417985029a4f60__reference.png", "kind": "image"}
  366 +```
  367 +
  368 +拿到的 `url` 直接填进 `first_frame_url`/`reference_image_urls` 等字段即可。也可以跳过这一步,直接给一个服务器能访问到的公网 URL(图床等)——底层是 `urllib.urlopen` 直接下载,本地上传出来的 URL 和外部 URL 对它没有区别,只是不做 URL 可达性预检,下载失败会在任务执行阶段才报错。
  369 +
  370 +---
  371 +
  372 +## 9. 系统状态
  373 +
  374 +### `GET /v1/backends`
  375 +
  376 +```json
  377 +{
  378 + "status": "ok",
  379 + "ready": 1, "total": 1,
  380 + "queued": 174, "running": 1,
  381 + "backends": [
  382 + {"status": "ok", "model": "ready", "gpu": "0,1,2,3", "partition": "raylight-fsdp",
  383 + "offload": "fsdp", "queued": 174, "running": 1, "backend": "http://127.0.0.1:8100"}
  384 + ],
  385 + "gpus": [
  386 + {"index": 0, "memory_used_mb": 5970, "memory_total_mb": 22528, "utilization_pct": 0},
  387 + {"index": 1, "memory_used_mb": 9068, "memory_total_mb": 22528, "utilization_pct": 99}
  388 + ]
  389 +}
  390 +```
  391 +
  392 +`total` 恒为 1(架构上就是一个集群,不是"N 个 worker 里 ready 了几个")。`queued`/`running` 是当前排队深度和是否有任务在跑,轮询这个可以判断"提交了大概要等多久"(没有精确 ETA,只能凭经验:running=1 时前面每条排队任务预计几十秒到几分钟不等,取决于分辨率/帧数/步数)。
  393 +
  394 +### `GET /v1/models`
  395 +
  396 +```json
  397 +{
  398 + "partitions": {
  399 + "fl2va": {"dit": "minimax_h3_fl2va_pruned_int8_convrot.safetensors", "desc": "首末帧条件生成"},
  400 + "ref2va": {"dit": "minimax_h3_ref2va_pruned_int8_convrot.safetensors", "desc": "文生视频 / 多参考图生成"}
  401 + },
  402 + "presets": [
  403 + {"label": "832×480,最多 1125 帧(≈46.9s@24fps)", "width": 832, "height": 480, "max_frames": 1125}
  404 + /* ...其余档位见第 4 节表格... */
  405 + ]
  406 +}
  407 +```
  408 +
  409 +`presets` 是实时查底层拿到的,跟第 4 节的表格应该始终一致——集成时建议用这个接口取分辨率档位,不要在自己代码里硬编码一份可能过期的表。
  410 +
  411 +### `POST /v1/admin/reset-gpu` ⚠️ 危险操作
  412 +
  413 +不挂在具体任务上的兜底重置——硬重启整个 raylight 服务,回收可能因为崩溃/OOM 卡住不释放的显存。会打断当前正在运行的任务(如果有),效果和代价跟单条 `cancel` 停止 `running` 任务完全一样(15-20 秒全局不可用)。只在确认 GPU 显存异常卡住时用,不要当成常规操作调用。
  414 +
  415 +---
  416 +
  417 +## 10. 错误码约定
  418 +
  419 +| HTTP 状态 | 含义 |
  420 +|---|---|
  421 +| 200 / 202 | 成功(`POST /v1/videos` 固定 202,见第 4 节警告) |
  422 +| 400 | 请求参数问题(批量接口的筛选条件缺失/命中过多,上传文件不是有效图片等) |
  423 +| 401 | token 缺失或错误 |
  424 +| 404 | 任务 id 不存在,或者已经在回收站/不在回收站(视接口而定) |
  425 +| 409 | 状态冲突(GPU 重置中;任务已是终态无法取消;非 queued 状态无法插队等) |
  426 +| 413 | 上传文件超过大小上限 |
  427 +| 502 | console 连不上底层 raylight(网络问题,重试即可) |
  428 +
  429 +除 `POST /v1/videos` 外,其它接口的错误都会原样反映在 HTTP 状态码上,body 统一是 FastAPI 默认的 `{"detail": "..."}` 形状。
  430 +
  431 +---
  432 +
  433 +## 11. 集成注意事项(读完再接)
  434 +
  435 +1. **只走 console,不要直连 raylight(8100)**。8100 没有鉴权、没有持久化、没有软删除,绕过 console 提交的任务 console 事后只能靠"扫描发现"机制捞回来,捞回来的记录用的是 raylight 自己的 id(跟正常提交产生的 console id 是同一种格式,肉眼无法区分),管理起来处处受限,2026-09-05 还实测触发过一次真实的重复记录 bug。
  436 +2. **批量提交请加节流**,别用 fire-and-forget 式的无限制并发。console 是单进程 FastAPI + sqlite,扛得住合理的并发(个位数到几十 QPS 级别的短请求),但成百上千个并发 POST 会把它的 HTTP 层和数据库拖到不可用(2026-09-05 实测:150 个并发提交导致后续 11 小时 `GET /v1/backends` 响应时间从 0.3 秒退化到 38 秒)。建议:每条提交间隔 100-200ms,或者控制并发数在个位数。
  437 +3. **`POST /v1/videos` 永远 202,靠 `status` 字段判断成功/失败**,见第 4 节。
  438 +4. **提交前用 `GET /v1/models` 查当前支持的分辨率档位**,不要硬编码——档位可能随实测结果调整。
  439 +5. **`turbo`/`cfg_scale`/`negative_prompt` 目前都是 no-op**,传了不会报错但也不会生效,别把它们当成可以调节生成效果的旋钮。
  440 +6. **停止 `running` 任务(`cancel`)= 硬重启整个服务**,会影响所有并发调用方 15-20 秒,不是低成本操作。
  441 +7. 任何时刻只有 **1 条**任务在真正计算,其余全部排队——设计并发预期时按"单机吞吐"算,不要假设 4 张卡能并行跑 4 条不同任务。