# Canto-Gen (港嘢) API Documentation

廣東話文字轉語音 API：支援預設聲線、聲音設計（design）與聲音複製（clone），所有預設聲線為廣東話原生。

- Base URL: `https://canto-gen.com`
- Auth: `Authorization: Bearer $CANTOGEN_API_KEY`
- 互動式參考：/v1/docs/reference · OpenAPI：/v1/openapi.json · llms.txt：/llms.txt

兩種生成方式 — 短文字用**同步**，其餘用**非同步**（兩者接受相同的請求欄位）：

## 取得 API 金鑰

1. 登入後前往 帳戶 → API 金鑰（`/account/api-keys`）建立金鑰。
2. 完整金鑰**只會顯示一次** — 請立即複製。伺服器端僅儲存 SHA-256 hash；遺失的金鑰只能撤銷後重新建立。
3. 將金鑰匯出為環境變數，供下方示例使用：

```bash
export CANTOGEN_API_KEY="sk_live_..."
```

| 前綴 | 用途 |
|---|---|
| `sk_live_…` | 唯一的金鑰格式。免費方案金鑰受每日生成上限約束；付費方案用量計入該金鑰的每月字符上限。 |

| Scope | 開放權限 |
|---|---|
| `tts:generate` | 預設。開放 voice 及 design 生成，以及所有讀取 endpoint（聲線目錄、任務查詢）。 |
| `tts:clone` | clone 模式（以參考樣本複製聲音）必須使用此 scope。 |
| `tts:design` | design 模式（gender + age 合成聲線）的細粒度 scope — 已由 tts:generate 涵蓋，兩者其一即可。 |
| `voices:manage` | 個人聲線管理（MCP 的 list / create / delete my voices）必須使用此 scope。 |

金鑰同時附帶每分鐘 rate limit（預設 60）及可選的每月字符上限。

**免費方案**：免費方案用戶同樣可以建立 API 金鑰（一律 `sk_live_` 格式），每日上限 50 個生成請求（輪詢與下載不計入）。升級至輕量方案後，同一金鑰即自動解鎖全速 API，無需重建。

## 定價

API 存取為**付費方案（輕量／入門／專業／工作室）專屬**，全速無每日上限。API 用量與網頁 Playground 共用同一個帳戶字符池，按帳單週期（訂閱周年日）重置 — 1 字符 = 1 字符，無附加費。字符池用完可購買字符包。`X-Credits-Charged` 回應 header 顯示每個請求的實扣字符數。方案詳情請見 /pricing。

免費方案用戶亦可建立 API 金鑰試用，詳情見「取得 API 金鑰」。

## 詞彙表

- **每日生成上限** — 免費方案金鑰每日 50 個生成請求（輪詢與下載不計）；付費方案無每日上限。
- **金鑰每月字符上限** — 每把金鑰本月（UTC）累計計費字符的上限，超出回 403 monthly_cap_reached；預設不設上限。
- **帳戶字符池** — 帳戶層級的字符餘額，網頁 Playground 與 API 共用，按帳單週期重置；不足時回 402 character_limit_reached。
- **字符包** — 為帳戶字符池一次性增值，永不過期。
- **待處理任務上限** — 同一帳戶可同時 pending 的任務數，按方案分級（3／4／5／10／20／20）；超限回 429 in_flight_limit_reached。

## 快速入門

先列出聲線目錄，再以目錄中的任何 `slug` 提交語音任務：

```bash
curl https://canto-gen.com/v1/voices \
  -H "Authorization: Bearer $CANTOGEN_API_KEY"
```

```json
{
  "voices": [
    {
      "slug": "sum",
      "name": "心妍",
      "gender": "female",
      "age": "adult",
      "styles": [],
      "preview_url": "https://canto-gen.com/api/voice-clip?slug=sum"
    }
  ]
}
```

> 聲線目錄為即時資料 — 請務必從你自己的 `GET /v1/voices` 回應中挑選 `slug`。此處的 `"sum"`（心妍）僅為文件示例。

提交語音任務（`model` 可省略 — `"cantogen-tts"` 是唯一值）：

```bash
# Async by design — submit returns 202 with a job_id, then poll.
export CANTOGEN_API_KEY="sk_live_…"

curl -X POST https://canto-gen.com/v1/audio/jobs \
  -H "Authorization: Bearer $CANTOGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "cantogen-tts",
    "voice": "sum",
    "text": "你好，歡迎使用港嘢！",
    "response_format": "mp3"
  }'
```

