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

# Voice Persona

> 从您自己的录音创建经过验证的声音角色

通过语音验证创建个性化的 Voice Persona。这是一个**单任务、两阶段**的流程：`init`（上传语音 + 获取验证短语）→ `complete`（上传验证录音 + 创建角色）。整个流程使用**同一个 `taskId`**。

## 工作流程

```
 用户的语音音频
      │
      ▼
 ① voicePersona/init
      │  上传语音 → 提取人声 → 返回验证短语
      │
      │  返回: { taskId }
      │  轮询: GET /suno/v2/status?taskId=xxx
      │  等待 status == "awaiting"
      │  data: { phrase_text, ... }
      │
      ▼
 用户朗读 phrase_text 并录音（30 秒超时限制内）
      │
      ▼
 ② voicePersona/complete（使用相同 taskId）
      │  上传验证录音 → 语音验证 → 创建角色
      │
      │  轮询同一 taskId: GET /suno/v2/status?taskId=xxx
      │  等待 status == "success"
      │  data: 角色详情
      ▼
    完成 → 在 /generate 中使用角色
```

### 任务状态流

```
queued → running → awaiting → running → success
                      │                    │
                      │                    └── complete 失败 → failed
                      └── 用户超时 → failed (VP_USER_TIMEOUT)
```

<Warning>
  任务达到 `awaiting` 状态后，您必须在 **30 秒**（默认）内调用 `complete`。超时后任务将失败并返回 `VP_USER_TIMEOUT`，您需要从 `init` 重新开始。
</Warning>

***

## 第一步：Init — 上传语音并获取验证短语

上传用户的语音音频。系统将提取人声并返回一段用户需要朗读的验证短语。

<Info>
  这是一个异步任务。使用返回的 `taskId` 轮询[获取任务状态](/zh/api-reference/suno/task)。等待状态变为 **`awaiting`**（不是 `success`）。
</Info>

### 请求

```
POST /suno/v2/voicePersona/init
```

| 字段                | 类型           | 必填 | 描述                                                       |
| ----------------- | ------------ | -- | -------------------------------------------------------- |
| `voice_audio_url` | string (URL) | 是  | 可公开下载的语音音频 URL（WAV/MP3）                                  |
| `language`        | string       | 是  | 验证短语语言：`zh` `en` `ja` `ko` `es` `fr` `de` `pt` `ru` `hi` |
| `vocal_start_s`   | number       | 否  | 人声提取开始时间（秒），默认：0                                         |
| `vocal_end_s`     | number       | 否  | 人声提取结束时间（秒），默认：自动检测                                      |

### 轮询结果（status: awaiting）

当任务达到 `awaiting` 状态时，`data` 包含：

| 字段                   | 描述                        |
| -------------------- | ------------------------- |
| `phrase_text`        | **验证短语文本** — 用户必须朗读并录制此文本 |
| `phrase_id`          | 验证短语 ID（内部使用）             |
| `vox_audio_id`       | 提取的人声音频 ID（内部使用）          |
| `voice_recording_id` | 录音 ID（内部使用）               |
| `vocal_start_s`      | 人声开始时间（秒）                 |
| `vocal_end_s`        | 人声结束时间（秒）                 |

<Tip>
  用户只需要 `phrase_text`。所有其他字段由系统内部使用 — 您**无需**将它们传给 `complete` 步骤。
</Tip>

参见 [Init API 参考 →](/zh/api-reference/suno/voicePersonaInit)

***

## 第二步：Complete — 上传验证录音并创建角色

用户朗读 `phrase_text` 并录制后，**使用相同的 `taskId`** 上传验证录音，完成语音验证并创建角色。

<Info>
  使用 init 返回的**相同 `taskId`**。调用 complete 后，继续轮询同一个 taskId 直到状态变为 `success`。
</Info>

### 请求

```
POST /suno/v2/voicePersona/complete
```

| 字段                       | 类型            | 必填 | 描述                    |
| ------------------------ | ------------- | -- | --------------------- |
| `taskId`                 | string (UUID) | 是  | init 返回的 taskId（同一任务） |
| `verification_audio_url` | string (URL)  | 是  | 用户的验证录音 URL（WAV/MP3）  |
| `name`                   | string        | 是  | 角色名称                  |
| `description`            | string        | 否  | 角色描述                  |
| `is_public`              | boolean       | 否  | 是否公开（默认：false）        |
| `image_s3_id`            | string        | 否  | 封面图片（base64），未提供则自动生成 |

<Tip>
  不需要中间数据（`vox_audio_id`、`phrase_id` 等）— 系统会自动从 init 阶段读取这些数据。
</Tip>

参见 [Complete API 参考 →](/zh/api-reference/suno/voicePersonaComplete)

***

## 完整示例

