# AuralWise API 参考文档

REST API，支持语音转写、说话人分离、声音事件检测，可直接集成到任何语言的应用中。

**BASE URL:** `https://api.auralwise.cn/v1`

---

## 转写模式对比

系统内置两种转写模式，通过 `optimize_zh` 参数和检测到的音频语言自动路由。

### 中文精简模式（推荐中文场景）

- **触发条件：** `optimize_zh=true`（默认）且检测到中文音频
- **处理速度：** 极快，批量推理，比标准模式快 10–30 倍
- **计费：** 远低于标准模式
- **中文准确率：** 更高，专用中文引擎针对中文语音优化
- **时间戳：** 仅段级（segments[].start/end），无 words 字段
- **适用语言：** 仅中文；非中文音频自动切换到标准模式

### 标准模式（通用）

- **触发条件：** `optimize_zh=false` 或非中文音频
- **处理速度：** 标准，逐段串行转写 + 词级对齐
- **计费：** 通用转写基础价格
- **中文准确率：** 良好，通用多语言引擎，英文品牌名识别更好
- **时间戳：** 词级 ~10ms，segments[].words[] 含每个词的 start/end
- **适用语言：** 99 种语言（中/英/日/韩/法/德/西/俄/阿等）

### 如何选择？

- **纯中文音频 + 不需要词级时间戳：** 保持默认 `optimize_zh=true`，享受更快速度、更低价格和更高中文准确率。
- **需要词级时间戳**（如字幕制作、卡拉OK对齐）：设置 `optimize_zh=false`，获取每个词的精确时间戳。
- **中英混合或多语言内容：** 设置 `optimize_zh=false`，标准模式对英文品牌名和多语种混合内容识别更准确。
- **非中文音频：** 无需设置，无论 `optimize_zh` 值如何，非中文音频始终使用标准模式。

### 路由逻辑

```
音频提交 → 语言检测 → optimize_zh=true && 中文？ → 是 → 中文精简模式
                                                  → 否 → 标准模式
```

---

## 认证

支持以下两种认证方式（二选一）：

**方式一：API Key**

```
X-API-Key: asr_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

API Key 在账号设置中创建，格式为 `asr_` 开头的 44 位字符串。

**方式二：Bearer Token（JWT）**

```
Authorization: Bearer <token>
```

通过登录接口获取的 JWT Token，适用于前端集成场景。

---

## 创建任务

```
POST /tasks
Content-Type: application/json
```

提交音频 URL 或 Base64 编码音频创建一个转写任务。服务端收到请求后立即返回任务 ID，实际处理异步完成。

> **音频文件限制：** URL 模式文件大小 1 KB ~ 2 GB；Base64 模式单次请求最大 200 MB；音频时长最长 5 小时。超出限制的请求将被拒绝或任务标记为失败。

### 请求体字段

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| audio_url | string | 二选一 | 音频文件的公开可访问 URL。文件大小 1 KB ~ 2 GB，时长不超过 5 小时 |
| audio_base64 | string | 二选一 | Base64 编码的音频文件内容（支持标准或 URL-safe 编码，单次请求最大 200 MB）。时长不超过 5 小时 |
| audio_filename | string | 视情况 | 文件名（含扩展名）；audio_base64 模式下必填（用于确定扩展名），audio_url 模式下可选 |
| options | object | 可选 | 处理选项，所有字段均有默认值，可只传需覆盖的字段，详见下方"任务选项完整参考" |
| callback_url | string | 可选 | 任务进入终态（done/failed/abandoned）后，系统向此 URL 发送 POST 回调 |
| callback_secret | string | 可选 | 设置后为每次回调附带 X-Signature: sha256=HMAC-SHA256(secret, body) 头 |
| batch_mode | boolean | 可选 | 批量模式，适合对完成时间无严格要求的大批量转写，处理可能延迟最多 24 小时 |

### 请求示例（URL 模式）

```json
{
  "audio_url": "https://example.com/episode.mp3",
  "audio_filename": "episode.mp3",
  "options": {
    "enable_asr": true,
    "enable_diarize": true,
    "enable_audio_events": true,
    "optimize_zh": true,
    "asr_beam_size": 5,
    "vad_threshold": 0.35
  }
}
```

### 请求示例（Base64 模式）

```json
{
  "audio_base64": "<base64-encoded-audio-data>",
  "audio_filename": "episode.mp3",
  "options": {
    "enable_asr": true,
    "enable_diarize": true
  }
}
```

### 响应 201 Created

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "processing",
  "audio_filename": "episode.mp3",
  "audio_source_type": "url",
  "audio_size": 52428800,
  "options": { "enable_asr": true, "enable_diarize": true, "..." : "..." },
  "created_at": "2026-03-09T10:00:00Z",
  "updated_at": "2026-03-09T10:00:00Z"
}
```