回應 — **`202 Accepted`**：

```http
HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "job_id": "b7e6f2a1-4c3d-4e5f-8a9b-0c1d2e3f4a5b",
  "status": "queued",
  "poll_url": "/v1/audio/jobs/b7e6f2a1-4c3d-4e5f-8a9b-0c1d2e3f4a5b",
  "srt_requested": false
}
```

每次成功的非同步提交均回傳 `202`，附帶 `job_id` / `status` / `poll_url` / `srt_requested`。

批次：傳入 `{"items": [ …最多 25 個請求內容… ]}` — 驗證為全有或全無（任何一項無效即回傳 `400 invalid_items`）；入隊後，各項結果在 `202 {"jobs":[…]}` 中逐項回報。超過 25 項回傳 `400 too_many_items`。批量生成為入門／專業／工作室方案功能。並行上限按方案分級（免費 3／輕量 4／入門 5／專業 10／工作室 20／企業 20 個 pending 任務），超上限回 `429 in_flight_limit_reached`，待現有任務完成後即可再提交。

### 同步快捷方式 — POST /v1/audio/speech

POST /v1/audio/speech 是同步快捷方式：在同一個請求內等待生成完成，直接以 200 OK 回傳原始音訊 bytes（通常 5–15 秒內完成，附 X-Audio-Duration / X-Credits-Charged headers）。限制：文字上限 300 個計費字符，僅支援 voice（預設）或 design 模式 — clone 回傳 400 clone_not_supported_sync，srt 回傳 400 srt_not_supported_sync（兩者請改用非同步 endpoint）。

```bash
curl -X POST https://canto-gen.com/v1/audio/speech \
  -H "Authorization: Bearer $CANTOGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "cantogen-tts",
    "voice": "sum",
    "text": "你好，歡迎使用港嘢！",
    "response_format": "mp3"
  }' --output speech.mp3
```

### 冷池 — 忙時回 503

同步隊列繁忙（有任何生成進行中）時，此 endpoint 以 503 pool_cold 直接拒絕 — 不扣費，亦不會靜默轉入排隊；請改用 POST /v1/audio/jobs。若請求已受理但等待超過約 45 秒，將改回 202 {job_id, poll_url} — 任務繼續執行，不會重複扣費，改以非同步輪詢即可。

```http
HTTP/1.1 503 Service Unavailable

{ "error": "pool_cold", "hint": "use POST /v1/audio/jobs (async)" }
```

## 聲音模式

三種聲音模式互斥：每次請求只可傳入 voice、design 或 clone 其中一項 — 否則回傳 400 voice_mode_conflict。設計及複製的聲線僅對該次請求生效，不會儲存為可重用的聲線資產。

### voice — 圖庫聲線

以 slug 直接選用廣東話優化圖庫中的現成聲線。

- 瀏覽 GET /v1/voices — 所有預設聲線均以廣東話為母語，附試聽片段。
- 將 slug 作為 voice 參數傳入。文件示例為 sum（心妍）— 請務必從你自己的回應中挑選 slug。slug 為 ASCII 穩定識別符（行業標準做法），中文名作介面顯示之用。

```bash
# Async by design — submit returns 202 with a job_id, then poll.
export CANTOGEN_API_KEY="sk_live_…"

curl -X POST https://canto-gen.com/v1/audio/jobs \
  -H "Authorization: Bearer $CANTOGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "cantogen-tts",
    "voice": "sum",
    "text": "你好，歡迎使用港嘢！",
    "response_format": "mp3"
  }'
```

### design — 設計聲線

以純文字描述一個合成聲線 — 指定 gender 與 age，無需上載任何音訊檔。

- gender: male | female · age: child | teen | young | adult | midage | elder — 兩項均必填。
- 適合產品人設；可自由搭配 emotion 及 srt 參數。

```bash
curl -X POST https://canto-gen.com/v1/audio/jobs \
  -H "Authorization: Bearer $CANTOGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "cantogen-tts",
    "design": { "gender": "female", "age": "young" },
    "text": "今日天氣幾好，一齊去行山啦。",
    "style": "brisk",
    "emotion": "happy",
    "srt": true
  }'
```

### clone — 複製聲線

上載參考樣本 — 平台在內部轉錄（ASR）並自動套用逐字稿，客戶端無需額外呼叫。

