# 音频生成 API

通过 POST /api/v1/speech 与 POST /api/v1/sound-effects 实现文本转语音和音效生成，以及公开的音色目录。

语音和音效会在请求返回前生成完毕：响应通常已带 `status: "success"`，音频 URL 在 `data.taskUrls` 中。请保持连接直到收到响应。返回的 `data.id` 同样可用于 [`GET /api/v1/tasks/{taskId}`](https://viewmax.studio/zh/docs/api/tasks)，便于统一轮询流程。

## 列出音色

`GET /api/v1/voices` 是公开端点（无需 API key），最多缓存一小时。

```bash
curl https://viewmax.studio/api/v1/voices
```

```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "default_model": "eleven_v3",
    "characters_per_credit": 20,
    "minimum_credits": 1,
    "voices": [
      {
        "id": "EXAVITQu4vr4xnSDxMaL",
        "name": "Sarah",
        "gender": "female",
        "language": "en",
        "languages": ["en", "fr", "ar", "zh", "es", "hi"],
        "accent": "american",
        "use_case": "entertainment_tv",
        "description": "Young adult woman with a confident and warm, mature quality and a reassuring, professional tone.",
        "preview_url": "https://…/preview.mp3",
        "recommended_model": "eleven_v3"
      }
    ]
  }
}
```

| 字段                    | 说明                                                                                                                                             |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `default_model`         | 音色没有 `recommended_model` 时使用的模型                                                                                                        |
| `characters_per_credit` | 语音费用为 `max(minimum_credits, ceil(文本字符数 / characters_per_credit))`                                                                      |
| `minimum_credits`       | 单次语音请求的最低价格                                                                                                                           |
| `voices[]`              | `id`（作为 `voice_id` 发送）、`name`、`gender`、`language`、`languages`、`accent`、`use_case`、`description`、`preview_url`、`recommended_model` |

## 文本转语音

`POST /api/v1/speech` 用所选音色将文本转为语音。费用为 `max(1, ceil(字符数 / 20))` 积分，按去掉首尾空白后的文本计算：19 个字符为 1 积分，1,000 个字符为 50 积分。套餐额度不适用于 API 的语音生成。

| 字段       | 必填 | 类型   | 说明                                  |
| ---------- | ---- | ------ | ------------------------------------- |
| `text`     | 是   | string | 非空的待朗读文本，首尾空白会被去掉    |
| `voice_id` | 是   | string | 来自 `GET /api/v1/voices` 的音色 `id` |
| `options`  | 否   | object | 见下表                                |

| 选项               | 类型   | 说明                                                                             |
| ------------------ | ------ | -------------------------------------------------------------------------------- |
| `model_id`         | string | 默认使用音色的 `recommended_model`。未知值会在提供方处失败（任务失败并退还积分） |
| `speed`            | number | 0.25–4；超出范围的值会被截断到边界                                               |
| `stability`        | number | 0–1；超出范围的值会被截断到边界                                                  |
| `similarity_boost` | number | 0–1；超出范围的值会被截断到边界                                                  |

```bash
curl -X POST https://viewmax.studio/api/v1/speech \
  -H "Authorization: Bearer $VIEWMAX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: welcome-001" \
  -d '{
    "text": "Welcome to ViewMax.",
    "voice_id": "EXAVITQu4vr4xnSDxMaL",
    "options": { "speed": 1.0 }
  }'
```

## 音效

`POST /api/v1/sound-effects` 根据文本提示生成音效，每次 5 积分。

| 字段      | 必填 | 类型   | 说明           |
| --------- | ---- | ------ | -------------- |
| `prompt`  | 是   | string | 非空的声音描述 |
| `options` | 否   | object | 见下表         |

| 选项               | 类型   | 说明                    |
| ------------------ | ------ | ----------------------- |
| `duration_seconds` | number | 0.5–22 之间的 JSON 数字 |
| `prompt_influence` | number | 0–1 之间的 JSON 数字    |

超出这些范围时会在扣费前返回 400。

```bash
curl -X POST https://viewmax.studio/api/v1/sound-effects \
  -H "Authorization: Bearer $VIEWMAX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: thunder-001" \
  -d '{ "prompt": "A distant rolling thunder clap", "options": { "duration_seconds": 5 } }'
```

## 成功响应

语音示例：

```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "9d1f3b5c-7e9a-4c2d-8f1b-3a5c7e9b1d24",
    "userId": "usr_123",
    "mediaType": "audio",
    "model": "eleven_v3",
    "prompt": "Welcome to ViewMax.",
    "options": "{\"voice_id\":\"EXAVITQu4vr4xnSDxMaL\",\"speed\":1}",
    "status": "success",
    "createdAt": "2026-09-29T08:00:00.000Z",
    "updatedAt": "2026-09-29T08:00:03.000Z",
    "deletedAt": null,
    "taskId": "sync:9d1f3b5c-7e9a-4c2d-8f1b-3a5c7e9b1d24",
    "taskInfo": null,
    "taskResult": "{\"output\":[\"https://cdn.example.com/audio/speech.mp3\"]}",
    "taskUrls": ["https://cdn.example.com/audio/speech.mp3"],
    "costCredits": 1,
    "scene": "text-to-speech",
    "visibility": "user",
    "creditId": "cr_246",
    "waivedCredits": 0,
    "fairUseKey": null
  }
}
```

音效返回相同结构，其中 `scene` 为 `"text-to-sound-effects"`，`costCredits` 为 `5`。字段说明见[视频生成 API](https://viewmax.studio/zh/docs/api/video-generation)中的**任务字段**。`taskUrls` 始终是数组。

## 幂等与退款

- 发送 `Idempotency-Key`（1–200 个可见 ASCII 字符，不含空格）。重复使用同一个键会返回原任务及其 `costCredits`，不会再次扣费；服务端不比较请求体，同一账号的所有创建端点共用这些键。超时后用同一个键重试；有意发起新尝试时换用新键。不带该请求头时，每次 POST 都会创建新任务并扣费。
- 任务创建时扣除积分。余额低于费用时返回 402，不创建任务。以 `failed` 或 `canceled` 结束的任务会自动退还积分。

## 错误

所有错误都使用[错误信封](https://viewmax.studio/zh/docs/api/errors)。

| HTTP | `error.type`           | `message` 示例                                                                            | 处理方式                                                                                            |
| ---- | ---------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| 400  | `invalid_request`      | `text is required` / `voice_id is required` / `prompt is required`                        | 补充必填字段                                                                                        |
| 400  | `invalid_request`      | `unknown voice_id: abc. Call GET /api/v1/voices (MCP: list_voices) for available voices.` | 使用 `GET /api/v1/voices` 中的音色 `id`                                                             |
| 400  | `invalid_request`      | `options.duration_seconds must be a number from 0.5 to 22`                                | 发送范围内的 JSON 数字（音效）                                                                      |
| 400  | `invalid_request`      | `options.prompt_influence must be a number from 0 to 1`                                   | 发送范围内的 JSON 数字（音效）                                                                      |
| 400  | `invalid_request`      | `the generation request uses unsupported settings`，并带 `error.task_id`                  | 提供方拒绝了这些设置，任务已失败并退还积分。读取该任务的 `taskInfo.errorMessage`，调整后用新键重试  |
| 400  | `content_rejected`     | `input content was rejected by safety filters`                                            | 修改文本或提示词，不要原样重试                                                                      |
| 402  | `insufficient_credits` | `insufficient credits`                                                                    | 未创建任务。补充积分前不要重试                                                                      |
| 500  | `internal_error`       | `the generation service is temporarily unavailable`                                       | 带 `error.task_id` 时任务已失败并退还积分，可重试一次。若 hint 说明结果未知，请轮询该任务，不要重试 |
| 503  | `internal_error`       | —                                                                                         | 仅音效：内容安全检查暂不可用，稍后重试                                                              |
