Seedance 的报错分两层:一层是 HTTP 请求失败,例如 401、403、400、429;另一层是请求成功但任务最终 failed 或 expired。只看提交接口返回 200 就认为成功,是接入视频 API 时最常见的误判。正确的排查顺序是:先看 HTTP 状态码,再看任务状态和 error 字段,最后才改提示词或换模型。

本文按错误类型拆开,给出原因、处理顺序和一段可复用的错误处理代码。接口路径、状态命名和额度口径可能随版本调整,实际以站内文档和官方文档为准。

说明:Seedance、豆包、火山引擎是字节跳动及其关联公司的商标。本文仅作技术说明,不代表任何隶属或背书关系。示例中的路径和字段仅用于演示,调用前请核对文档。

一、先分清请求失败与任务失败

现象 所在层 常见原因 处理
401 HTTP Key 缺失、格式错误、已失效 修鉴权
403 HTTP 权限不足、模型未开通、区域限制 查权限与开通状态
400 HTTP 参数越界、模型名错误、素材不可访问 对照文档改参数
429 HTTP 并发、速率或配额受限 退避、降并发
5xx HTTP 上游过载或临时故障 退避、切通道
failed 任务 审核拒绝、素材问题、生成异常 读 error,修素材
expired 任务 超时未完成或未取结果 重建任务、检查期限
下载失败 结果 URL 过期、鉴权、网络 重新获取或转存

日志至少记录时间、HTTP 状态码、错误码、模型名、task_id、业务单号和耗时。不要记录完整 Key,也不要把带签名的结果 URL 原样打印。

二、401 与 403:先查请求头和 Key

401 表示身份没有通过验证,通常和模型能力、余额无关。按顺序检查:

  • Authorization 是否为 Bearer <key>,火山方舟使用 API Key 作为 Bearer Token。
  • Key 是否带空格、换行或多余引号,环境变量是否真的加载。
  • Base URL 与 Key 是否属于同一平台、同一区域。
  • Key 是否被删除、轮换、停用,或项目权限不足。
  • 反向代理是否覆盖或丢弃了 Authorization 头。

403 更多是权限问题:模型未开通、账号没有该模型权限、区域或合规限制。401 换一把新 Key 做最小请求即可快速定位;403 要查控制台的开通状态和账号权限,改代码通常无效。

三、429:限流、并发与配额

429 表示请求超过速率、并发或配额限制。常见触发场景:

  • 多个服务共用一把 Key,互相挤占额度。
  • 批量任务在整点同时启动,形成流量脉冲。
  • 重试没有退避,越失败越发,形成重试风暴。
  • 长任务占满并发,短任务也排不进去。

处理原则是指数退避加随机抖动、降低并发、按项目拆 Key、错峰提交。不要在没有退避的情况下循环重试;429 期间继续高频提交,会把限流拖成持续故障。业务确实需要高并发时,提前确认配额上限,并准备备用通道。

四、400:参数错误占大多数

400 通常能从错误信息直接定位,重点核对:

  • 模型名与版本:从文档复制,不要凭记忆拼后缀。
  • 时长:不同型号范围不同,例如 2.5 为 4–30 秒,2.0 为 4–15 秒,1.0 为 2–12 秒。
  • 分辨率与比例:确认该模型是否支持 4K、21:9 或 adaptive。
  • 参考素材数量:2.5 最多 30 张图、10 段视频、10 段音频;2.0 的上限更低。
  • 图片或视频 URL:必须能被平台访问,不能是内网地址或需要登录的链接。
  • 编辑或续写任务:部分模式只允许自动时长,不能手填秒数。
  • 音频输入:部分版本不支持纯音频输入。

最省时间的做法是用文档示例请求体做最小复现,再逐项加回自己的参数。每加一项测一次,就能定位到具体字段。

五、任务 failed 与 expired

提交成功只代表任务入队。查询结果里常见状态包括 queued、running、succeeded、failed、expired。failed 要看 error 字段:内容审核拒绝、输入素材不合规、参考图无法读取、生成过程异常,处理方式完全不同。

expired 常见于任务超过执行期限仍未完成或未取回结果。文档中 execution_expires_after 可配置,默认 48 小时;任务 ID 保留 7 天。超过期限后继续轮询不会恢复,只能重建任务。使用回调时还要注意:回调未在 5 秒内确认会被重试 3 次,回调接口必须幂等。

审核拒绝不要靠改几个词反复绕过。先确认素材授权、人物肖像、品牌元素和内容政策,再决定是否重提。

六、超时与轮询

超时要分三层:客户端连接超时、单次查询超时、任务总 deadline。提交接口通常很快返回 task_id,真正耗时的是排队和渲染。HTTP 客户端超时设得过短,会把正常提交误判为失败;设得过长,又会拖住线程池。