- reference_audio: data URL 或 raw base64 — 支援 audio/mpeg、wav、webm、mp4/m4a、ogg。單一請求上限 4.4 MB（base64 編碼後音訊約 ≤3 MB）。
- 樣本至少 8 秒（不足會被拒絕）；處理時按句界自動裁至約 15–20 秒、上限 30 秒 — 乾淨樣本效果最佳。
- 只需 reference_audio；舊版 prompt_text 欄位已退役（送出會被忽略）。
- 需要具備 tts:clone scope 的 API 金鑰。

```bash
B64=$(base64 -w0 sample.wav)

curl -X POST https://canto-gen.com/v1/audio/jobs \
  -H "Authorization: Bearer $CANTOGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"cantogen-tts\",
    \"clone\": {
      \"reference_audio\": \"data:audio/wav;base64,$B64\"
    },
    \"text\": \"用我把聲講呢句嘢。\"
  }"
```

其他生成參數：

- `style` — 演繹風格 — 建議使用英文 slug：節奏 steady（平穩） / composed（從容） / rapid（急促） / slow（慢板） / drawl（拖長） / brisk（明快） / dynamic（起伏） / staccato（頓挫）；音色 mellow（圓潤） / cute（奶音） / sharp（尖細） / lazy（懶音） / breathy（氣聲） / hoarse（沙啞） / deep（洪厚） / crisp（清脆） / feeble（無力） / trembling（顫音）；場景／角色 slug 同樣接受（corporate／promo／…／storytelling（講古）／sports 等）。原有中文 preset 值（明快／圓潤 等）仍作別名接受 — 兩者內部會正規化到同一 canonical 值；其他自由文字原樣接受。
- `emotion` — 9 個支援值其中之一（見下方 chips）。注意：API enum 使用英文值（happy／excited 等），並以英文原樣傳入引擎；與 Playground 介面的中文情緒 preset（雀躍／冷靜 等）屬兩套不同體系 — 中文情緒 preset 僅限 Playground 使用，API emotion 參數不接受中文值。
- `instruction` — 自由文字演繹指示，上限 500 字符。優先次序：style → emotion → instruction — instruction 有最終決定權。
- `pitch` — 已棄用（2026-09-20）：原音交付下不生效（接受參數但忽略其值）。歷史範圍 −12…+12 半音，預設 0。
- `volume` — 已棄用（2026-09-20）：原音交付下不生效（接受參數但忽略其值）。歷史範圍 0–10，預設 1。
- `response_format` — mp3（預設）/ wav。
- `srt` — true → 完成後的任務附帶 srt 欄位（以較高字符費率計費）。全方案適用。
- `emotion` 可選值：`happy` / `sad` / `angry` / `fearful` / `disgusted` / `surprised` / `neutral` / `excited` / `gentle`

## 語氣標籤（inline tone tags）

`text` 支援 inline 語氣標籤 — 在台詞中插入 `[標籤]`，該句即帶對應的動作／語氣演繹。閉集共 15 條：

| 標籤 | token | 位置 |
|---|---|---|
| `笑聲` | `laughing` | head 或 tail |
| `嘆氣` | `sigh` | head 或 tail |
| `深呼吸` | `breath` | head（句首） |
| `遲疑` | `Uhm` | head（句首） |
| `噓` | `Shh` | head（句首） |
| `唓` | `Dissatisfaction-hnn` | head（句首） |
| `啊?` | `Question-ah` | tail（句尾） |
| `誒?` | `Question-ei` | tail（句尾） |
| `嗯?` | `Question-en` | tail（句尾） |
| `哦?` | `Question-oh` | tail（句尾） |
| `嘩!` | `Surprise-wa` | head（句首） |
| `唷!` | `Surprise-yo` | tail（句尾） |
| `呀!` | `Surprise-ah` | tail（句尾） |
| `哦!` | `Surprise-oh` | tail（句尾） |
| `嗯哼` | `Confirmation-en` | tail（句尾） |

位置規則：`head` 標籤置於句首（開口前動作／感嘆詞），`tail` 標籤置於句尾（粵語語氣詞）；問句標籤（`啊?` `嗯?` `哦?` `誒?`）只配以 `？`/`?` 收尾的台詞。方括號內不屬閉集的 token 原樣保留（永不刪字）。標籤字元按正常文字計費。

示例：`"text": "[笑聲]今日天氣好好[唷!]"` — 句首一個笑聲、句尾一個語氣詞。

## 輪詢與 SRT

