在火山方舟上轮询 Seedance 任务,四条事实就够用。查询端点是 GET https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks/{id}。状态字段叫 status,一共六个取值。成片地址在 content.video_url。这个地址的有效期是 24 小时。API 总览讲的是创建那一侧,下面全是创建之后的循环。
轮询到底能拿回哪些字段
创建调用只还你一个任务 ID,能直接用的信息很少。官方文档列了 22 个响应字段,其中六个决定 worker 的下一步动作。
| 字段 | 它告诉你的代码什么 |
|---|---|
status | 任务在跑、跑完,还是已经死了 |
content.video_url | 托管好的视频,成功后才出现 |
content.last_frame_url | 尾帧图像,同一个 24 小时窗口 |
error | 失败的任务失败在哪 |
execution_expires_after | 任务超时阈值,单位秒 |
usage | token 用量,成功才计费 |
官方那条响应示例是 2.0 的真实回包,不是 2.5 的:"model": "doubao-seedance-2-0-260128"、"resolution": "720p"、"duration": 5、"usage": { "completion_tokens": 108900, "total_tokens": 108900 }。那个 token 数只能按 2.0 的形态看,别拿它估 2.5。
端点与状态字段
查询调用是对创建时那个集合发 GET,任务 ID 是必选的 Path 参数。鉴权用同一把长效方舟 Key:Authorization: Bearer $ARK_API_KEY。
六个状态取值
status 是字符串,官方给了六个取值。把每个取值对应的处理动作写进分支语句,能省掉大部分猜测:
queued— 排队中,继续等running— 任务运行中,继续等cancelled— 取消任务,取消状态 24h 自动删除,属于终态succeeded— 任务成功,去取content.video_urlfailed— 任务失败,读errorexpired— 超出execution_expires_after,属于终态
其中两个会改变循环的出口。cancelled 只能从 queued 到达,记录会在 24 小时后自己删掉。expired 说明任务熬过了 execution_expires_after,官方给的默认值是 172800 秒。还有一点容易忽略:这套词表里没有 pending,所以等 pending 或 processing 的循环会一直等下去。端点参考里有请求侧的完整字段。
轮询循环怎么写