---

## 列出任务

```
GET /tasks
```

获取当前用户的任务列表，支持按状态过滤和分页。

### 查询参数

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| status | string | 可选 | 按状态过滤：processing / done / failed / abandoned |
| page | integer | 可选 | 页码，从 1 开始，默认 1 |
| page_size | integer | 可选 | 每页条数，最大 100，默认 20 |

### 响应 200 OK

```json
{
  "tasks": [
    {
      "id": "550e8400-...",
      "status": "done",
      "audio_filename": "episode.mp3",
      "audio_source_type": "url",
      "created_at": "2026-03-09T10:00:00Z",
      "updated_at": "2026-03-09T10:05:30Z",
      "finished_at": "2026-03-09T10:05:30Z"
    }
  ],
  "total": 42,
  "page": 1,
  "page_size": 20
}
```

---

## 查询任务状态

```
GET /tasks/:id
```

获取单个任务的状态。可通过轮询该接口（建议间隔 3-5 秒）等待任务完成。

### 响应 200 OK

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "processing",
  "audio_filename": "episode.mp3",
  "audio_source_type": "url",
  "audio_size": 52428800,
  "error_message": null,
  "options": { "..." : "..." },
  "created_at": "2026-03-09T10:00:00Z",
  "updated_at": "2026-03-09T10:01:30Z",
  "finished_at": null
}
```

### 任务状态说明

| 状态 | 说明 |
|------|------|
| processing | 正在处理中，可轮询此接口等待完成 |
| done | 处理完成，可通过 /tasks/:id/result 接口获取结果 |
| failed | 处理失败，error_message 中包含原因，将自动重试 |
| abandoned | 多次重试失败后放弃，不再重试，可提交新任务 |

---

## 获取任务结果

```
GET /tasks/:id/result
```

获取已完成任务的完整结果。仅当任务状态为 `done` 时返回数据，否则返回 404。

各能力字段仅在该能力启用时出现，未启用的字段不包含在响应中（而非 null），可通过 key 是否存在来判断。

### 响应 200 OK

```json
{
  "task_id": "550e8400-e29b-41d4-a716-446655440000",
  "audio_duration": 315.5,

  "language": "zh",
  "language_probability": 0.99,
  "segments": [
    {
      "id": 0,
      "start": 0.500,
      "end": 2.340,
      "text": "这是第一句话",
      "speaker": "SPEAKER_0",
      "words": [
        { "word": "这是", "start": 0.500, "end": 0.820, "probability": 0.98 },
        { "word": "第一", "start": 0.840, "end": 1.120, "probability": 0.97 },
        { "word": "句话", "start": 1.140, "end": 1.460, "probability": 0.96 }
      ]
    }
  ],

  "vad_segments": [
    { "start": 0.500, "end": 2.340 }
  ],

  "num_speakers": 2,
  "speaker_embeddings": [
    {
      "speaker_id": "SPEAKER_0",
      "embedding": [0.124, -0.083, 0.211, "..."],
      "segment_count": 25
    },
    {
      "speaker_id": "SPEAKER_1",
      "embedding": [-0.057, 0.193, -0.142, "..."],
      "segment_count": 18
    }
  ],
  "diarize_segments": [
    { "start": 0.500, "end": 2.340, "speaker": "SPEAKER_0" }
  ],

  "audio_events": [
    { "start": 15.0, "end": 15.96, "class": "Cough", "confidence": 0.87 }
  ]
}
```

### 结果字段说明

#### 顶层字段（始终包含）

| 字段 | 类型 | 说明 |
|------|------|------|
| task_id | string | 任务 UUID |
| audio_duration | float | 音频总时长，单位秒 |

#### ASR 字段（enable_asr=true 时包含）

| 字段 | 类型 | 说明 |
|------|------|------|
| language | string | 检测到的音频语言代码，如 zh、en |
| language_probability | float | 语言识别置信度，0～1 |
| segments | object[] | 转写结果，按句子分段 |

#### segments[] — 转写段落

| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 段落序号，从 0 开始 |
| start | float | 段落开始时间（秒） |
| end | float | 段落结束时间（秒） |
| text | string | 该段落的转写文本 |
| speaker | string | 说话人 ID（如 SPEAKER_0），仅 enable_diarize=true 时包含 |
| words | object[] | 词级时间戳列表。**仅标准模式（optimize_zh=false 或非中文）时返回**；中文精简模式（optimize_zh=true + 中文音频）下此字段不存在，仅有段级 start/end |

#### segments[].words[] — 词级时间戳（标准模式专有）

| 字段 | 类型 | 说明 |
|------|------|------|
| word | string | 该词/字的文本内容 |
| start | float | 词开始时间（秒） |
| end | float | 词结束时间（秒） |
| probability | float | 该词的识别置信度，0～1 |

#### VAD 字段（enable_asr=true 时包含）

| 字段 | 类型 | 说明 |
|------|------|------|
| vad_segments | object[] | 语音活动检测识别到的语音段列表 |
| vad_segments[].start | float | 语音段开始时间（秒） |
| vad_segments[].end | float | 语音段结束时间（秒） |

#### 说话人分离字段（enable_diarize=true 时包含）

| 字段 | 类型 | 说明 |
|------|------|------|
| num_speakers | int | 聚类检测到的说话人总数 |
| speaker_embeddings | object[] | 每个说话人的声纹信息 |
| speaker_embeddings[].speaker_id | string | 说话人标识，如 SPEAKER_0 |
| speaker_embeddings[].embedding | float[] | 192 维说话人声纹向量，可用于跨任务说话人比对 |
| speaker_embeddings[].segment_count | int | 该说话人参与聚类的语音段数量 |
| diarize_segments | object[] | 按说话人标注的时间段列表 |
| diarize_segments[].start | float | 分离段开始时间（秒） |
| diarize_segments[].end | float | 分离段结束时间（秒） |
| diarize_segments[].speaker | string | 该时间段归属的说话人 ID |

> **单说话人场景**：当仅检测到 1 位说话人时，`diarize_segments` 仍会返回，所有片段均归属 `SPEAKER_0`。

#### 声音事件字段（enable_audio_events=true 时包含）

| 字段 | 类型 | 说明 |
|------|------|------|
| audio_events | object[] | 检测到的声音事件列表，按时间排列 |
| audio_events[].start | float | 事件开始时间（秒），精度约 ±1s |
| audio_events[].end | float | 事件结束时间（秒） |
| audio_events[].class | string | 声音事件英文类名（如 Cough、Music），共 521 种 |
| audio_events[].confidence | float | 置信度得分，0～1 |

### 时间戳精度

| 能力 | 精度 | 说明 |
|------|------|------|
| ASR 段级 | ~10ms | 与语音段边界对齐 |
| VAD | ~10ms | 语音活动检测帧级精度 |
| 说话人分离 | ~10ms | 直接使用 VAD 段边界 |
| 声音事件 | ~1s | 声音事件检测以 1s 为步长推理，固有限制 |

---

## 删除任务

```
DELETE /tasks/:id
```

删除任务及其关联的音频文件和结果文件。处于 `processing` 状态的任务**无法删除**，将返回 409 Conflict。

### 响应 200 OK

```json
{
  "message": "task deleted"
}
```

---

## 声音事件类别

```
GET /audio-event-classes
```

获取系统支持的全部 521 个声音事件类别列表，含中文名称和分类信息。

### 响应 200 OK

```json
[
  {
    "index": 0,
    "mid": "/m/09x0r",
    "display_name": "Speech",
    "zh_name": "语音",
    "category": "Human sounds",
    "category_zh": "人声与人体声音"
  },
  {
    "index": 42,
    "mid": "/m/01b_21",
    "display_name": "Cough",
    "zh_name": "咳嗽",
    "category": "Human sounds",
    "category_zh": "人声与人体声音"
  }
]
```

共 8 个大类：人声与人体声音、动物声音、音乐、自然声音、交通工具、家居与工具、电子设备、环境与背景声。

---

## 账单流水

`GET /billing/transactions`

获取当前用户的账单流水记录，按时间倒序排列。

### 查询参数

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| page | integer | 否 | 页码，从 1 开始，默认 1 |
| page_size | integer | 否 | 每页条数，最大 100，默认 20 |

### 响应 200 OK

```json
{
  "transactions": [
    {
      "id": "a1b2c3d4-...",
      "user_id": "550e8400-...",
      "task_id": "660f9511-...",
      "type": "deduction",
      "amount": "-1.5000",
      "balance_before": "100.0000",
      "balance_after": "98.5000",
      "description": "任务扣费 - 标准转写 3.5 分钟",
      "created_at": "2026-03-09T10:05:30Z"
    }
  ],
  "total": 42,
  "page": 1,
  "page_size": 20
}
```

`type` 取值：`deduction`（扣费，amount 为负）、`recharge`（充值，amount 为正）、`refund`（退款，amount 为负）。`task_id` 仅扣费记录包含，充值和退款时为 null。

---

## 任务选项（options）完整参考

提交任务时通过 `options` 字段控制处理行为。所有字段均有默认值，可只传需要覆盖的字段。

**依赖规则：**
- `enable_diarize=true` 且 `enable_asr=false`：返回 400（说话人分离依赖转写结果）
- 三项 `enable_*` 全为 false：返回 400

### 功能开关

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| enable_asr | bool | true | 语音转写，含自动语音段切分 |
| enable_diarize | bool | true | 说话人分离，需同时启用 ASR |
| enable_audio_events | bool | true | 声音事件检测，支持 521 类 |
| optimize_zh | bool | true | 中文精简模式：检测到中文时使用专用中文引擎，转写速度更快、中文准确率更高，但仅返回段级时间戳（无 words 字段）；false 时所有语言统一使用通用引擎，返回完整词级时间戳。对非中文音频无影响。 |

### ASR 参数

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| asr_language | string/null | null | ISO 639-1/3 语言代码，null 自动检测。支持 99 种语言，常用：zh、en、ja、ko、fr、de、es、ru、ar、pt、th、vi、hi |
| asr_beam_size | integer | 5 | Beam Search 宽度，越大精度越高但速度越慢 |
| asr_temperature | float | 0.0 | 解码温度，0 为贪心解码，>0 启用采样 |
| asr_best_of | integer/null | null | Best-of 采样数（1-20），仅在 asr_temperature > 0 时生效；null 表示不启用 |
| hotwords | string[]/null | null | 热词列表，提升专有名词、品牌名、人名的识别概率 |
| initial_prompt | string/null | null | 初始提示文本，可引导转写风格 |

### VAD 参数

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| vad_threshold | float | 0.35 | 语音概率阈值（0-1），越高越保守，越低越灵敏，默认 0.35 对背景音乐场景友好 |
| vad_min_speech_ms | integer | 250 | 最短语音段时长（毫秒），短于此被忽略 |
| vad_min_silence_ms | integer | 100 | 最短静音间隔（毫秒），短于此不切断 |
| vad_speech_pad_ms | integer | 30 | 语音段前后填充时长（毫秒） |
| no_speech_threshold | float | 0.6 | 无语音概率阈值，高于此值丢弃片段 |

### 说话人分离参数

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| num_speakers | integer/null | null | 固定说话人数量，null 表示自动检测（推荐） |
| min_speakers | integer | 1 | 自动检测时最小说话人数 |
| max_speakers | integer | 10 | 自动检测时最大说话人数 |
| diarize_min_segment_sec | float | 0.5 | 提取声纹所需最短语音段时长（秒） |
| diarize_single_speaker_threshold | float | 0.05 | Silhouette Score 阈值，低于此值判定为单说话人 |

### 声音事件参数

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| audio_events_threshold | float | 0.3 | 声音事件置信度阈值，仅返回超过此值的事件 |
| audio_events_classes | string[]/null | null | 指定监听的声音事件类名列表；null 检测全部 521 类 |

---

## Webhook 回调

任务进入终态后，系统会向 `callback_url` 发送一次 HTTP POST 请求，请求体为精简的任务状态通知 JSON。如需完整任务详情或转写结果，请在收到回调后调用 `GET /tasks/:id` 和 `GET /tasks/:id/result`。

### 触发条件

| 状态 | 说明 |
|------|------|
| done | 任务处理成功完成，结果已存储 |
| failed | 任务因不可恢复的错误直接失败（如文件大小/时长超限），不会重试 |
| abandoned | 任务多次重试后仍然失败，最终放弃 |

> **注意**：普通的任务处理失败（将自动重试）**不会**触发回调。仅上述终态会触发。

### 回调请求格式

```
POST https://your-server.com/webhook HTTP/1.1
Content-Type: application/json
X-Signature: sha256=3d9e5f2a1b8c4d7e...