轮询建议从 5 秒起步,逐步退避到 15–30 秒,并设置总 deadline。支持回调时优先用回调,轮询作为兜底。不要在超时后立刻重复提交,先用业务单号或幂等键检查是否已有任务在运行。

七、结果下载失败

任务成功不等于文件到手。video_url 往往是带签名的临时地址,有有效期;直接存库、几小时后再下载,常见 403 或 404。处理方式:

  • 成功后立即流式下载到自己的对象存储。
  • 下载失败单独重试,不要重建生成任务。
  • 校验 Content-Type、文件大小和可播放性。
  • 日志里不要打印完整签名 URL。

八、示例:带退避与幂等的错误处理

下面的代码把提交、轮询和下载分开处理,只对瞬时错误重试。路径与字段以站内文档为准。

import os, time, random, requests

BASE = os.environ["SEEDANCE_BASE_URL"].rstrip("/")
HEADERS = {
    "Authorization": f"Bearer {os.environ['SEEDANCE_API_KEY']}",
    "Content-Type": "application/json",
}

def submit(payload, max_retries=5):
    # 调用前先按业务单号查任务表,已有 task_id 就直接复用
    for attempt in range(max_retries):
        try:
            r = requests.post(
                f"{BASE}/contents/generations/tasks",
                headers=HEADERS, json=payload, timeout=(5, 30),
            )
        except requests.Timeout:
            time.sleep(min(2 ** attempt, 20) + random.random())
            continue
        if r.status_code == 429 or r.status_code >= 500:
            time.sleep(min(2 ** attempt, 20) + random.random())
            continue
        if r.status_code >= 400:
            raise RuntimeError(f"{r.status_code}: {r.text[:500]}")
        return r.json()["id"]
    raise RuntimeError("submit retries exhausted")

def wait(task_id, deadline_seconds=900):
    deadline = time.time() + deadline_seconds
    delay = 5
    while time.time() < deadline:
        r = requests.get(
            f"{BASE}/contents/generations/tasks/{task_id}",
            headers=HEADERS, timeout=(5, 30),
        )
        if r.status_code == 429 or r.status_code >= 500:
            time.sleep(delay)
            delay = min(delay * 1.5, 30)
            continue
        r.raise_for_status()
        data = r.json()
        if data["status"] == "succeeded":
            return data["content"]["video_url"]
        if data["status"] in ("failed", "expired"):
            raise RuntimeError(f"{data['status']}: {data.get('error')}")
        time.sleep(delay)
        delay = min(delay * 1.5, 30)
    raise TimeoutError(task_id)

def download(url, path):
    with requests.get(url, stream=True, timeout=(5, 120)) as r:
        r.raise_for_status()
        with open(path, "wb") as f:
            for chunk in r.iter_content(1024 * 256):
                f.write(chunk)

这段代码刻意不重试 400、401、403。参数错误和鉴权错误重试多少次都不会成功,只会放大日志和费用。

九、重试、幂等与支持清单

重试只适合瞬时错误:429、5xx、连接超时。400、401、403 和审核拒绝不应自动重试。幂等的关键不是重试次数,而是业务单号:提交前查任务表,提交后落库 task_id,重复请求复用已有任务。

联系服务方前,按下面的清单整理信息:

  1. 记录发生时间、HTTP 状态码、错误码和完整错误信息,注意脱敏。
  2. 记录模型名、时长、分辨率、比例和参考素材数量。
  3. 记录 task_id、业务单号、提交次数和轮询次数。
  4. 保留一条最小复现请求,去掉用户隐私和密钥。
  5. 区分请求失败与任务 failed 或 expired。
  6. 检查素材 URL 是否可公开访问、响应是否过慢。
  7. 确认配额、并发和账号权限,排除 429 与 403。
  8. 附上请求 ID 和复现时间,再提交排查。

常见问题

提交接口返回 200,为什么任务还是失败?

200 只代表任务创建成功。视频生成是异步的,最终状态要看查询结果里的 status 和 error 字段。

429 是不是余额不足?

不一定。429 通常表示速率、并发或配额受限,余额问题可能返回其他错误。先查并发、共享 Key 和重试策略。

任务 failed 后能直接重试吗?

先读 error。审核拒绝和素材不合规,原样重试通常还会失败;网络或上游故障可以退避重试。重试前用业务单号做幂等检查。

轮询多久一次合适?

从 5 秒起步,逐步退避到 15–30 秒,并设置总超时。支持回调时优先用回调,轮询作为兜底。

结果 URL 打不开怎么办?

多数是有效期过期或签名失效。成功后应立即转存对象存储;已经过期则重新查询任务或重建任务,不要反复请求旧链接。

下一步

先在中商起航 AI 网关创建测试 Key,把 401、429、参数错误和任务失败各演练一次,确认告警和重试策略生效。模型可用性与计费方式以模型价格为准。

相关阅读: