跳到正文
文档版本 r15
OpenAPIJSONYAML
指南 / 生成第一个视频

生成第一个视频

在你自己的服务器上跑通一次完整调用:提交、等待、下载,最后核对费用。示例请求一段五秒的 Seedance 2.0 Fast 视频。

Markdown 文件在 Playground 试运行文档版本 r15

开始前

需要一个已验证邮箱、有可用 Credits 的账户,项目已开通要调用的模型。在服务端设置两个环境变量:

变量用途
VIZO_API_KEY项目 Key,需要 models:read、generations:create、generations:read、generations:list(回包丢失后用来找回任务)和 assets:download。
VIZO_BILLING_KEY只读账单 Key,需要 billing:read,只在最后核对费用时使用。
  1. 获取 API Key

    GET/v1/models

    在控制台的 API Keys 创建项目 Key。完整 Key 只显示一次,请立即存进服务端的密钥管理。不要把 Key 放进网页、App 或代码库。

    用 listModels 确认 Key 可用。返回的每一项是项目可以下单的一个规格,包含 modelId、operation、inputSchema 和参考价。

    cURL
    # Keep the key on the server only
    export VIZO_API_KEY="va_xxxxxxxx"
    
    curl https://api.vizoapi.com/v1/models \
      -H "Authorization: Bearer $VIZO_API_KEY"
    Python
    import os, requests
    
    api = "https://api.vizoapi.com/v1"
    headers = {"Authorization": f"Bearer {os.environ['VIZO_API_KEY']}"}
    
    models = requests.get(f"{api}/models", headers=headers, timeout=30).json()
    for m in models["entries"]:
        print(m["modelId"], m["operation"])
    Node.js
    const api = "https://api.vizoapi.com/v1";
    const headers = {
      Authorization: `Bearer ${process.env.VIZO_API_KEY}`,
      "Content-Type": "application/json",
    };
    
    const models = await fetch(`${api}/models`, { headers }).then((r) => r.json());
    console.log(models.entries.map((m) => `${m.modelId} ${m.operation}`));
    响应200
    {
      "entries": [
        {
          "modelId": "seedance-2-0-fast",
          "operation": "text-to-video",
          "routeId": "seedance-2-0-fast-720p",
          "inputSchema": {
            "…": "…"
          }
        }
      ],
      "next_cursor": ""
    }
  2. 提交生成

    POST/v1/generations

    把 model、operation 和 input 直接发给 POST /v1/generations。返回 202 表示任务已受理,请立即保存返回的 id。

    新任务可能先处于 pending_funding:额度确认前不会冻结任何 Credits,确认后变为 queued。额度被拒绝时,任务会关闭,error.code 为 insufficient_balance、budget_exceeded 或 spending_blocked。

    把 metadata.client_reference 设为你自己的订单号。回包丢失时,可以用它找回任务。

    创建请求只发送一次。每个被受理的 POST 都是独立任务,会分别冻结和扣费,内容完全相同的第二次提交也一样。请关闭 HTTP 客户端和代理对这个请求的自动重试。

    超时或断连时先查询,再决定是否重新提交。已经拿到任务 ID,就用 getGeneration 查询原任务。没有拿到 ID,就用 listGenerations 查看近期任务,按 metadata.client_reference 找回它。列表按创建时间从新到旧排列,可用 model、source、status、created_after(含)和 created_before(不含)缩小范围。

    cURL
    # Send once: do not let a client or proxy retry this request
    curl https://api.vizoapi.com/v1/generations \
      -H "Authorization: Bearer $VIZO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "seedance-2-0-fast",
        "operation": "text-to-video",
        "input": { "prompt": "池塘里漂着一只纸船", "duration_seconds": 5 },
        "metadata": { "client_reference": "order-1042" }
      }'
    Python
    import os, requests
    
    api = "https://api.vizoapi.com/v1"
    headers = {"Authorization": f"Bearer {os.environ['VIZO_API_KEY']}"}
    
    # Send once: do not retry this request automatically
    task = requests.post(f"{api}/generations", headers=headers, json={
        "model": "seedance-2-0-fast",
        "operation": "text-to-video",
        "input": {"prompt": "池塘里漂着一只纸船", "duration_seconds": 5},
        "metadata": {"client_reference": "order-1042"},
    }, timeout=60).json()
    
    task_id = task["id"]  # save it now
    Node.js
    const api = "https://api.vizoapi.com/v1";
    const headers = {
      Authorization: `Bearer ${process.env.VIZO_API_KEY}`,
      "Content-Type": "application/json",
    };
    
    // Send once: do not retry this request automatically
    const task = await fetch(`${api}/generations`, {
      method: "POST",
      headers,
      body: JSON.stringify({
        model: "seedance-2-0-fast",
        operation: "text-to-video",
        input: { prompt: "池塘里漂着一只纸船", duration_seconds: 5 },
        metadata: { client_reference: "order-1042" },
      }),
    }).then((r) => r.json());
    
    const taskId = task.id; // save it now
    响应202
    {
      "id": "gen_01JD8Z5QW3XR",
      "status": "pending_funding",
      "execution_status": "pending_funding",
      "delivery_status": "pending",
      "billing_status": "pending",
      "version": 1,
      "price": {
        "unit": "credit",
        "reserved": "0.000",
        "quoted": "1400.000",
        "charged": "0.000"
      },
      "allowed_actions": [
        "cancel"
      ],
      "customer_resolution_deadline": "2026-11-03T08:00:12Z",
      "created_at": "2026-11-02T08:00:12Z",
      "model": "seedance-2-0-fast",
      "source": "api",
      "closed_at": null,
      "late_result_available": false,
      "error": null,
      "result": null,
      "metadata": {
        "client_reference": "order-1042"
      }
    }
  3. 等待结果

    GET/v1/generations/{id}

    轮询原任务,直到 closed_at 不为空。任务有三个独立状态,分别看:

    • execution_status:模型生成进度
    • delivery_status:结果是否已保存,ready 表示可以下载
    • billing_status:冻结中、已扣费或已释放

    closed_at 有值而 status 不是 succeeded 时,没有可下载的结果。读取 error.code 查看原因,例如 insufficient_balance、budget_exceeded 或 spending_blocked。没有拿到额度的任务,charged 为 0.000。

    不想轮询?在控制台创建回调端点,提交时传 webhook_endpoint_id。收到通知后先验签,再用任务 ID 查询最新状态,以查询结果为准。

    cURL
    curl https://api.vizoapi.com/v1/generations/$GENERATION_ID \
      -H "Authorization: Bearer $VIZO_API_KEY"
    Python
    import os, requests
    
    api = "https://api.vizoapi.com/v1"
    headers = {"Authorization": f"Bearer {os.environ['VIZO_API_KEY']}"}
    import time
    
    while True:
        task = requests.get(f"{api}/generations/{task_id}", headers=headers, timeout=30).json()
        if task["closed_at"]:
            break
        time.sleep(5)
    
    if task["status"] != "succeeded":
        # Closed without a result, for example insufficient_balance, budget_exceeded or spending_blocked
        raise SystemExit(f"task closed: {task['status']} {(task['error'] or {}).get('code')}")
    print(task["status"], task["price"]["charged"])
    Node.js
    const api = "https://api.vizoapi.com/v1";
    const headers = {
      Authorization: `Bearer ${process.env.VIZO_API_KEY}`,
      "Content-Type": "application/json",
    };
    
    let task;
    do {
      await new Promise((r) => setTimeout(r, 5000));
      task = await fetch(`${api}/generations/${taskId}`, { headers }).then((r) => r.json());
    } while (!task.closed_at);
    
    if (task.status !== "succeeded") {
      // Closed without a result, for example insufficient_balance, budget_exceeded or spending_blocked
      throw new Error(`task closed: ${task.status} ${task.error?.code}`);
    }
    console.log(task.status, task.price.charged);
    响应200
    {
      "id": "gen_01JD8Z5QW3XR",
      "status": "succeeded",
      "execution_status": "succeeded",
      "delivery_status": "ready",
      "billing_status": "settled",
      "version": 4,
      "price": {
        "unit": "credit",
        "reserved": "1400.000",
        "quoted": "1400.000",
        "charged": "1400.000"
      },
      "allowed_actions": [],
      "customer_resolution_deadline": "2026-11-03T08:00:12Z",
      "created_at": "2026-11-02T08:00:12Z",
      "model": "seedance-2-0-fast",
      "source": "api",
      "closed_at": "2026-11-02T08:01:43Z",
      "late_result_available": false,
      "error": null,
      "result": {
        "asset_id": "ast_01JD8Z9M2C4V",
        "outputs": [
          {
            "index": 0,
            "asset_id": "ast_01JD8Z9M2C4V",
            "content_type": "video/mp4"
          }
        ]
      },
      "metadata": {
        "client_reference": "order-1042"
      }
    }
  4. 下载结果

    GET/v1/assets/{id}/download

    用 result.asset_id 申请下载授权,返回一个短期有效的 url。再单独请求这个链接拿到文件,不要附带 API Key。链接过期后,对同一个素材重新申请即可。

    一次任务有多个结果时,按 result.outputs 的顺序逐个下载。成品保存 7 天,可以用 deleteAsset 提前删除。

    cURL
    # 1. Ask for a short-lived download URL
    curl https://api.vizoapi.com/v1/assets/$ASSET_ID/download \
      -H "Authorization: Bearer $VIZO_API_KEY"
    
    # 2. Fetch the returned url on its own, without the API key
    curl -o result.mp4 "$DOWNLOAD_URL"
    Python
    import os, requests
    
    api = "https://api.vizoapi.com/v1"
    headers = {"Authorization": f"Bearer {os.environ['VIZO_API_KEY']}"}
    
    if task["status"] != "succeeded" or not task["result"]:
        raise SystemExit("no result to download: read task['error']['code']")
    asset_id = task["result"]["asset_id"]
    dl = requests.get(f"{api}/assets/{asset_id}/download", headers=headers, timeout=30).json()
    
    # Fetch the signed URL on its own, without the API key
    with requests.get(dl["url"], stream=True, timeout=300) as r, open("result.mp4", "wb") as f:
        for chunk in r.iter_content(1 << 20):
            f.write(chunk)
    Node.js
    import { writeFile } from "node:fs/promises";
    
    if (task.status !== "succeeded" || !task.result) throw new Error("no result to download: read task.error.code");
    const dl = await fetch(`${api}/assets/${task.result.asset_id}/download`, { headers }).then((r) => r.json());
    
    // Fetch the signed URL on its own, without the API key
    const media = await fetch(dl.url);
    await writeFile("result.mp4", Buffer.from(await media.arrayBuffer()));
    响应200
    {
      "url": "https://media.vizoapi.com/d/ast_01JD8Z9M2C4V?sig=…",
      "url_expires_at": "2026-11-02T08:16:43Z"
    }
  5. 核对费用

    GET/v1/ledger

    用只读账单 Key 读取 /v1/ledger,沿着 next_cursor 读完所有分页。把这个任务的结算分录减去冲正分录,结果应等于任务的 price.charged。

    生成失败或超时的任务,charged 为 0.000,冻结额度已经退回可用余额。

    cURL
    curl "https://api.vizoapi.com/v1/ledger?limit=100" \
      -H "Authorization: Bearer $VIZO_BILLING_KEY"
    
    # When next_cursor is not empty, read the next page
    curl "https://api.vizoapi.com/v1/ledger?limit=100&cursor=$NEXT_CURSOR" \
      -H "Authorization: Bearer $VIZO_BILLING_KEY"
    Python
    import os, requests
    
    api = "https://api.vizoapi.com/v1"
    billing = {"Authorization": f"Bearer {os.environ['VIZO_BILLING_KEY']}"}
    
    entries, cursor = [], ""
    while True:
        page = requests.get(f"{api}/ledger", headers=billing, timeout=30,
                            params={"limit": 100, "cursor": cursor}).json()
        entries += page["items"] or []
        cursor = page.get("next_cursor")
        if not cursor:
            break
    Node.js
    const api = "https://api.vizoapi.com/v1";
    const billing = { Authorization: `Bearer ${process.env.VIZO_BILLING_KEY}` };
    
    const entries = [];
    let cursor = "";
    do {
      const page = await fetch(`${api}/ledger?limit=100&cursor=${cursor}`, { headers: billing }).then((r) => r.json());
      entries.push(...(page.items ?? []));
      cursor = page.next_cursor;
    } while (cursor);
    响应200
    {
      "items": [
        {
          "id": "le_01JD8ZA1T6",
          "reference": "gen_01JD8Z5QW3XR",
          "from": "wallet:held",
          "to": "platform:settled",
          "credits": "1400.000",
          "created_at": "2026-11-02T08:01:43Z"
        }
      ],
      "next_cursor": ""
    }

示例响应中的金额和 ID 仅作演示,你的请求会返回各自的值。

接下来