启动循环的创建调用长这样:
curl -X POST https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ARK_API_KEY" \
-d '{
"model": "doubao-seedance-2-5-260628",
"content": [
{ "type": "text", "text": "A workshop at dawn, hands assembling a clock movement." }
],
"ratio": "16:9",
"duration": 5
}'
bash 版循环
TASK_ID="your_task_id"
while :; do
payload=$(curl -s -X GET \
"https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks/${TASK_ID}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ARK_API_KEY")
status=$(printf '%s' "$payload" | jq -r .status)
case "$status" in
queued|running) sleep 10 ;;
succeeded)
curl -sL -o "seedance-${TASK_ID}.mp4" \
"$(printf '%s' "$payload" | jq -r .content.video_url)"
break ;;
failed|expired|cancelled)
printf '%s' "$payload" | jq -r .error
break ;;
*) echo "unexpected status: $status"; break ;;
esac
done
这个 case 没有兜底的 sleep,遇到不认识的字符串会让循环结束,而不是在里面空转。这一点比间隔更重要:官方没有公布推荐的轮询间隔。
官方 SDK 版循环
方舟提供官方 Python SDK,包名是 volcenginesdkarkruntime。
import os
import time
from volcenginesdkarkruntime import Ark
client = Ark(api_key=os.environ["ARK_API_KEY"])
task = client.content_generation.tasks.create(
model="doubao-seedance-2-5-260628",
content=[{"type": "text", "text": "A workshop at dawn."}],
ratio="16:9",
duration=5,
)
print(task.id)
TERMINAL = {"succeeded", "failed", "expired", "cancelled"}
while True:
result = client.content_generation.tasks.get(task_id=task.id)
if result.status in TERMINAL:
break
time.sleep(10)
print(result.status)
print(getattr(result.content, "video_url", None))
取消或删除任务
同样是这六个取值,决定 DELETE 能不能发出去。DELETE https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks/{id} 干的是两件不同的事,还直接拒绝两个状态。
| 当前任务状态 | 是否支持 DELETE | 操作含义 | 操作后状态 |
|---|---|---|---|
queued | 是 | 任务取消排队,状态被变更为 cancelled | cancelled |
running | 否 | — | — |
succeeded | 是 | 删除记录,后续将不支持查询 | — |
failed | 是 | 删除记录,后续将不支持查询 | — |
cancelled | 否 | — | — |
expired | 是 | 删除记录,后续将不支持查询 | — |
成功的 DELETE 返回 HTTP 200 和一个空对象 {}。运行中的任务停不下来,所以界面上的取消按钮得接受「点了可能什么也没发生」。
同一个路径还有一个列表调用,收 page_num 和 page_size(默认 1 和 20,取值 [1, 500]),外加 filter.status、filter.task_ids 和 filter.model。对账时以它为准,不要把自己的账本当唯一真相。
结果 URL 的 24 小时窗口
三个窗口各自公布,互不对齐。
| 窗口 | 取值 |
|---|---|
| 任务记录可查询 | 最近 7 天 |
| 结果 URL 有效期 | 24 小时 |
| 2.5 结果 URL 的下载次数上限 | 100 次 |
官方措辞很精确。记录窗口:仅支持查询最近 7 天的任务记录,时间区间为 [T-7 天, T),其中 T 为请求发起时刻的 UTC 时间戳(精确到秒)。 URL:视频 URL 有效期为 24 小时,请及时下载或转存。 次数:Seedance 2.5 模型生成的视频 URL 下载次数上限为 100 次。 尾帧图像走的是同一个时钟。
在 URL 过期之前做什么
轮询一次,然后就转存。不要把方舟那条 URL 直接发给用户,第 100 次下载会让它对所有人失效。
import os
import requests
from volcenginesdkarkruntime import Ark
client = Ark(api_key=os.environ["ARK_API_KEY"])
result = client.content_generation.tasks.get(task_id="your_task_id")
if result.status == "succeeded":
response = requests.get(result.content.video_url, timeout=120)
response.raise_for_status()
with open(f"seedance-{result.id}.mp4", "wb") as handle:
handle.write(response.content)
print("archived", len(response.content), "bytes")
纪律就这一条:一次下载,进自己的存储,立刻做。每次轮询都下载一边,会把下载额度和时间一起烧掉。如果在意 token 那一侧,价格拆解里有公式。
官方没有公布什么
本次核查中官方没有公布:
- 推荐的轮询间隔和退避策略。官方只说支持轮询、也可以用 Webhook 通知,就到此为止。
- 请求被限流时返回的 HTTP 状态码与响应头。本次读到的官方页面里没有出现
429,也没有出现Retry-After。 - 任何请求追踪头。想把一次轮询和一张工单对上,官方只给了任务 ID。
- 任务耗时区间、队列深度、延迟和成功率保证。
- 这个模型的免费额度。
官方示例里的 ID 形如 cgt-2026****-****,但那个形态从没被写成规则。分支语句按 status 走,不要按 ID 前缀走。
常见问题
用哪个端点轮询任务? GET https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks/{id},任务 ID 放在 Path 参数里,方舟 Key 放在 Authorization 头里。
六个状态取值是哪六个? queued、running、cancelled、succeeded、failed、expired。没有 pending 和 processing,等这两个的循环会一直等下去。
我有多久时间去取视频? 24 小时,2.5 的结果 URL 最多下载 100 次。任务记录本身可查 7 天。
运行中的任务能停吗? 不能。只有 queued 能被取消,running 和 cancelled 都会拒绝 DELETE。
能不能完全不轮询? 官方文档另有回调方式作为替代,但本次核查没能读到它的字段细节,所以这条路线在这里只算「有文档、未核实」。
上线前把每个字段和每个窗口对着官方页面核一遍;限流和有效期这两套规则都不太预告就变。