目錄(21 節)
粵語 TTS API 快速上手(Canto-Gen 港嘢)
三分鐘用港嘢 Canto-Gen API 生成第一句廣東話語音:攞 API key、POST /v1/audio/jobs 提交任務、輪詢攞音頻 — curl/Node/Python 範例+錯誤處理對照表。
粵語 TTS API 快速上手(Canto-Gen 港嘢)
一句答你:註冊攞 API key →
POST /v1/audio/jobs提交文字 → 輪詢個 job 到completed→ 由audio_url攞音頻。四步,三分鐘出第一句廣東話。
港嘢(Canto-Gen)係一個為廣東話而設嘅文字轉語音 API。一個 REST API + 一個官方 MCP server,令你嘅後端或者 AI agent 直接生成地道廣東話語音 — 唔使錄音、唔使請配音員。
唔少開發者第一次接 TTS 都撞過同一個位:服務標明支援「Chinese」,接通之後出嚟係普通話音,廣東話文本照樣用普通話聲線讀。港嘢由底層就係為香港廣東話而訓練,language 參數預設 yue,專登出粵語,唔使估會出乜嘢音。想先全面了解粵語 TTS 嘅能力同揀選準則,睇 廣東話 TTS 完全指南;想玩「設計聲線/情緒/風格」進階參數,睇 點樣用 API 生成地道粵語語音。
Key Takeaways
- 主路徑係 async:
POST /v1/audio/jobs提交 → 回202+job_id→ pollGET /v1/audio/jobs/{id}到completed→ 下載audio_url- ≤300 字符短文本有 sync 捷徑(
POST /v1/audio/speech,voice/design mode 限定;目前恆回 503pool_cold,新整合直接用 async 最穩陣)text上限 1–500 字符:超上限係拒單、唔會幫你截斷 — 長稿要預先分段- 錯誤碼穩定、機器可讀:
text_too_long/pool_cold/rate_limit_exceeded各有明確對應做法(文末有錯誤處理表)- 批量一次 request 最多 25 個項目,驗證全有或全無(專業/工作室計劃專屬)
- 唔想寫 code?官方 MCP server(npm 套件
cantogen-mcp)令 AI agent 直接用你個 key 生成語音
端點總覽
| Method | Endpoint | 用途 |
|---|---|---|
POST | /v1/audio/jobs | 提交生成任務(async 主路徑),回 202 + job_id |
GET | /v1/audio/jobs/{id} | 查詢任務狀態;completed 後附 audio_url(請求過 SRT 嘅仲有 srt 欄) |
POST | /v1/audio/speech | 同步生成短語音(≤300 字符,voice/design mode 限定),直接回傳 audio bytes |
DELETE | /v1/audio/jobs/{id} | 取消排隊/處理中嘅任務 |
GET | /v1/voices | 列出官方聲線目錄,附 preview 試聽 URL |
GET | /v1/voices/{slug}/styles | 列出單一聲線支援嘅演繹風格 |
官方 OpenAPI 3.1 spec 喺 /v1/openapi.json(免 auth、CORS 全開,可以用嚟生成 typed client);互動式文件喺 /v1/docs。
第一步:取得 API key
- 登入 playground,開「API 金鑰」頁
- 建立一條 key — 格式
sk_live_開頭;建立時可以設 rate limit(預設每分鐘 60 次)同月度字符上限 - 原始 key 只顯示一次 — server 只存 SHA-256 hash;冇 copy 低就即時 revoke 重開一條
export CANTOGEN_API_KEY="sk_live_..."
Scope 同 plan 各有兩個留意位:
- Scope:
tts:generate(預設)已涵蓋聲線目錄讀取+voice/design 生成;要聲音複製先加tts:clone - 免費/入門計劃:可以開
sk_test_sandbox key 試接 — 每日 20 個生成 call(計生成 submission,poll/download 唔計);升級專業後現有 key 即時自動變 full mode - API 同 playground 1:1 共用 plan 配額 — 1 字符 = 1 字符,零附加費
第二步:提交生成任務
先攞聲線目錄,揀一把聲:
curl -s https://canto-gen.com/v1/voices \
-H "Authorization: Bearer $CANTOGEN_API_KEY"
回應每把聲帶 slug、name、gender、age、styles、preview_url(試聽 URL)。目錄係 live 嘅 — slug 由你自己嘅 GET /v1/voices 回應度揀;下面用 "sum"(心妍,female、adult)做文件例子。
提交生成任務:
curl -s -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:
{
"job_id": "b7e6f2a1-4c3d-4e5f-8a9b-0c1d2e3f4a5b",
"status": "queued",
"poll_url": "/v1/audio/jobs/b7e6f2a1-4c3d-4e5f-8a9b-0c1d2e3f4a5b",
"srt_requested": false
}
每次成功 async 提交都回 202,body 四個欄位:job_id/status/poll_url/srt_requested。意思係「任務收咗,排緊隊」— 音頻要去 poll 嗰步先攞到。
批量提交 — 一個 request 帶最多 25 個項目(專業/工作室計劃專屬;免費/入門帶 items 會被零提交、零扣費擋返):
{ "items": [ { "model": "cantogen-tts", "voice": "sum", "text": "第一句。" } ] }
批量驗證係全有或全無:任何一項無效,成個 request 回 400 invalid_items — 零提交、零扣費;超過 25 項回 400 too_many_items。交夠數之後回應逐項報結果:202 回 body 係 {"jobs":[{index, job_id, status, poll_url}, …]}。批量一次請求計一次限流(唔係逐項計)。
第三步:輪詢到完成,下載音頻
GET /v1/audio/jobs/{id} 查狀態:
curl -s https://canto-gen.com/v1/audio/jobs/<job_id> \
-H "Authorization: Bearer $CANTOGEN_API_KEY"
狀態流轉:queued → processing → completed | failed | cancelled。poll 頻率建議每 2–5 秒一次(簡單 backoff 就夠)。status 變 completed 時,回應帶 audio_url:
{
"job_id": "b7e6f2a1-4c3d-4e5f-8a9b-0c1d2e3f4a5b",
"status": "completed",
"poll_url": "/v1/audio/jobs/b7e6f2a1-4c3d-4e5f-8a9b-0c1d2e3f4a5b",
"srt_requested": false,
"audio_url": "https://canto-gen.com/storage/v1/object/sign/audio_history/..."
}
下載唔使 auth header(URL 本身已 pre-signed):
curl -sL -o speech.mp3 "$AUDIO_URL"
audio_url有時效:materialized 音頻庫嘅 pre-signed URL 有效 1 小時;由生成 worker 直接出嘅 URL(未 materialize)有效 7 天。盡快下載;過期就 re-poll 同一條 job 攞新 URL。job 仲喺queued/processing、或者已failed/cancelled時,audio_url唔存在。呢條 poll URL 仲有兩個細陷阱:job id 打錯(唔係 UUID)回
400 invalid_job_id;條 id 存在但唔屬於你個 key,回404 job_not_found。
同步捷徑:短文本直接回音頻(≤300 字符)
POST /v1/audio/speech 係同步版:喺同步池暖機嘅情況下,≤300 個計費字符、voice/design mode 限定,回 200 OK 直接係 audio bytes,唔使 poll。回應 header 行 X-Audio-Duration(音頻長度,秒)同 X-Credits-Charged(實扣字符數),方便收音嗰邊即場驗:
curl -s -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
兩個 sync 限定嘅限制:clone mode 回 400 clone_not_supported_sync;srt: true 回 400 srt_not_supported_sync — 兩樣都係 async path 先有。
而家 sync 捷徑暫時用唔到:同步專用池唔暖嘅時候,endpoint 會 fail closed — 恆回 503 pool_cold(body 有 hint 提你轉用 async),冇靜默 fallback。所以新整合成條 pipeline 用 async 寫最穩陣,sync 當佢係之後嘅延遲優化,唔好當依賴。
用 Node.js 生成第一句(完整可用)
Node.js 18+ 內置 fetch,唔使裝 SDK。完整流程:提交 → poll → 寫檔:
// gen.mjs — node gen.mjs
const BASE = "https://canto-gen.com";
const KEY = process.env.CANTOGEN_API_KEY;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function generate(text, voice = "sum") {
// 1) 提交
const submit = await fetch(`${BASE}/v1/audio/jobs`, {
method: "POST",
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ model: "cantogen-tts", voice, text, response_format: "mp3" }),
});
if (submit.status !== 202) {
const err = await submit.json().catch(() => ({}));
throw new Error(`submit failed: ${submit.status} ${err.error ?? ""}`);
}
const { job_id } = await submit.json();
// 2) 輪詢
for (;;) {
await sleep(3000);
const poll = await fetch(`${BASE}/v1/audio/jobs/${job_id}`, {
headers: { Authorization: `Bearer ${KEY}` },
});
const job = await poll.json();
if (job.status === "completed") return job.audio_url;
if (job.status === "failed" || job.status === "cancelled") {
throw new Error(`job ended: ${job.status}`);
}
// queued / processing → 繼續等
}
}
// 3) 下載(audio_url 係 pre-signed,唔使 auth header)
const url = await generate("你好,歡迎使用港嘢!");
const audio = await fetch(url);
const buf = Buffer.from(await audio.arrayBuffer());
const fs = await import("node:fs");
fs.writeFileSync("speech.mp3", buf);
console.log("saved speech.mp3");
三個位值得留意:
- poll 期間
audio_url唔存在 — 上面例子先 poll 到completed就即刻攞,冇呢個問題;如果將 URL 存落隊列隔夜先處理,記得 re-poll 換新 URL(1 小時/7 天時效,見上節) 202以外嘅 submit 回應都係失敗 — 400 族係 request 資料問題(改了再試),429 撞限流(睇Retry-After),403monthly_cap_reached係 key 嘅月度上限到text長度你自己把關 — API 會直接拒絕超過 500 字符嘅文本(唔會截斷),段落切分喺你个 client 做
用 Python 生成第一句(完整可用)
Python 3 內置 urllib 都得,但用 requests 最易讀(pip install requests):
# gen.py — python gen.py
import os
import time
import requests
BASE = "https://canto-gen.com"
KEY = os.environ["CANTOGEN_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}
def generate(text: str, voice: str = "sum") -> str:
# 1) 提交
r = requests.post(
f"{BASE}/v1/audio/jobs",
headers={**HEADERS, "Content-Type": "application/json"},
json={"model": "cantogen-tts", "voice": voice, "text": text, "response_format": "mp3"},
timeout=30,
)
r.raise_for_status()
job_id = r.json()["job_id"]
# 2) 輪詢
while True:
time.sleep(3)
p = requests.get(f"{BASE}/v1/audio/jobs/{job_id}", headers=HEADERS, timeout=30)
p.raise_for_status()
job = p.json()
if job["status"] == "completed":
return job["audio_url"]
if job["status"] in ("failed", "cancelled"):
raise RuntimeError(f"job ended: {job['status']}")
# 3) 下載(pre-signed URL,唔使 auth header)
url = generate("你好,歡迎使用港嘢!")
audio = requests.get(url, timeout=120)
audio.raise_for_status()
with open("speech.mp3", "wb") as f:
f.write(audio.content)
print("saved speech.mp3")
一個位提醒:r.raise_for_status() 會喺 4xx/5xx 時直接擲 exception,真正嘅錯誤碼喺 response body 嘅 error 欄 — 生產環境想按錯誤碼分行為(例如 429 等 Retry-After 秒再試),就要 r.json() 先至攞到 code。
SRT 字幕:加一個參數就出
提交時加 "srt": true,完成嘅 job poll 回應會多一個 srt 欄,內容係標準 SRT 格式字幕:
curl -s -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": "你好,歡迎使用港嘢!今日同大家示範一下。",
"srt": true
}'
completed 之後嘅 poll 回應:
{
"job_id": "b7e6f2a1-...",
"status": "completed",
"srt_requested": true,
"srt": "1\n00:00:00,000 --> 00:00:02,400\n你好,歡迎使用港嘢!",
"audio_url": "https://..."
}
三個位留意:
srt_requested永遠會喺 job 回應出現;srt欄就只有請求咗字幕、而且字幕 ready 先有language參數:yue/zh/en,預設yue。文本以廣東話為主就照用預設,唔使特意改- SRT 有字符附加收費(SRT 附加費)— 詳細計費睇 /v1/docs
想配合剪片流程用 SRT(時間軸對齊、批量調整),詳細 workflow 睇 TTS 加 SRT 字幕。
批量同長稿嘅建議流程
結合上面所有限制,生產級用法有兩條硬規則:
- 單項 ≤500 字符 — 長稿按逗號/句號切分段落,每段一個 item
- 批量一次過派 — 25 段為一組交
items(Pro/Studio plan),202回應逐項有job_id,輪詢時每條 job 自己 poll
詳細批量 pipeline(切稿、失敗重試、合併音檔)睇 批量生成 workflow;輪詢想寫得穩陣啲(超時、退避、ghost job 處理),睇 TTS 輪詢 Polling 指南。
錯誤處理對照表
常見錯誤碼同對應做法。統一回應格式:{ "error": "<code>", "message": "…" } — 程式處理認 error code,唔好 parse message 文字。
| HTTP | error code | 乜嘢事 | 點對應 |
|---|---|---|---|
| 400 | text_too_long | text 超過 500 字符 | 拒喺 client 端切分段落再交;唔會有部分截斷結果 |
| 400 | text_required | 冇 text 欄 | 檢查 request body |
| 400 | voice_mode_conflict | voice/design/clone 傳咗多過一個 | 每次請求淨係揀一個 mode |
| 400 | invalid_emotion | emotion 唔係 9 個支援值之一 | 改用支援值(見 /v1/docs) |
| 400 | clone_not_supported_sync | sync 端點交 clone mode | 轉用 async POST /v1/audio/jobs |
| 400 | srt_not_supported_sync | sync 端點開 srt: true | 轉用 async,srt: true 一併交嗰度 |
| 400 | invalid_items/too_many_items | 批量有無效項目/超過 25 項 | 修正後重新交;全有或全無,唔怕半交 |
| 401 | invalid_or_expired_token | key 無效/過期/被 revoke | 攞返當時 copy 嗰條 key;唔見咗就 revoke 重開一條 |
| 403 | insufficient_scope | key 冇所需 scope(clone 要 tts:clone) | 開過一條帶齊 scope 嘅 key,或者補 scope(重新建 key) |
| 403 | monthly_cap_reached | key 嘅月度字符上限到(body 有 used) | 等月度重置,或者調高 key 上限 |
| 404 | job_not_found | job id 唔存在/唔屬於呢條 key | 核對 job_id 來源 |
呢度特登抽三個出嚟講,因為佢哋嘅處理方式最反直覺:
pool_cold(503) — 只會喺 sync 端點出現:同步池未暖,endpoint fail closed 冇靜默 fallback。做法就一個:轉 async。呢個唔係「服務壞咗」,係設計上嘅明確信號rate_limit_exceeded(429) — 滑動 60 秒窗口計成功計費請求,預設 60/min。睇Retry-Afterheader(body 都有),等夠秒數再交;讀取 endpoint(poll/voices)唔計入限流in_flight_limit_reached(429) — 在途任務上限:免費 3、入門 5、專業 10、工作室 20。等現有 job 完成,或者刪走未完成 job(DELETE /v1/audio/jobs/{id})騰位;批量一次交嘅嘢計一次在途配額
仲有兩個「唔係你個 code 嘅錯」:503 voice_gallery_unavailable/jobs_unavailable/audio_history_unavailable 係後端暫時性問題 — 用 backoff 重試就得。全部錯誤碼清單睇 /v1/docs。
速率限制細節
兩條獨立嘅 per-key 限制,都作用喺 POST /v1/audio/jobs 同 sync POST /v1/audio/speech:
- 每分鐘限流:滑動 60 秒窗口,計成功嘅計費請求,預設 60/min(開 key 時可調)。撞牆回
429 rate_limit_exceeded+Retry-After(秒)。X-RateLimit-Limit/X-RateLimit-Remaining/X-RateLimit-Reset(Unix epoch 秒)出現喺POST /v1/audio/jobs嘅202/429同 sync 嘅200/429 - 月度字符上限:key 嘅月內(UTC)累計計費字符唔可以過 cap(開 key 時設,
null= 無上限)。超咗回403 monthly_cap_reached+used數字,月度重置先回復
注意方向:讀取 endpoint 唔限流 — poll、GET /v1/voices 呢類唔計入去,所以輪詢頻率唔會撞 429(但都唔好每秒 poll 十幾次,純粹浪費你自己資源)。另外兩個 meter 喺後端故障時 fail open — 計費系統出事唔會鎖你出返嚟。
用 MCP 令 AI agent 自己識用
唔想自己寫 poll loop?港嘢提供官方 MCP server(npm 套件 cantogen-mcp,本地 stdio 執行,零託管):Claude Desktop、Claude Code、Cursor、VS Code(Copilot)、Codex 呢類 MCP client 接入之後,AI agent 就可以直接列聲線、生成語音。
npx -y cantogen-mcp
以 Claude Code 為例,一行加入:
claude mcp add cantogen \
-e CANTOGEN_APP_URL=https://canto-gen.com \
-e CANTOGEN_API_KEY=<sk_live_...> \
-- npx -y cantogen-mcp
其他 client(Cursor/VS Code/Codex 嘅 config JSON 形狀)照官方文件抄 — /v1/docs 入面嘅 MCP/Agent 章節有齊。Agent-side 接入嘅完整教學( tool schema、設計考量、實戰例子)另見 MCP 接入粵語 TTS 完整教學。
常見問題
港嘢 API 免費試到咩程度?
開戶每月有 5,000 字符(月月重置),試晒本文啲範例都夠;另外免費/入門計劃可以開 sk_test_ sandbox key — 每日 20 個生成 call(poll/download 唔計)。用量大咗先升專業/工作室。API 同 playground 共用同一份 plan 配額。
text 上限幾多?長稿點算?
單次請求 text 上限 500 字符,超上限直接拒單、唔會自動截斷。長稿喺 client 端按標點切段落,每段一個 request 或者入批量 items — 即係「分段生成、逐段拼接」。
點解 POST /v1/audio/speech 回 503?
pool_cold 代表同步專用池未暖機,sync 捷徑 fail closed、冇靜默 fallback。唔使 debug — 直接用 async POST /v1/audio/jobs,呢個係主路徑,產能同穩定性都係 async 嗰邊最好。
429 之後應該點做?
睇 Retry-After header(或 body 嘅 retry_after 欄),等夠秒數再交同一個 request。唔使指數退避到好耐 — 限流係每分鐘窗口,等 Retry-After 就精準。撞 in_flight_limit_reached 就唔同:要等喺途 job 完成,唔係過限流窗口。
點樣揀聲線?
GET /v1/voices 攞目錄,逐把聲開 preview_url 試聽 — slug 係穩定 ASCII id(例如 sum),中文名只係介面顯示。寫 code 用 slug,唔好 hardcode 中文名。揀聲線嘅完整方法(性別、年齡、風格匹配)睇 點樣用 API 生成地道粵語語音。
音頻 URL 過期咗點算?
re-poll 同一條 GET /v1/audio/jobs/{id} 就會攞到新 pre-signed URL。materialized 庫嘅 URL 有效 1 小時;worker 直接出嘅 URL 有效 7 天。最佳做法:job 一 completed 就即刻下載,唔好攞住條 URL 放幾日。
仲有邊啲參數可以玩?
emotion(9 個英文值:happy/sad/angry 等)、style(演繹風格)、instruction(自由文字演繹指示,最多 500 字符,優先級 style → emotion → instruction 之中最高)、response_format(mp3/wav)。注意 speed/pitch/volume 已於 2026-09-20 棄用(API 照收但唔生效),新整合唔好送。全部參數細節睇 /v1/docs;情緒/風格實戰示範睇 TTS 情緒控制實作。
開戶每月 5,000 字符就夠你行晒本文所有範例 — 免費開始。