<CodeGroup>
  ```javascript Node.js theme={null}
  const API_BASE = 'https://api.mountsea.ai';
  const headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-api-key'
  };

  async function pollTask(taskId, targetStatus = 'success') {
    while (true) {
      const res = await fetch(`${API_BASE}/suno/v2/status?taskId=${taskId}`, { headers });
      const task = await res.json();
      if (task.status === targetStatus) return task.data;
      if (task.status === 'success') return task.data;
      if (task.status === 'failed') throw new Error(task.failReason);
      await new Promise(r => setTimeout(r, 3000));
    }
  }

  // Step 1: Init — upload voice and get verification phrase
  const initRes = await fetch(`${API_BASE}/suno/v2/voicePersona/init`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      voice_audio_url: 'https://example.com/my-voice.wav',
      language: 'zh'
    })
  });
  const { taskId } = await initRes.json();

  // Poll until status is "awaiting"
  const initData = await pollTask(taskId, 'awaiting');
  console.log('Please read aloud:', initData.phrase_text);

  // → User records themselves reading the phrase ...

  // Step 2: Complete — upload verification recording (same taskId)
  await fetch(`${API_BASE}/suno/v2/voicePersona/complete`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      taskId,
      verification_audio_url: 'https://example.com/verification.wav',
      name: 'My Voice',
      description: '我的专属声音'
    })
  });

  // Poll the SAME taskId until status is "success"
  const persona = await pollTask(taskId, 'success');
  console.log('Voice Persona created:', persona);
  ```

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

  API_BASE = 'https://api.mountsea.ai'
  headers = {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer your-api-key'
  }

  def poll_task(task_id, target_status='success'):
      while True:
          res = requests.get(f'{API_BASE}/suno/v2/status', params={'taskId': task_id}, headers=headers)
          task = res.json()
          if task['status'] == target_status:
              return task['data']
          if task['status'] == 'success':
              return task['data']
          if task['status'] == 'failed':
              raise Exception(task.get('failReason', 'Unknown error'))
          time.sleep(3)

  # Step 1: Init
  init_res = requests.post(f'{API_BASE}/suno/v2/voicePersona/init', headers=headers, json={
      'voice_audio_url': 'https://example.com/my-voice.wav',
      'language': 'zh'
  })
  task_id = init_res.json()['taskId']

  # Poll until "awaiting"
  init_data = poll_task(task_id, target_status='awaiting')
  print(f"Please read aloud: {init_data['phrase_text']}")

  # ... user records the phrase ...

  # Step 2: Complete (same taskId)
  requests.post(f'{API_BASE}/suno/v2/voicePersona/complete', headers=headers, json={
      'taskId': task_id,
      'verification_audio_url': 'https://example.com/verification.wav',
      'name': 'My Voice',
      'description': '我的专属声音'
  })

  # Poll the SAME taskId until "success"
  persona = poll_task(task_id, target_status='success')
  print(f"Voice Persona created: {persona}")
  ```

  ```bash cURL theme={null}
  # 1. Init
  curl -X POST "https://api.mountsea.ai/suno/v2/voicePersona/init" \
    -H "Authorization: Bearer your-api-key" \
    -H "Content-Type: application/json" \
    -d '{"voice_audio_url":"https://example.com/voice.wav","language":"zh"}'
  # → {"taskId":"abc-123"}

  # 2. Poll until status == "awaiting"
  curl "https://api.mountsea.ai/suno/v2/status?taskId=abc-123" \
    -H "Authorization: Bearer your-api-key"
  # → {"status":"awaiting","data":{"phrase_text":"风吹过山谷带来了远方的消息",...}}

  # 3. Complete (same taskId, after user records the phrase)
  curl -X POST "https://api.mountsea.ai/suno/v2/voicePersona/complete" \
    -H "Authorization: Bearer your-api-key" \
    -H "Content-Type: application/json" \
    -d '{"taskId":"abc-123","verification_audio_url":"https://example.com/verify.wav","name":"My Voice"}'
  # → {"taskId":"abc-123"}

  # 4. Poll same taskId until status == "success"
  curl "https://api.mountsea.ai/suno/v2/status?taskId=abc-123" \
    -H "Authorization: Bearer your-api-key"
  # → {"status":"success","data":{"id":"persona-xxx","name":"My Voice",...}}
  ```
</CodeGroup>

***

## 错误码

| 状态码 | 错误                                    | 描述                              |
| --- | ------------------------------------- | ------------------------------- |
| 400 | VP\_TASK\_NOT\_FOUND                  | taskId 不存在或不是 Voice Persona 任务  |
| 400 | VP\_INVALID\_STATUS                   | 任务状态不是 `awaiting`，无法调用 complete |
| 408 | VP\_USER\_TIMEOUT                     | init 后等待 complete 超时（默认 30 秒）   |
| 409 | VP\_SESSION\_EXPIRED                  | 验证会话已过期，需从 init 重新开始            |
| 500 | VP\_LOCK\_EXPIRED                     | 内部锁已过期（请重试）                     |
| 503 | VP\_NO\_DEDICATED\_ACCOUNT\_AVAILABLE | 没有可用的专用账户                       |
| 503 | VP\_ALL\_ACCOUNTS\_BUSY               | 所有账户队列已满，请稍后重试                  |
| 504 | VP\_ORPHAN\_TIMEOUT                   | 任务排队超时                          |

## 重要说明

<Warning>
  验证录音必须清晰包含完整的 `phrase_text` 内容。不完整或不清晰的录音将导致语音验证失败。
</Warning>

* **单一 taskId 生命周期**：init 和 complete 使用相同的 `taskId` — 在整个流程中轮询同一个任务。
* **`awaiting` 状态**：init 完成后，任务状态为 `awaiting`（不是 `success`）。`data` 字段包含供用户朗读的 `phrase_text`。
* **30 秒时间限制**：任务达到 `awaiting` 后，您必须在 30 秒内调用 `complete`。超时将导致 `VP_USER_TIMEOUT`。
* **简化参数**：`complete` 只需要 `taskId` + 验证录音 URL + 角色信息。所有中间数据由系统自动填充。
* **同一账户保证**：两个阶段自动使用相同的 Suno 账户。
* **语言选择**：`language` 决定验证短语的语言。为获得最佳效果，请选择与原始语音音频匹配的语言。
* **处理时间**：Init 大约需要 20-60 秒（包含人声提取）；Complete 大约需要 10-30 秒（包含语音验证）。
* **并发安全**：系统对每个账户的 Voice Persona 操作进行串行化 — 不同用户的并发请求不会相互干扰。