狀態生命週期：`queued` → `processing` → `completed` | `failed` | `cancelled`；取消生成中的任務時會先出現暫態 `cancelling`（見下文「取消任務」）。以 2–5 秒間隔（含退避）查詢 `GET /v1/audio/jobs/{id}`：

```bash
curl https://canto-gen.com/v1/audio/jobs/b7e6f2a1-4c3d-4e5f-8a9b-0c1d2e3f4a5b \
  -H "Authorization: Bearer $CANTOGEN_API_KEY"
```

當 `status` 為 `"completed"`，回應會附上預簽名的 `audio_url`：

```json
{
  "job_id": "b7e6f2a1-4c3d-4e5f-8a9b-0c1d2e3f4a5b",
  "status": "completed",
  "poll_url": "/v1/audio/jobs/b7e6f2a1-4c3d-4e5f-8a9b-0c1d2e3f4a5b",
  "srt_requested": true,
  "srt": "1\n00:00:00,000 --> 00:00:02,400\n你好，歡迎使用港嘢！",
  "audio_url": "https://canto-gen.com/storage/v1/object/sign/audio_history/..."
}
```

以普通 GET 下載（無需 Authorization header — 連結已預簽名）：

```bash
curl -sL -o speech.mp3 "$AUDIO_URL"
```

> audio_url 為預簽名連結，設有有效期：由素材音訊庫直接供應時有效期為 1 小時，由生成節點即時供應時為 7 天。請盡早下載；連結過期後重新查詢該任務即可取得新連結。任務在 queued/processing 期間以及 failed/cancelled 任務，均不附帶 audio_url 欄位。

格式錯誤的任務 id 回傳 `400 {"error":"invalid_job_id"}`；不存在的 id 回傳 `404 {"error":"job_not_found"}`。

**字幕（SRT）：** 提交時傳入 `"srt": true`，完成後任務的查詢回應會附上 `srt` 欄位，內容為字幕。計費細節：任務的帳戶 credit 包含 SRT 附加費（srt: true 時 billedCharacters 會高於基本字符數），而每金鑰每月上限的帳本則按提交文字長度直接計算。SRT 字幕輸出全方案適用。

**Rate limit 與上限：** 每月字符上限 — 金鑰本月至今（UTC）的計費字符數必須低於其上限（null = 無限制）。超限會回傳 403 monthly_cap_reached 並附 used 數字，直至該月重設。X-RateLimit-* headers 附於 POST /v1/audio/jobs 的 202 及 429 回應、以及 POST /v1/audio/speech 的 429 回應（200 回應附帶 X-Credits-Charged 等 usage headers）；讀取 endpoint（GET /v1/audio/jobs/{id}、GET /v1/voices）不受 rate limit 限制。兩個計量器均以「故障時放行」為設計原則 — 計量系統故障絕不會令請求被拒。

### 取消任務 — DELETE /v1/audio/jobs/{id}

不再需要的任務可以取消 — 對同一個 `job_id` 發出 `DELETE /v1/audio/jobs/{id}`：

```bash
curl -X DELETE https://canto-gen.com/v1/audio/jobs/<job_id> \
  -H "Authorization: Bearer \$CANTOGEN_API_KEY"
```

四種結果：

- 未開始（`queued`）→ **`200`** `{ "ok": true, "status": "cancelled", "refunded": 1234 }` — 任務即時取消並全額退款（`refunded` 為實際退回的字符數）。
- 生成中（`processing`）→ **`202`** `{ "ok": true, "status": "cancelling" }` — 取消已受理，任務收尾中；其後輪詢會顯示 `status: "cancelling"`，最終收斂為 `"cancelled"`。
- 已終態（`completed` / `failed` / `cancelled`）→ **`409`** `{ "error": "already_settled" }` — 任務無法再取消，亦不會退款。
- id 不存在（或屬於另一金鑰）→ **`404`** `{ "error": "job_not_found" }`。

## 文件轉寫

文件轉寫 — 上載音檔，同一請求內同步回傳文字。Canonical path：`POST /v1/asr/transcribe`；以 multipart/form-data 傳送。欄位：`file`（必填）、`model`（必填）、`language`（選填，ISO-639-1）、`response_format`（`json` 預設／`text`）。`srt`／`verbose_json` 需要段級時間戳，暫不支援（回 400 unsupported_response_format）。現階段轉寫以廣東話為本。

