# 任务查询与结算
> 图片、视频和 3D 任务提交后的查询与处理:轮询频率、每种状态的含义和完整响应、产物下载与链接过期处理,以及如何读懂费用状态。
## 查询与协议隔离
如需由平台通知任务状态,可在生成时设置 `callback_url`,完整接收、验签和去重示例见[任务回调](task-callbacks.md)。轮询仍可随时使用。
以下为 Bash / curl 示例,先按[快速开始](overview.md)设置 `GATEWAY_ORIGIN` 和 `KEY`;`TASK_ID` 替换为提交响应中的 `id`(形如 `task_` 加 32 位字符)。
```bash
TASK_ID="替换为提交响应中的任务 id"
curl -s "$GATEWAY_ORIGIN/media/v1/tasks/$TASK_ID" \
-H "Authorization: Bearer $KEY"
```
| 项目 | 规则 |
| --- | --- |
| 可查询的任务 | 当前身份有权读取的 `/media/v1` 任务,以及通过 Midjourney `2026-10-16` 响应版本登记的新任务;MJ 使用提交返回的 `platform_task_id`,不能使用 Proxy 的 `task_id` |
| 协议隔离 | 除上述显式登记的 MJ 任务外,其他兼容或原生任务仍回原接口查询;旧任务不自动导入,任务 ID 不混用 |
| 权限 | 每次查询都会重新校验身份和模型权限。额度已用完但仍有效、未禁用的密钥可以继续查询已提交的任务 |
| 时间字段 | `created_at`、`started_at`、`finished_at` 为 Unix 秒;平台不知道的时间返回 `null`,`started_at` 经常为 `null` |
MJ 的普通 U/V 仍使用专用动作接口;统一查询只提供状态、产物、父任务、请求关联和权威账务,不提供完整操作协议。MJ 本期不支持此处的取消、签名产物下载和回调。新任务提交与按钮查询见 [Midjourney 图片](native-mj.md)。
### 轮询建议
| 任务类型 | 建议做法 |
| --- | --- |
| 图片 | 提交时设 `"wait": 50`,通常一次请求就能拿到结果;仍未完成时每 3 秒查询一次 |
| 视频 | 提交时设 `"wait": 0`,之后每 5 秒查询一次 |
| 3D | 提交时设 `"wait": 0`,之后每 5–10 秒查询一次 |
- 查询类接口(模型查询、任务查询、取消)默认每个用户合计每分钟 120 次。同时跟踪多个任务时请拉长间隔,或在服务端集中轮询;超限返回 HTTP 429 `rate_limited`,等几秒再查即可。
- 生成终态与交付完成分开判断。`succeeded` 后产物为空、任一项不可读、下载失败或账务未明确时,报告未完成并继续查询原任务;不能因为生成成功就退出整个流程。`reconciling` 继续查询;失败、取消或生成超时后停止生成等待,但仍需核对账务。
- 建议给单个任务设置总等待上限(例如视频 30 分钟),超时后记录任务 ID 以便后续核查,**不要重新提交生成**。
## 生成状态
| `status` | 含义 | 你应该怎么做 |
| --- | --- | --- |
| `queued` | 已受理,等待执行 | 继续查询 |
| `running` | 正在生成 | 继续查询 |
| `succeeded` | 生成成功 | 读取 `artifacts` 下载结果;费用看 `billing` |
| `failed` | 生成失败 | 读取 `error.message`,修改请求后用新的幂等键重新提交 |
| `cancelled` | 已取消 | 停止查询;任务仍可查询 |
| `expired` | 厂商侧任务超时未完成(不是下载链接过期) | 停止查询,必要时重新提交 |
| `reconciling` | 提交结果或生成结果正在核对 | 继续查询同一任务,**不要重新提交** |
`error` 为 `null`,或是包含 `code`、`message`、`stage`、`retryable` 的对象。`stage` 取值为 `validation`、`submission`、`generation`、`delivery`、`billing`,表示出错环节;是否收费另看 `billing`,与 `stage` 无关。`progress` 是 0–100 的整数,只作展示参考,部分模型完成前一直为 0。
### 各状态的响应示例
以下为同一个视频任务在不同阶段的查询结果。ID、链接与金额均为示意。
排队中(提交后立即返回的也是这个形状):
```json
{
"id": "task_example",
"object": "media.task",
"model": "doubao-seedance-2-0-260128",
"type": "video",
"status": "queued",
"progress": 0,
"created_at": 1791763200,
"started_at": null,
"finished_at": null,
"artifacts": [],
"error": null,
"billing": {"status": "reserved", "currency": "CNY", "amount": null},
"metadata": {"client_ref": "video-1"},
"request_id": "req_example"
}
```
生成成功:
```json
{
"id": "task_example",
"object": "media.task",
"model": "doubao-seedance-2-0-260128",
"type": "video",
"status": "succeeded",
"progress": 100,
"created_at": 1791763200,
"started_at": null,
"finished_at": 1791763290,
"artifacts": [
{"index": 0, "kind": "video", "mime_type": "video/mp4", "state": "available", "url": "https://example.com/result.mp4", "url_type": "upstream", "expires_at": null},
{"index": 1, "kind": "cover", "mime_type": "image/jpeg", "state": "available", "url": "https://example.com/cover.jpg", "url_type": "upstream", "expires_at": null}
],
"error": null,
"billing": {"status": "settled", "currency": "CNY", "amount": "1.200000"},
"metadata": {"client_ref": "video-1"},
"request_id": "req_example"
}
```
生成失败(`message` 为厂商返回的原因,此处为示意):
```json
{
"id": "task_example",
"object": "media.task",
"model": "doubao-seedance-2-0-260128",
"type": "video",
"status": "failed",
"progress": 0,
"created_at": 1791763200,
"started_at": null,
"finished_at": 1791763230,
"artifacts": [],
"error": {"code": "provider_rejected", "message": "厂商返回的失败原因(示意)", "stage": "generation", "retryable": false},
"billing": {"status": "released", "currency": "CNY", "amount": null},
"metadata": {"client_ref": "video-1"},
"request_id": "req_example"
}
```
待核对(提交时网络中断或厂商响应不明确,平台正在确认任务是否已受理):
```json
{
"id": "task_example",
"object": "media.task",
"model": "doubao-seedance-2-0-260128",
"type": "video",
"status": "reconciling",
"progress": 0,
"created_at": 1791763200,
"started_at": null,
"finished_at": null,
"artifacts": [],
"error": {"code": "provider_unavailable", "message": "Submission or result is being reconciled; query this task and do not resubmit", "stage": "submission", "retryable": false},
"billing": {"status": "reserved", "currency": "CNY", "amount": null},
"metadata": {"client_ref": "video-1"},
"request_id": "req_example"
}
```
`reconciling` 期间费用保持预留,平台确认后任务会转为成功或失败。重新提交会产生一个新的生成任务和新的费用,所以请继续查询原任务;需要人工确认时,把任务 `id` 和 `request_id` 提供给平台管理员。
另一种情况:`status` 为 `succeeded`,但 `artifacts` 为空且 `error.code` 为 `provider_unavailable`、`stage` 为 `delivery`(`Generation succeeded but the output is not yet readable`)。这表示生成成功但交付未完成,应继续查询原任务,不能静默忽略空数组。稍后查询并不保证文件一定恢复:上游文件过期或丢失时要保留任务 ID 请求平台核对,不要重新生成。
## 产物
`artifacts` 是产物数组,每项字段如下:
| 字段 | 类型与出现条件 | 说明 |
| --- | --- | --- |
| `index` | integer,每项必有,0 起 | 产物身份序号;0 是有效序号,不代表首选格式或预览优先级 |
| `kind` | string,每项必有 | `image`、`video`、`model3d`、`audio`、`cover`、`last_frame` 表示用途,不表示格式;客户端保留未知类别 |
| `mime_type` | string,每项必有 | 如 `image/jpeg`、`image/png`、`video/mp4`、`video/quicktime`;`application/octet-stream` 是通用二进制类型,具体 3D 格式查看 `format` |
| `format` | string,3D 模型格式可识别时出现 | 小写模型格式:`obj`、`glb`、`fbx`、`stl`、`usdz`。优先保留上游产物类型,缺失时按可识别的上游文件后缀补充;不从请求的 `output_format` 推断。未知时省略 |
| `container` | string,3D 模型采用已知包装时出现 | 当前为 `zip`,表示下载文件是压缩包;例如 `format=obj, container=zip`。缺失不证明文件没有包装 |
| `filename` | string,3D 模型产物出现 | 平台建议保存名,如 `model-4.fbx`、`model-0.zip`,与签名下载的 `Content-Disposition` 一致。仅使用可识别的实际文件后缀;包装未知时可能没有后缀 |
| `state` | string,每项必有 | `available` 可读取;`pending` 准备中;`unavailable` 暂不可读。未知值也不能当作可读 |
| `url` | string,仅可读且已取得地址时使用 | 必须是非空 HTTP(S) 地址;缺失/空字符串不能发起下载,更不表示零字节文件 |
| `url_type` | string,有地址时 | `upstream` 为厂商链接,`signed` 为平台签名链接;不要从路径末尾推断文件格式 |
| `expires_at` | integer 或 null,Unix 秒 | 平台签名授权到期时间;厂商链接为 null,表示平台未提供有效期,不能当成永久有效或 0 秒 |
| `error` | object,可读失败时出现 | 产物自身的 `code/message/stage/retryable`;保留并结合任务错误处理 |
`artifacts=[]` 表示当前没有可返回的产物,不证明“不需要下载”。任一产物不可读时保留已下载文件,继续查询原任务;`kind`、MIME、文件包装、生成状态、账务和客户端预览均须分别判断。
3D 调用方应按 `kind=model3d` 和 `format` 筛选所需文件,例如 `format=fbx`;不要固定选择第一项。`format` 表示模型格式,`container` 表示外层包装,`filename` 用于保存文件,三者不改变下载字节。旧任务重新查询也会返回可识别的格式信息,无需重新生成。客户端仍应检查下载完整性和文件头,发现与元数据不符时保留原任务核对。
### 下载产物
使用[快速开始的完整 Python 示例](overview.md#overview-h-600de8a7),保存为 `media_client.py` 后执行:
```bash
export TASK_ID="替换为提交响应中的 id"
python media_client.py
```
该模式只查询与下载已有任务。脚本处理全部产物,拒绝空地址,检查 HTTP 失败与不完整字节,完成前只写 `.part`;未知格式保留 `.bin`。下载失败后重新查询同一 ID,获得当前链接再重试,不会调用生成入口。
| 链接类型 | 下载方式 | 有效期 |
| --- | --- | --- |
| `upstream` | 直接 GET。不要带平台密钥。部分厂商链接只签名了 GET,HEAD 返回 403 不代表文件失效 | 由厂商决定:方舟(Seedream、Seedance)24 小时,Seedance 2.5 视频最多下载 100 次;可灵 30 天后清理 |
| `signed` | 直接 GET,不需要也不应带 `Authorization`;支持 HEAD 和 `Range` 断点续传 | 默认 24 小时,最长 7 天,见 `expires_at` |
3D 产物和 Nano Banana / Gemini 图片一律是 `signed`;GPT Image 的 Base64 图片也由平台保存并签发链接,上游 URL 结果以实际 `url_type` 为准。签名链接只授权这一个任务的这一个产物,必须使用返回的完整地址,不能改路径或参数;每次查询任务都会重新签发,所以两次查询的 URL 不同,但指向同一个文件。签名链接在有效期内任何持有者都能下载,撤销密钥也不能收回已发出的链接,请勿公开分享。
下载签名链接时的报错:
| HTTP 状态与 `error.code` | 原因 | 处理 |
| --- | --- | --- |
| 403 `model_not_authorized` | 链接已过期,或地址被改动 | 重新查询任务,使用新返回的 `url` |
| 409 `unsupported_operation` | 任务尚未成功,产物还不能读取 | 等任务变为 `succeeded` 后再下载 |
| 404 `task_not_found` | 任务或该序号的产物不存在,或用其他协议的地址访问 | 核对任务 ID 和 `index`,使用任务返回的原地址 |
| 429 `rate_limited` | 下载过于频繁(默认每分钟 600 次) | 稍后重试 |
### 交付恢复与预览
| 现象 | 下一步 | 是否已交付完成 |
| --- | --- | --- |
| `succeeded` 但产物为空、`pending/unavailable` 或 delivery 错误 | 报告暂不可读,退避查询原任务;超出总等待上限时保留 ID 供后续恢复 | 否 |
| 下载 HTTP 失败、空文件、字节不完整 | 保留 `.part` 和已下载产物,重新查询原任务后再下载 | 否 |
| 平台签名过期 | 重新查询原任务,用新签名重试;旧地址不能自行修改 | 否,直到文件完整取得 |
| 上游文件过期、被清理或下载次数耗尽 | 查询并核对是否存在可读归档;没有可恢复副本时保留 ID 联系平台,重签不能重建文件 | 否 |
| 文件完整但客户端不能预览 | 检查格式、ZIP 包内模型/贴图、编码、材质与压缩支持,必要时在本地转换;不能只改扩展名 | 下载已完成,应用使用尚未确认 |
| `billing.status=reserved/reconciling`,或金额未知 | 已有文件可先保存,继续查询/核对原任务账务;null 不是免费 | 整个处理流程仍待确认 |
以上情形都不自动新建生成。签名有效期只是读取授权期限,不延长上游文件寿命;重新查询也不保证一定返回可恢复文件。尽快下载转存。示例只确认文件保存与已知账务,不能替调用方证明客户端预览兼容。
## 账务状态
`billing` 与生成状态相互独立,请分别判断:
| `billing.status` | `billing.amount` | 含义 |
| --- | --- | --- |
| `reserved` | `null` | 已按预估预留费用,尚未结算 |
| `settled` | 十进制字符串,如 `"1.200000"` | 已按实际用量结算,这是本任务的实际扣费 |
| `released` | `null` | 未扣费,预留已释放(常见于失败或取消) |
| `refunded` | 十进制字符串 | 扣费后发生过退款,金额为退款后的净额 |
| `reconciling` | 按已确认的账务证据提供,可能为 `null` | 费用正在核对,不代表免费 |
`currency` 固定为 `CNY`,金额单位为人民币元,最多 6 位小数。生成失败不一定免费,生成成功也不一定已经结算;对账以 `settled` 或 `refunded` 时的 `amount` 为准。重复查询不会重复扣费。
GPT Image 响应可正常解析、且平台已确认完整图片,但上游的输入或输出 token 计数缺失、为 `null`,或整数数值未满足计费校验要求时,`status=succeeded` 与 `billing.status=reconciling` 可以同时出现,`billing.amount=null`;账务进入核对前也可能仍返回 `reserved`。已有产物可先下载;费用预留继续保留,等待原任务用量核对,不按零费用完成,也不重新提交生成。明确的零计数与未知计数不同;即使 token 为零,适用的按次或按图价格仍可能产生费用。
用量字段类型或响应结构异常导致整体解析失败时,任务本身为 `status=reconciling`,可能没有可下载产物,账务保留预留。按实际返回的生成、产物和账务状态处理,具体边界见[GPT Image 统一任务生成与编辑](media-images.md#media-images-h-eeced450)。
## 取消
```bash
curl -s -X POST "$GATEWAY_ORIGIN/media/v1/tasks/$TASK_ID/cancel" \
-H "Authorization: Bearer $KEY"
```
| 情况 | 返回 |
| --- | --- |
| 模型支持取消(目前仅 Seedance 视频,模型能力中 `supports_cancel` 为 `true`),且任务仍为 `queued` | HTTP 200 与任务对象,`status` 为 `cancelled` |
| 任务已是 `cancelled` | HTTP 200 与任务对象 |
| 模型不支持取消(图片、可灵、MiniMax H3、3D) | HTTP 400 `unsupported_operation`:`This model does not support task cancellation` |
| 任务已开始生成或已结束 | HTTP 400 `unsupported_operation`:`This task cannot be cancelled in its current state` |
取消不会删除任务,之后仍可查询。取消成功后继续查询 `billing`,确认变为 `released` 后才表示费用已释放。