> ## Documentation Index
> Fetch the complete documentation index at: https://docs.viggle.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 快速入门：动作

> 从驱动视频创建可复用的动作。

从驱动视频创建一次可复用的**动作（Motion）**，之后即可在多次[视频重混](/zh/v1/guides/quickstart-mix)中引用其 ID，无需重复上传视频。本流程使用 `POST /v1/motions` 的 `type=render` 模式；如需提取 `glb`，见 [3D 动作快速入门](/zh/v1/guides/quickstart-mocap)；如需直接从提示词生成动作，见[文本生成动作快速入门](/zh/v1/guides/quickstart-text-to-motion)。

设置环境变量 `VIGGLE_API_KEY`，并准备下方示例所需的动作视频。

## 准备工作

* 在 [Viggle 控制台](https://portal.viggle.ai/keys)创建 API 密钥，并导出为 `VIGGLE_API_KEY`。

<Note>
  示例中的 `https://assets.viggle.ai/samples/motion.mp4` 是占位地址。请替换为自己的 `motion_video`/`motion_video_url`，或使用 Viggle 正式发布的示例素材。
</Note>

## 完整示例

<CodeGroup>
  ```bash cURL theme={null}
  #!/usr/bin/env bash
  set -euo pipefail

  response=$(curl -s -X POST "https://apis.viggle.ai/v1/motions" \
    -H "Authorization: Bearer $VIGGLE_API_KEY" \
    -F "motion_video_url=https://assets.viggle.ai/samples/motion.mp4" \
    -F "name=Quickstart motion")
  motion_id=$(echo "$response" | jq -r '.id')

  for _ in $(seq 1 60); do
    motion=$(curl -s "https://apis.viggle.ai/v1/motions/$motion_id" \
      -H "Authorization: Bearer $VIGGLE_API_KEY")
    status=$(echo "$motion" | jq -r '.status')

    case "$status" in
      ready)
        echo "$motion_id"
        exit 0
        ;;
      failed)
        echo "Motion failed: $(echo "$motion" | jq -c '.error')" >&2
        exit 1
        ;;
    esac
    sleep 3
  done

  echo "Timed out waiting for motion $motion_id" >&2
  exit 1
  ```

  ```javascript Node theme={null}
  const baseUrl = "https://apis.viggle.ai/v1";
  const headers = { Authorization: `Bearer ${process.env.VIGGLE_API_KEY}` };

  async function createMotion() {
    const form = new FormData();
    form.append("motion_video_url", "https://assets.viggle.ai/samples/motion.mp4");
    form.append("name", "Quickstart motion");

    const response = await fetch(`${baseUrl}/motions`, {
      method: "POST",
      headers,
      body: form,
    });
    if (!response.ok) {
      const body = await response.json().catch(() => ({}));
      throw new Error(`HTTP ${response.status}: ${JSON.stringify(body.error ?? body)}`);
    }
    return response.json();
  }

  async function waitForMotion(id) {
    for (let attempt = 0; attempt < 60; attempt++) {
      const response = await fetch(`${baseUrl}/motions/${id}`, { headers });
      if (!response.ok) {
        const body = await response.json().catch(() => ({}));
        throw new Error(`HTTP ${response.status}: ${JSON.stringify(body.error ?? body)}`);
      }
      const motion = await response.json();
      if (["ready", "failed"].includes(motion.status)) return motion;
      await new Promise((resolve) => setTimeout(resolve, 3000));
    }
    throw new Error(`Timed out waiting for motion ${id}`);
  }

  const created = await createMotion();
  const motion = await waitForMotion(created.id);

  if (motion.status !== "ready") {
    throw new Error(`Motion failed: ${JSON.stringify(motion.error)}`);
  }
  console.log(motion.id);
  ```

  ```python Python theme={null}
  import os
  import time
  import requests

  base_url = "https://apis.viggle.ai/v1"
  headers = {"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"}

  response = requests.post(
      f"{base_url}/motions",
      headers=headers,
      files={"motion_video_url": (None, "https://assets.viggle.ai/samples/motion.mp4")},
      data={"name": "Quickstart motion"},
  )
  response.raise_for_status()
  motion = response.json()

  for _ in range(60):
      if motion["status"] in {"ready", "failed"}:
          break
      time.sleep(3)
      response = requests.get(f"{base_url}/motions/{motion['id']}", headers=headers)
      response.raise_for_status()
      motion = response.json()
  else:
      raise TimeoutError(f"Timed out waiting for motion {motion['id']}")

  if motion["status"] != "ready":
      error = motion.get("error") or {}
      raise RuntimeError(f"failed: {error.get('code')} {error.get('message')}")

  print(motion["id"])
  ```
</CodeGroup>

## 响应

```json theme={null}
{
  "id": "mot_550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "name": "Quickstart motion",
  "progress": 0,
  "capabilities": [],
  "created_at": "2026-07-21T10:00:00+00:00",
  "completed_at": null,
  "error": null,
  "type": "render",
  "vsplat": null,
  "glb": null
}
```

每 3 秒轮询 `GET /v1/motions/{motion_id}`，直到 `status` 为 `ready` 或 `failed`。就绪后，`id` 就是**可复用的动作 ID**。在[渲染视频](/zh/v1/api-reference/renders/create)或[视频重混快速入门](/zh/v1/guides/quickstart-mix)中将其作为 `motion_id` 使用，即可反复渲染，无需重新上传源视频。

费用见[计费与保留期限](/zh/v1/pricing#motion)：`type=render` 的动作不单独收取预处理费。

<Card title="从视频创建动作" icon="film" href="/zh/v1/api-reference/motions/create">
  查看全部请求和响应字段，包括 `glb`/`all` 提取选项。
</Card>