**計費：** 按音頻時長計費（預付 HK$ 餘額）— 文件轉寫 HK$1.40／小時、實時聽寫 HK$2.80／小時（2 倍），按秒計費、不進位。請求受理前先按偵測到的時長預扣，完成後按實際時長結算；失敗、超時或服務不可用一律全額退回（只扣成功）。餘額不足回 402 insufficient_balance — 請前往 /account/billing 儲值後重試。費率與餘額可用 GET /v1/balance 查詢。

上限：

- `file size` — 單檔 ≤20 MB（超出回 413 payload_too_large）。
- `audio duration` — 單請求音頻同步實務上限約 9 分鐘（超出回 503 engine_unavailable 並全額退款）；4 小時上限已規劃、未上線。
- `daily cap` — 每日計費音頻總量 24 小時（香港時間日界；超出回 429 daily_limit_exceeded）。

示例：

```bash
curl -X POST https://canto-gen.com/v1/asr/transcribe \
  -H "Authorization: Bearer \$CANTOGEN_API_KEY" \
  -F file=@sample.wav \
  -F model=cantogen-asr \
  -F response_format=json
```

回應 — **`200`**：

```json
{
  "id": "b7e6f2a1-4c3d-4e5f-8a9b-0c1d2e3f4a5b",
  "object": "transcription",
  "text": "今日天氣幾好，一齊去行山啦。",
  "duration_seconds": 1830,
  "charged_hkd": 0.7117
}
```

> 過渡說明：轉寫於同一請求內同步交付 — 短至中等長度音檔一般直接完成；處理時間超過同步等待窗（約 9 分鐘音頻）會全額退款並回 503 engine_unavailable（worker 仍會完成，惟結果暫無取回面）。非同步取回 API 上線後恢復 4 小時宣稱。

### 查詢餘額 — GET /v1/balance

查詢預付餘額（HKD）、文件轉寫／實時聽寫費率與自動儲值設定。測試金鑰期間回傳測試環境餘額（與正式環境互相隔離）。餘額不足請前往 /account/billing 儲值（可開啟自動儲值 — 餘額跌低門檻時自動增值）。

```bash
curl https://canto-gen.com/v1/balance \
  -H "Authorization: Bearer \$CANTOGEN_API_KEY"
```

```json
{
  "object": "balance",
  "currency": "HKD",
  "balance_hkd": 50,
  "asr": {
    "batch_rate_hkd_per_hour": 1.4,
    "realtime_rate_hkd_per_hour": 2.8,
    "billed_unit": "audio_second"
  },
  "auto_topup": { "enabled": true, "threshold_hkd": 20, "amount_hkd": 100 }
}
```

## 實時 WebSocket（Realtime）

除 REST 外，港嘢另提供 WebSocket 實時端點：在單一長連線上傳送文字並接收串流 PCM 音訊，適合自帶 LLM 的語音應用。協議以 **OpenAI Realtime** 為基礎；港嘢新增的事件與欄位一律標明為 extension。

`wss://api.canto-gen.com/v1/audio/realtime`

### 認證

認證方式與 REST 相同：`sk_` API 金鑰，所需 scope 為 `tts:generate`。優先使用 `Authorization: Bearer sk_…` header；瀏覽器 WebSocket API 無法設定自訂 header，可改用 query 參數 `?api_key=sk_…`（別名 `?token=`）。query 參數會寫入 URL、伺服器日誌與瀏覽器歷史，存在洩漏風險，**僅限瀏覽器環境**使用；伺服器端請求一律使用 header。

### 事件 — client → server

| 事件 | 來源 | 用途 |
|---|---|---|
| `session.update` | OpenAI 原生 | 建立／更新 session 設定：voice、language、output_audio_format、max_session_chars、tts.segmentation。 |
| `conversation.item.create` | OpenAI 原生 | 提交一段明確文字（input_text）；觸發合成。 |
| `input_text.append` | 港嘢 extension | 串流傳送原始 token delta。 |
| `input_text.commit` | 港嘢 extension | 強制段落邊界（立即 flush）。 |
| `response.create` | OpenAI 原生 | 觸發合成。 |
| `response.cancel` | OpenAI 原生 | barge-in：停止當前句子並退款。 |
| `session.close` | 港嘢 extension | 主動關閉連線並結算。 |

### 事件 — server → client