{
  "task_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "done",
  "created_at": "2026-03-09T10:00:00Z",
  "finished_at": "2026-03-09T10:05:30Z",
  "error_message": null
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| task_id | string | 任务 UUID |
| status | string | 终态：done / failed / abandoned |
| created_at | string | 任务创建时间（ISO 8601） |
| finished_at | string/null | 任务完成时间 |
| error_message | string/null | 错误描述，仅失败时包含 |

### 签名验证（X-Signature）

若创建任务时设置了 `callback_secret`，每次回调请求都会附带签名头：

```
X-Signature: sha256=<hex(HMAC-SHA256(secret, body_bytes))>
```

服务端应对请求体重新计算签名，并与请求头比对，以防止伪造回调。

**Python 验证示例：**

```python
import hmac, hashlib

def verify_callback(secret: str, body: bytes, signature_header: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)
```

**Node.js 验证示例：**

```javascript
const crypto = require('crypto');

function verifyCallback(secret, bodyBuffer, signatureHeader) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(bodyBuffer)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}
```

**Go 验证示例：**

```go
func verifyCallback(secret string, body []byte, signatureHeader string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(signatureHeader))
}
```

### 重试策略

若回调请求失败（网络错误或响应非 2xx），系统自动重试：

| 次数 | 延迟 | 说明 |
|------|------|------|
| 第 1 次 | 立即发送 | 任务进入终态时立即触发 |
| 第 2 次 | 1 秒后 | 首次失败后等待 1 秒重试 |
| 第 3 次 | 5 秒后 | 二次失败后等待 5 秒重试 |
| 第 4 次 | 30 秒后 | 三次失败后等待 30 秒最后一次重试，之后放弃 |

你的回调服务器应尽快返回 2xx，避免处理逻辑阻塞响应。建议先将请求入队，异步处理任务结果。

---

## 完整调用示例

### 方式一：轮询（Polling）

```bash
# 1. 提交任务
TASK_ID=$(curl -s -X POST https://api.auralwise.cn/v1/tasks \
  -H "X-API-Key: asr_xxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "audio_url": "https://example.com/meeting.mp3",
    "audio_filename": "meeting.mp3",
    "options": {
      "enable_asr": true,
      "enable_diarize": true,
      "enable_audio_events": false,
      "optimize_zh": true,
      "asr_language": "zh",
      "asr_beam_size": 5,
      "vad_threshold": 0.35,
      "num_speakers": null,
      "max_speakers": 5
    }
  }' | jq -r .id)

echo "Task ID: $TASK_ID"

# 2. 轮询等待完成（建议间隔 5 秒）
while true; do
  RESP=$(curl -s -H "X-API-Key: asr_xxxx" https://api.auralwise.cn/v1/tasks/$TASK_ID)
  STATUS=$(echo $RESP | jq -r .status)
  echo "Status: $STATUS"
  [ "$STATUS" = "done" ]      && break
  [ "$STATUS" = "abandoned" ] && echo "Task abandoned!" && exit 1
  sleep 5
done

# 3. 获取转写结果
curl -s -H "X-API-Key: asr_xxxx" \
  https://api.auralwise.cn/v1/tasks/$TASK_ID/result | jq .
```

### 方式二：Webhook 回调（推荐用于生产）

```bash
curl -s -X POST https://api.auralwise.cn/v1/tasks \
  -H "X-API-Key: asr_xxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "audio_url": "https://example.com/meeting.mp3",
    "options": {
      "enable_asr": true,
      "enable_diarize": true,
      "asr_language": "zh"
    },
    "callback_url": "https://your-server.com/webhook",
    "callback_secret": "your-secret-key"
  }' | jq .
```

---

## 错误处理

所有错误响应均为 JSON 格式，包含 `error` 字段：

```json
{
  "error": "错误描述"
}
```

| HTTP 状态码 | 说明 |
|-------------|------|
| 400 | 请求格式错误或参数不合法（如 options 依赖规则冲突） |
| 401 | API Key 缺失或无效 |
| 402 | 余额不足，请充值后重试 |
| 403 | 权限不足（如访问其他用户的任务） |
| 404 | 任务不存在，或任务尚未完成（查询结果时） |
| 409 | 操作冲突（如删除 processing 状态的任务） |
| 500 | 服务内部错误 |
