> ## 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.

# 快速入门：3D 角色

> 从角色图片提取可下载的 3D 高斯泼溅模型。

创建角色时设置 `type=vsplat`，可提取 3D 高斯泼溅模型（`.vsplat`），而不是用于视频渲染的 2D 角色。就绪后，通过[导出 3D 角色](/zh/v1/api-reference/characters/export)获取签名下载链接。若还想在同一个角色中保留 2D 渲染能力，请改用 `type=all`；仅需 2D 角色时，请参阅[角色快速入门](/zh/v1/guides/quickstart-character)。

设置环境变量 `VIGGLE_API_KEY`，并准备下方示例所需的角色图片。

## 准备工作

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

<Note>
  示例中的 `https://assets.viggle.ai/samples/character.png` 是占位地址。请替换为自己的 `image`/`image_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/characters" \
    -H "Authorization: Bearer $VIGGLE_API_KEY" \
    -F "image_url=https://assets.viggle.ai/samples/character.png" \
    -F "type=vsplat")
  character_id=$(echo "$response" | jq -r '.id')

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

    case "$status" in
      ready)
        exported=$(curl -s "https://apis.viggle.ai/v1/characters/$character_id/export?download_type=vsplat" \
          -H "Authorization: Bearer $VIGGLE_API_KEY")
        echo "$exported" | jq -r '.vsplat_url'
        exit 0
        ;;
      failed)
        echo "Character failed: $(echo "$character" | jq -c '.error')" >&2
        exit 1
        ;;
    esac
    sleep 3
  done

  echo "Timed out waiting for character $character_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 createCharacter() {
    const form = new FormData();
    form.append("image_url", "https://assets.viggle.ai/samples/character.png");
    form.append("type", "vsplat");

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

  const created = await createCharacter();
  const character = await waitForCharacter(created.id);

  if (character.status !== "ready") {
    throw new Error(`Character failed: ${JSON.stringify(character.error)}`);
  }

  const exportResponse = await fetch(
    `${baseUrl}/characters/${character.id}/export?download_type=vsplat`,
    { headers },
  );
  if (!exportResponse.ok) {
    const body = await exportResponse.json().catch(() => ({}));
    throw new Error(`HTTP ${exportResponse.status}: ${JSON.stringify(body.error ?? body)}`);
  }
  const exported = await exportResponse.json();
  console.log(exported.vsplat_url);
  ```

  ```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}/characters",
      headers=headers,
      files={"image_url": (None, "https://assets.viggle.ai/samples/character.png")},
      data={"type": "vsplat"},
  )
  response.raise_for_status()
  character = response.json()

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

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

  exported = requests.get(
      f"{base_url}/characters/{character['id']}/export",
      params={"download_type": "vsplat"},
      headers=headers,
  )
  exported.raise_for_status()
  print(exported.json()["vsplat_url"])
  ```
</CodeGroup>

## 响应

```json theme={null}
{
  "id": "char_550e8400-e29b-41d4-a716-446655440000",
  "status": "ready",
  "download_type": "vsplat",
  "vsplat_url": "https://assets.viggle.ai/results/character.vsplat",
  "thumbnail_url": null,
  "created_at": "2026-07-31T09:15:22+00:00",
  "updated_at": "2026-07-31T09:16:40+00:00",
  "error": null
}
```

每 3 秒轮询 `GET /v1/characters/{character_id}`，直到 `status` 为 `ready` 或 `failed`。就绪后，请求 `GET /v1/characters/{character_id}/export?download_type=vsplat` 获取签名链接 `vsplat_url`。链接有效期为 1 小时，每次调用都会重新签名，请重新获取而非长期缓存。创建时可添加 `enhance=true` 以使用更高质量的提取流程（费用不同，见下方），或添加 `render_thumbnail=true`，以便在同一次导出响应中获取 `thumbnail_url`。

`vsplat`、`enhance` 和 `all` 的费用见[计费与保留期限](/zh/v1/pricing#character-3d)。

<Card title="从图片创建角色" icon="cube" href="/zh/v1/api-reference/characters/create">
  查看全部请求参数，包括 `model_precision`、`filter_low_quality` 和 `joint_set`。
</Card>
