AI 视频 API 错误码:含义与重试时机
视频生成任务比普通 API 调用更容易失败:模型可能繁忙、拒绝提示词或超时。本指南列出你可能收到的所有错误、哪些值得重试,以及如何安全地重试。
错误可能出现的两个环节
视频或图像任务可能在两个时间点出错,你的代码需要同时处理:
- 提交时。
POST /v1/generations会返回 HTTP 错误状态码,以及类似{"error": {"code": "...", "message": "..."}}的响应体。任务没有启动,也不会扣费。 - 运行过程中。提交成功并返回了
202,但任务之后以status: "failed"结束。它的error包含code、一条可以展示给请求者的message,以及retryable。
任何模型上失败的任务都不会扣费。提交时价格会从你的余额中预留,任务失败后即释放。
提交时的 HTTP 错误
| 状态码 | 错误码 | 含义 | 是否重试? |
|---|---|---|---|
| 400 | invalid_request | 请求体不是 JSON,缺少 model 或 input,或者包含 API 无法识别的字段。 | 不要重试。请修正请求体。 |
| 401 | unauthorized | API 密钥缺失或不正确。 | 不要重试。请检查密钥。 |
| 402 | insufficient_credits | 余额不足以支付该任务预留的价格。 | 充值后重试。 |
| 403 | account_disabled, generation_not_enabled | 账户已被暂停,或该密钥只能查询价格,不能生成。 | 不要重试。请联系支持团队。 |
| 404 | not_found | 你的账户下没有该 id 对应的生成任务。 | 不要重试。 |
| 409 | idempotency_conflict | Idempotency-Key 已经配合另一个不同的请求体使用过。 | 换一个新的密钥后重试。 |
| 409 | not_ready | 你在任务成功之前就请求了文件。 | 可以,任务成功后即可。 |
| 413 | body_too_large | JSON 请求体超过 1 MB。请先上传文件,再改为发送链接。 | 不要重试。请缩小请求体。 |
| 422 | invalid_input | 模型不接受的取值,例如它未列出的时长或尺寸。错误信息会指明具体是哪一项。 | 不要重试。请修改该值。 |
| 422 | max_cost_exceeded | 所有线路的费用都高于你设置的 max_cost_usd。 | 不要重试。请调高上限,或降低参数设置。 |
| 422 | model_not_available | 没有该 id 对应的模型。 | 不要重试。请检查 id。 |
| 429 | rate_limited | 同一账户在同一秒内发送的请求过多。 | 可以重试,请在 Retry-After 之后。 |
| 429 | concurrency_limit | 该账户同时运行的任务数已达上限。 | 可以重试,等某个任务结束后即可。 |
| 500 | server_error | 我们这边出了问题。 | 可以重试,请使用相同的 Idempotency-Key。 |
| 503 | busy | 短暂的高负载。 | 可以重试,请在 Retry-After 之后,使用相同的密钥。 |
| 503 | model_unavailable | 该模型目前没有任何可用线路。 | 可以重试,稍等片刻即可。 |
经验法则:429、500 和 503 值得带退避地重试;4xx 范围内除 429 以外的错误,都说明请求本身需要修改。
任务启动后为何会失败
| error.code | retryable | 发生了什么 |
|---|---|---|
content_policy | no | 模型拒绝了提示词或素材,或者扣留了生成结果。reason 会说明原因:sexual、minors、copyright、real_person 或 other。 |
invalid_input | no | 某个参考素材无法读取,或者模型拒绝了某个取值。错误信息会指明具体是哪一项。 |
model_declined | no | 模型用文字作答,而没有生成图片或视频。请在提示词中直接描述你想要的结果。 |
reference_changed | no | 某个参考文件在校验之后发生了变化。请重新发送。 |
max_cost_exceeded | no | 唯一能运行的线路,费用高于你设置的上限。 |
model_busy | yes | 所有线路都已满载。请一分钟后再试。 |
model_unavailable | yes | 目前没有任何线路能接收该任务。 |
timeout | yes | 没有线路在规定时间内返回结果,或者你设置的 max_wait_s 已到。 |
all_routes_failed | yes | 所有线路都因各自一侧的原因而失败。 |
在后台,任务遇到繁忙或故障的线路时,会自动转到下一条线路,因此当你看到 model_busy 或 all_routes_failed 时,所有线路都已经尝试过了。attempts 字段会告诉你尝试了多少条。通常等待一两分钟后再试就够了。
内容拒绝
content_policy 是人们最常遇到的失败。这是由模型而不是 API 决定的,reason 会告诉你触发了哪条规则。常见原因包括:提示词或参考图片中出现知名人物的姓名或面孔(real_person)、品牌和知名角色(copyright),以及任何看起来涉及未成年人的内容。原样重试没有用;请改写提示词或更换图片。各模型的严格程度不同,同一个提示词可能在一个模型上通过,在另一个模型上被拒绝。
一个行为正确的重试循环
这个 Python 辅助函数只重试值得重试的错误,遇到不该重试的就停止。有两个细节很重要:
- 在收到
503或连接中断后重新发送提交请求时,使用相同的Idempotency-Key,这样就绝不会启动第二个付费任务。 - 任务失败之后再次尝试,要使用新的密钥。如果用同一个密钥,只会把那个失败的任务原样返回。
import os
import time
import uuid
import requests
API = "https://api.revana.si/v1"
AUTH = {"Authorization": f"Bearer {os.environ['REVANA_API_KEY']}"}
RETRY_HTTP = {429, 500, 503}
class GenerationError(Exception):
pass
def submit(body):
"""POST a job. Retries 429, 500 and 503 with the same Idempotency-Key, so a retry never starts a second job."""
headers = {**AUTH, "Idempotency-Key": str(uuid.uuid4())}
for attempt in range(6):
r = requests.post(f"{API}/generations", headers=headers, json=body, timeout=60)
if r.ok:
return r.json()
if r.status_code in RETRY_HTTP:
time.sleep(float(r.headers.get("Retry-After", 2 ** attempt)))
continue
err = r.json()["error"] # 400, 401, 402, 403, 409, 413, 422: fix the request, do not retry
raise GenerationError(f"{r.status_code} {err['code']}: {err['message']}")
raise GenerationError("still busy after 6 tries")
def wait(job):
while job["status"] in ("queued", "running"):
time.sleep(5)
job = requests.get(f"{API}/generations/{job['id']}", headers=AUTH).json()
return job
def generate(body, tries=3):
for n in range(tries):
job = wait(submit(body)) # each call uses a new key: a failed job is final
if job["status"] == "succeeded":
return job
err = job["error"]
if not err["retryable"]:
raise GenerationError(f"{err['code']}: {err['message']}")
time.sleep(60 * (n + 1)) # model_busy, timeout and the like: wait, then try again
raise GenerationError(f"gave up after {tries} tries: {err['code']}")
job = generate({
"model": "seedance-2.0-fast",
"input": {"prompt": "Waves roll onto black sand at sunset.", "resolution": "480p", "duration": 5},
"max_cost_usd": 0.3,
})
print(job["output"]["urls"][0], job["cost_usd"])示例任务是一段 5 秒、480p 的 Seedance 2.0 Fast 视频,成功时费用为 $0.25,失败则不收费。max_cost_usd 上限可以避免因设置错误而产生超出预期的费用。
从源头避免错误
- 发送前先查看可选项。
GET /v1/models和模型页面列出了每个模型支持的尺寸、时长和参考素材数量限制。超出这些范围的值会立刻被拒绝,并返回422 invalid_input。 - 上传文件,而不是内嵌文件。
POST /v1/uploads支持最大 30 MB 的图片、50 MB 的视频和 15 MB 的音频,并返回一个有效期 14 天的链接,这样请求体可以保持很小。 - 在代码中检查余额。在大批量任务之前调用
GET /v1/balance,可以避免一连串402错误。额度包见价格页面。
想了解如何降低成功任务的费用,请参阅如何降低 AI 视频 API 成本。完整的错误参考见文档;获取 API 密钥即可试用。
常见问题
AI 视频生成失败时会扣费吗?
不会。提交时价格会被预留,任务无论因何原因失败,预留的额度都会释放。
哪些错误应该重试?
提交时,遇到 429、500 和 503,请在 Retry-After 指定的延迟后重试。任务失败时,当 error.retryable 为 true 就可以重试:model_busy、model_unavailable、timeout 和 all_routes_failed。
content_policy 是什么意思?
模型拒绝了提示词或素材,或者扣留了已生成的内容。reason 字段会说明触发的规则:sexual、minors、copyright、real_person 或 other。请修改请求,而不是直接重试。
如何重试才不会重复扣费?
发送 Idempotency-Key 请求头,并在超时或收到 503 后重新发送时沿用同一个值。你拿到的是同一个生成任务,而不是第二个。
422 invalid_input 是什么意思?
模型不接受你发送的某个值,例如时长、尺寸或参考图数量。错误信息会指出具体字段及其可接受的取值。