| 事件 | 來源 | 用途 |
|---|---|---|
| `session.created` | OpenAI 原生 | 連線建立；附伺服器簽發（server-minted）的 session id。 |
| `session.updated` | OpenAI 原生 | 設定更新確認。 |
| `response.created` | OpenAI 原生 | 單句開始合成。 |
| `response.output_audio.delta` | OpenAI 原生 | base64 PCM16 @ 48 kHz 音訊 chunk。 |
| `response.output_audio.done` | OpenAI 原生 | 單句音訊完成。 |
| `response.done` | OpenAI 原生 | 單句結束；usage 附 input_chars／billed_chars／remaining（extension 欄位）。 |
| `error` | OpenAI 原生 | {type, code, message, http_status}。 |

`tts.segmentation`（extension）控制切句方式：`{"mode":"server"}`（預設）由伺服器按標點自動切句並逐句合成 — client 只需持續 append token；`{"mode":"client"}` 則由 client 透過 `conversation.item.create` / `input_text.commit` 自行決定段落邊界。可選 `min_chars` / `max_chars` / `punctuation`。

### 限制與逾時

- `idle_timeout` — 閒置 120 秒（無任何 app-level 活動）→ 伺服器關閉連線並結算。
- `max_session_duration` — free plan 單一 session 最長 5 分鐘；付費 plan 無時長上限（達全域上限時關閉）。
- `max_sessions_per_user` — 同一用戶的同時 session 上限按 plan 階梯：free 1／lite 2／starter 3／pro 4／studio 5／enterprise 5；未知 plan 一律視為 free。
- `max_sessions_global` — 全平台同時最多 8 個 session。
- `max_session_chars` — 每個 session 計費字符上限（預設 2000，可經 session.update 調整）；超出回 session_char_limit_exceeded。
- `max_in_flight` — 每 session 同時最多 3 句未結算。
- `raw_ingress` — 每 session 原始輸入上限 20,000 字符、每分鐘 5,000 字符（標點與空白亦計）；超出回 raw_limit_exceeded，即時拒絕。
- `lease` — 每句合成有 60 秒結算租約；逾時未完成會自動退款。
- `rate_limit / monthly_cap` — 沿用同一金鑰的每分鐘 rate limit 及每月字符上限；用量帳本逐句記錄（per utterance）。
- `daily_generation_limit` — 免費方案每日 50 個生成句（與 REST 共用同一計數，輪詢與下載不計）；超出回 sandbox_daily_limit（429），付費方案無每日上限。
- `frame_size` — 單一 WS 訊息上限 64 KiB。
- `heartbeat` — 伺服器定期 ping；心跳不計入 app 活動。

### Node 範例 client

```js
// npm i ws   — one long-lived connection: stream tokens in, stream PCM out.
import WebSocket from "ws";

const ws = new WebSocket("wss://api.canto-gen.com/v1/audio/realtime", {
  headers: { Authorization: `Bearer ${process.env.CANTOGEN_API_KEY}` },
});

ws.on("open", () => {
  ws.send(JSON.stringify({
    type: "session.update",
    session: { voice: "sum", language: "yue", output_audio_format: "pcm16", max_session_chars: 500 },
  }));
  // One explicit utterance — or keep sending input_text.append deltas.
  ws.send(JSON.stringify({
    type: "conversation.item.create",
    item: { type: "message", content: [{ type: "input_text", text: "你好，歡迎使用港嘢！" }] },
  }));
});

const chunks = [];
ws.on("message", (raw) => {
  const ev = JSON.parse(raw.toString());
  if (ev.type === "session.created") console.log("session", ev.session.id);
  if (ev.type === "response.output_audio.delta") chunks.push(Buffer.from(ev.delta, "base64"));
  if (ev.type === "response.done") {
    // PCM16 @ 48 kHz — feed an AudioWorklet or write the chunks to disk.
    console.log("billed", ev.response.usage.billed_chars, "remaining", ev.response.usage.remaining);
    ws.close();
  }
  if (ev.type === "error") console.error(ev.error.code, ev.error.message);
});
```

### 錯誤碼

| code | HTTP | 原因 |
|---|---|---|
| `invalid_api_key` | 401 | 金鑰缺失、無效、過期或 scope 不符。 |
| `rate_limit_exceeded` | 429 | 觸及每金鑰每分鐘上限；附 retry_after。 |
| `sandbox_daily_limit` | 429 | 免費方案每日 50 個生成句已用完（與 REST 共用計數）；明日再試或升級方案。 |
| `monthly_cap_reached` | 429 | 已達金鑰本月字符上限。 |
| `character_limit_reached` | 402 | 帳戶字符池不足，該句不會合成。 |
| `concurrency_limit_reached` | 429 | 同時 session 或同時未結算句數已達上限。 |
| `session_char_limit_exceeded` | 403 | 已達本次 session 字數上限。 |
| `raw_limit_exceeded` | 413 | 原始輸入量（含標點／空白）超出上限。 |
| `text_too_long` | 400 | 單段文字超出上限。 |
| `invalid_session` | 400 | session 已關閉或狀態無效。 |
| `engine_unavailable` | 503 | 實時引擎暫時不可用；請重試。 |

