参考

生成视频

视频模型与聊天补全不同。生成是异步的:你提交一个任务,拿回一个任务 id,然后轮询结果。目前唯一的视频模型是 doubao-seedance-2-0-pro(火山引擎方舟上的字节跳动 Seedance 2.0 Pro)。

1. 端点

方法路径用途
POSThttps://chinzy.com/v1/videos/tasks提交一个生成任务。立即返回 { "id": "cgt-..." }
GEThttps://chinzy.com/v1/videos/tasks/{taskId}轮询任务状态。原样返回上游负载。

两个端点都使用你在聊天补全中使用的同一个 Authorization: Bearer tsk_... 头。如果你还没有密钥,参见使用你的 API 密钥

任务生命周期。 状态在 queuedrunningsucceeded(或 failedcancelledexpired)之间流转。一段 4 秒 720p 片段的典型渲染时间为 60–180 秒。

2. 提交任务

curl

curl https://chinzy.com/v1/videos/tasks \
  -H "Authorization: Bearer $TOKEN_RELAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedance-2-0-pro",
    "content": [
      {"type": "text", "text": "a calm sunset over rolling hills --rs 720p --dur 4"}
    ],
    "ratio": "16:9"
  }'

请求字段

字段类型说明
modelstring必填。使用 doubao-seedance-2-0-pro。(其他视频别名上线后会出现在这里。)
contentarray必填。每一项都是一个带类型的块:textimage_urlvideo_urlaudio_url。接受内联 base64 data URL。
ratiostring宽高比:16:99:164:33:421:91:1adaptive
resolutionstring480p720p1080p2K。你也可以在提示词中以 --rs 1080p 的形式内联传入。
durationnumber4–15 秒。内联形式:--dur 5
generate_audioboolean是否在视频之外渲染一条音轨。
seednumber可选的可复现性种子。

3. 轮询至完成

# poll-loop.sh
TASK_ID="cgt-..."

while true; do
  RESP=$(curl -s "https://chinzy.com/v1/videos/tasks/$TASK_ID" \
    -H "Authorization: Bearer $TOKEN_RELAY_KEY")
  STATUS=$(echo "$RESP" | jq -r .status)
  echo "[\$(date +%T)] $STATUS"
  case "$STATUS" in
    succeeded)
      echo "$RESP" | jq -r .content.video_url
      break
      ;;
    failed|cancelled|expired)
      echo "$RESP" | jq -r .error
      exit 1
      ;;
  esac
  sleep 10
done

Python

import os, time, requests

BASE = "https://chinzy.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['TOKEN_RELAY_KEY']}"}

def submit(prompt: str, *, ratio="16:9", resolution="720p", duration=4):
    r = requests.post(f"{BASE}/videos/tasks", headers=HEADERS, json={
        "model": "doubao-seedance-2-0-pro",
        "content": [{"type": "text", "text": prompt}],
        "ratio": ratio, "resolution": resolution, "duration": duration,
    })
    r.raise_for_status()
    return r.json()["id"]

def wait(task_id: str, *, timeout=600, interval=10):
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(f"{BASE}/videos/tasks/{task_id}", headers=HEADERS)
        r.raise_for_status()
        body = r.json()
        if body["status"] == "succeeded":
            return body["content"]["video_url"]
        if body["status"] in ("failed", "cancelled", "expired"):
            raise RuntimeError(body.get("error") or body["status"])
        time.sleep(interval)
    raise TimeoutError(task_id)

task_id = submit("a calm sunset over rolling hills")
url = wait(task_id)
print(url)

Node.js

const BASE = 'https://chinzy.com/v1';
const headers = { Authorization: `Bearer ${process.env.TOKEN_RELAY_KEY}` };

async function submit(prompt) {
  const r = await fetch(`${BASE}/videos/tasks`, {
    method: 'POST',
    headers: { ...headers, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'doubao-seedance-2-0-pro',
      content: [{ type: 'text', text: prompt }],
      ratio: '16:9',
      resolution: '720p',
      duration: 4,
    }),
  });
  if (!r.ok) throw new Error(await r.text());
  return (await r.json()).id;
}

async function wait(taskId, { interval = 10_000, timeoutMs = 600_000 } = {}) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const r = await fetch(`${BASE}/videos/tasks/${taskId}`, { headers });
    if (!r.ok) throw new Error(await r.text());
    const body = await r.json();
    if (body.status === 'succeeded') return body.content.video_url;
    if (['failed', 'cancelled', 'expired'].includes(body.status)) {
      throw new Error(body.error?.message ?? body.status);
    }
    await new Promise((res) => setTimeout(res, interval));
  }
  throw new Error('timeout');
}

const taskId = await submit('a calm sunset over rolling hills');
console.log(await wait(taskId));

4. 视频 URL

succeeded 时,响应包含 content.video_url ——一个指向字节跳动对象存储的直链 MP4。关于它有两点需要知道:

  • 它在 24 小时后过期。在该窗口内下载或转存该文件。不要长期保存这个签名 URL 本身。
  • 它绕过中转。下载是客户端到火山引擎的直连;我们从不看到这些字节,因此获取结果不产生任何中转带宽或钱包扣费。

5. 价格与计量

视频按 token 计费,方式与聊天模型相同。token 数会报告在 succeeded 响应的 usage.total_tokens 字段中。粗略来说,一段 4 秒 720p 片段约为 87,000 token(按当前转售汇率约 $0.08)。1080p 和更长的片段大致线性扩展。

结算发生在第一次观察到 succeeded 的轮询时刻。后续轮询免费——中转会幂等地跳过对已结算任务的重复扣费。

6. 所有权与访问

一个任务绑定到创建它的 API 密钥。视频 URL 仅可由同一个密钥取回,期限为 7 天。7 天后任务本身会从上游历史中失效,中转返回 404;在那之前下载好你需要的一切。

7. 常见错误

状态码含义解决
400Body 缺少 modelcontent,或其中之一格式错误。确认 content 是一个由带类型块组成的非空数组。
403 (在 GET 时)你在轮询一个由不同 API 密钥创建的任务。使用创建该任务的同一个密钥。如果你轮换了密钥,请重新提交任务。
404中转不认识该任务 id(拼写错误、7 天后过期,或针对不同部署创建)。重新提交;核对该 id 与创建时返回的一致。
503火山引擎方舟后端不可用,或该部署未配置 DOUBAO_API_KEY退避后重试;若持续,检查你的状态页。

在本文档中发现遗漏或错误?提交一个 issue 或联系管理员。