目錄(21 節)

粵語 TTS API 快速上手(Canto-Gen 港嘢)

三分鐘用港嘢 Canto-Gen API 生成第一句廣東話語音:攞 API key、POST /v1/audio/jobs 提交任務、輪詢攞音頻 — curl/Node/Python 範例+錯誤處理對照表。

— 港嘢內容團隊約 11 分鐘ttstutorial
分享:

粵語 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 → poll GET /v1/audio/jobs/{id} 到 completed → 下載 audio_url
  • ≤300 字符短文本有 sync 捷徑(POST /v1/audio/speech,voice/design mode 限定;目前恆回 503 pool_cold,新整合直接用 async 最穩陣)
  • text 上限 1–500 字符:超上限係拒單、唔會幫你截斷 — 長稿要預先分段
  • 錯誤碼穩定、機器可讀:text_too_long/pool_cold/rate_limit_exceeded 各有明確對應做法(文末有錯誤處理表)
  • 批量一次 request 最多 25 個項目,驗證全有或全無(專業/工作室計劃專屬)
  • 唔想寫 code?官方 MCP server(npm 套件 cantogen-mcp)令 AI agent 直接用你個 key 生成語音

端點總覽

MethodEndpoint用途
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

  1. 登入 playground,開「API 金鑰」頁
  2. 建立一條 key — 格式 sk_live_ 開頭;建立時可以設 rate limit(預設每分鐘 60 次)同月度字符上限
  3. 原始 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),403 monthly_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 字幕。

批量同長稿嘅建議流程

結合上面所有限制,生產級用法有兩條硬規則:

  1. 單項 ≤500 字符 — 長稿按逗號/句號切分段落,每段一個 item
  2. 批量一次過派 — 25 段為一組交 items(Pro/Studio plan),202 回應逐項有 job_id,輪詢時每條 job 自己 poll

詳細批量 pipeline(切稿、失敗重試、合併音檔)睇 批量生成 workflow;輪詢想寫得穩陣啲(超時、退避、ghost job 處理),睇 TTS 輪詢 Polling 指南。

錯誤處理對照表

常見錯誤碼同對應做法。統一回應格式:{ "error": "<code>", "message": "…" } — 程式處理認 error code,唔好 parse message 文字。

HTTPerror code乜嘢事點對應
400text_too_longtext 超過 500 字符拒喺 client 端切分段落再交;唔會有部分截斷結果
400text_required冇 text 欄檢查 request body
400voice_mode_conflictvoice/design/clone 傳咗多過一個每次請求淨係揀一個 mode
400invalid_emotionemotion 唔係 9 個支援值之一改用支援值(見 /v1/docs)
400clone_not_supported_syncsync 端點交 clone mode轉用 async POST /v1/audio/jobs
400srt_not_supported_syncsync 端點開 srt: true轉用 async,srt: true 一併交嗰度
400invalid_items/too_many_items批量有無效項目/超過 25 項修正後重新交;全有或全無,唔怕半交
401invalid_or_expired_tokenkey 無效/過期/被 revoke攞返當時 copy 嗰條 key;唔見咗就 revoke 重開一條
403insufficient_scopekey 冇所需 scope(clone 要 tts:clone)開過一條帶齊 scope 嘅 key,或者補 scope(重新建 key)
403monthly_cap_reachedkey 嘅月度字符上限到(body 有 used)等月度重置,或者調高 key 上限
404job_not_foundjob id 唔存在/唔屬於呢條 key核對 job_id 來源

呢度特登抽三個出嚟講,因為佢哋嘅處理方式最反直覺:

  • pool_cold(503) — 只會喺 sync 端點出現:同步池未暖,endpoint fail closed 冇靜默 fallback。做法就一個:轉 async。呢個唔係「服務壞咗」,係設計上嘅明確信號
  • rate_limit_exceeded(429) — 滑動 60 秒窗口計成功計費請求,預設 60/min。睇 Retry-After header(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 字符就夠你行晒本文所有範例 — 免費開始。

相關文章