## 錯誤碼

錯誤回應使用穩定的機器可讀 `error` 代碼，另附可選的人類可讀 `message`，例如 `{"error": "text_too_long", "message": "…"}`。

| HTTP | error | 原因／處理方法 |
|---|---|---|
| 400 | `invalid_model` | model 必須是 "cantogen-tts"（或省略）。 省略 model，或設為 "cantogen-tts"。 |
| 400 | `voice_mode_conflict` | 同時傳入了多於一個 voice / design / clone — 三種模式互斥。 每次請求只傳入一種聲音模式。 |
| 400 | `text_required` | text 缺失或空白。 傳入非空 text（1–500 字符）。 |
| 400 | `text_too_long` | text 超過 500 字符。 請拆分文字 — 超限請求會被拒絕，不會截斷處理。 |
| 400 | `unsupported_format` | response_format 必須是 mp3 或 wav。 使用 mp3 或 wav。 |
| 400 | `invalid_pitch` | pitch 必須介乎 −12 至 +12 半音。 將 pitch 調整至有效範圍。 |
| 400 | `invalid_volume` | volume 必須介乎 0 至 10。 將 volume 調整至有效範圍。 |
| 400 | `invalid_emotion` | emotion 不在 9 個支援值之內。 使用 9 個支援的情緒值之一。 |
| 400 | `invalid_srt` | srt 必須是 boolean。 使用 true / false。 |
| 400 | `reference_audio_required` | clone.reference_audio 缺失。 附上 base64 參考樣本。 |
| 400 | `invalid_reference_audio` | clone.reference_audio 不是有效的 base64 音訊。 傳入 data URL（data:audio/…;base64,…) 或 raw base64。 |
| 400 | `invalid_reference_audio_size` | clone.reference_audio 音頻超過約 3 MB（JSON API 單一請求上限）。 縮短樣本（至少 8 秒即可）再試。 |
| 400 | `spoken_text_required` | text 無可朗讀內容 — 計費字符為 0（只剩標點／空白／emoji／tone tags）。 傳入含可讀文字的 text。 |
| 400 | `design_gender_and_age_required` | design.gender 與 design.age 兩項均必填。 同時提供兩個欄位。 |
| 400 | `invalid_job_id` | 查詢或取消所用的 id 不是 UUID。 檢查 job_id。 |
| 400 | `invalid_request` | body 不是有效 JSON，或 Content-Type 非 JSON（/v1 僅接受 application/json）；文件轉寫則為 multipart 缺 `file`／`model`、音檔為空或音檔格式不支援。 以 Content-Type: application/json 傳送合法 JSON body。 |
| 413 | `payload_too_large` | 請求超過大小上限 — JSON API 4.4 MB（通常為 clone 參考樣本過長）；文件轉寫音檔 25 MB。 縮短參考樣本（音頻 ≤3 MB；樣本至少 8 秒即可）；文件轉寫請縮短音檔再試。 |
| 401 | `invalid_or_expired_token` | API 金鑰缺失、無效、已過期或已被撤銷。 建立新金鑰並檢查 Bearer header。 |
| 402 | `character_limit_reached` | 帳戶字符池不足以完成本次生成（回應內含 remaining）。 購買字符包或等待月度重置。 |
| 403 | `insufficient_scope` | 金鑰缺少所需 scope（複製聲音需要 tts:clone）；回應內含 "scope"。 建立具備所需 scope 的金鑰。 |
| 403 | `monthly_cap_reached` | 金鑰的每月字符上限已達；回應內含 "used"。 等候 UTC 月份重設，或調高上限。 |
| 404 | `job_not_found` | 沒有此 id 的任務（或任務屬於另一金鑰）。 檢查 job_id 及提交時所用的金鑰。 |
| 409 | `already_settled` | 任務已屬終態（completed/failed/cancelled），無法再取消，亦不會退款。 改以 GET /v1/audio/jobs/{id} 查詢最終狀態。 |
| 409 | `duplicate_request` | 同一個 Idempotency-Key（alias x-idempotency-key）已提交過（重試安全：不會重複扣費）。 沿用原本的 job_id 輪詢，或以新 key 提交新任務。 |
| 404 | `not_found` | 沒有此 endpoint（路徑有誤）。 對照 /v1/openapi.json 的路徑。 |
| 403 | `batch_requires_pro` | 批量生成（items）為入門／專業／工作室方案功能。單句任務的並行上限按方案分級（free 3／輕量 4／入門 5／專業 10／工作室 20／企業 20 個 pending），超上限回 429 in_flight_limit_reached。 待現有任務完成後再提交，或升級至入門／專業／工作室方案以解鎖批量生成與更高並行上限。 |
| 429 | `capacity_exceeded` | 公開 API 生成池到頂（全域並發上限）。 等候 Retry-After 秒再試，或改用非同步 `POST /v1/audio/jobs`。 |
| 429 | `rate_limit_exceeded` | 已觸及每金鑰每分鐘上限。 等候 Retry-After 秒（回應內容亦以 retry_after 標示），然後重試。 |
| 429 | `sandbox_daily_limit` | 免費方案每日 50 個生成請求上限已用完；輪詢與下載不受此限。 明日再試，或升級至輕量方案解鎖全速 API。 |
| 429 | `api_busy` | API 生成隊列已達全域容量上限（受理前即拒，不扣費）。 稍候數秒重試。 |
| 502 | `audio_delivery_failed` | 生成已完成，但音訊提取失敗。 改以 GET /v1/audio/jobs/{id} 輪詢並下載 audio_url。 |
| 503 | `sync_generation_failed` | 同步等待期間任務失敗或被取消（已自動退款）。 改用 POST /v1/audio/jobs（非同步）重試。 |
| 503 | `reference_upload_failed` | clone 參考音頻暫時儲存失敗。 直接重試。 |
| 503 | `voice_gallery_unavailable / jobs_unavailable` | 後端暫時性問題。 以指數退避重試。 |
| 400 | `unsupported_response_format` | 文件轉寫的 response_format 暫只支援 json／text — srt／verbose_json 需要段級時間戳，暫未提供。 使用 json 或 text。 |
| 402 | `insufficient_balance` | 預付餘額不足以預扣本次文件轉寫費用（未開始處理、零扣費）。 前往 /account/billing 儲值後重試（失敗不扣費）。 |
| 429 | `daily_limit_exceeded` | 已達每日計費音頻上限（24 小時／日，香港時間日界）。 香港時間明日再試。 |
| 503 | `engine_unavailable` | 文件轉寫服務暫時不可用（音檔時長偵測失敗、計費系統或轉寫管線暫時故障；超出現階段同步交付能力亦歸此類）。已預扣金額全額退回。 稍後重試；不會產生費用。 |

## 基礎建設

API 語音負載運行於專用 GPU 池 — 與互動 Playground 完全隔離，正式流量不會與任何瀏覽器工作階段爭奪算力。

## 參考與下載

### Endpoints

- `POST /v1/audio/speech` — 短文字同步生成 — 直接回傳 200 音訊 bytes（voice/design 模式，≤300 字符）。
- `POST /v1/audio/jobs` — 提交語音任務（單一或批次 ≤25；批次需入門方案或以上）。回傳 202 及 job_id 與 poll_url。
- `GET /v1/audio/jobs/{id}` — 查詢任務狀態；完成後回應會附上預簽名的 audio_url。
- `GET /v1/voices` — 列出廣東話優化聲線目錄，附試聽片段。此 endpoint 可匿名存取。
- `POST /v1/asr/transcribe` — 文件轉寫 — 上載音檔同步回傳文字；按音頻時長計費（文件轉寫 HK$1.40／小時），只扣成功。
- `GET /v1/balance` — 查詢預付餘額（HKD）、文件轉寫／實時聽寫費率與自動儲值設定。

- 互動式參考（Scalar）：https://canto-gen.com/v1/docs/reference
- OpenAPI 3.1 規格：https://canto-gen.com/v1/openapi.json — 可直接生成所用語言的 typed client
- 本頁純文字 Markdown 版：https://canto-gen.com/v1/docs.md
- Agent 索引：https://canto-gen.com/llms.txt
- AI agent 適用 MCP：local stdio server（npm 套件 `cantogen-mcp`，執行 `npx -y cantogen-mcp`；設定指示見 /llms.txt）

---

港嘢 Canto-Gen — Built in Hong Kong.
