📖 API Documentation
REST API dành cho developer — tích hợp TTS Tiếng Việt và STT audio/video vào ứng dụng của bạn.
- Đăng ký tài khoản miễn phí → nhận credits khởi đầu
- Vào API Keys → tạo key mới
- Gửi request với header
Authorization: Bearer YOUR_API_KEY - Đọc docs phía dưới để biết endpoints + parameters
🔐 Authentication
Mọi API request cần gửi API key qua HTTP header:
Authorization: Bearer sk-ov-YOUR_API_KEY
- Đăng ký tài khoản tại /register
- Vào /api-keys → click "Tạo Key mới"
- Copy key (format
sk-ov-xxxxxxxx...) và gửi trong header mọi request
⚠️ Key không hợp lệ hoặc bị disable → HTTP 401 Unauthorized.
Có thể quản lý (Active / Disabled / Delete) nhiều keys cùng lúc trong tab API Keys.
⏱️ Rate Limits
| Loại giới hạn | Giá trị | Mô tả |
|---|---|---|
| Requests/phút (per user) | 10 | Số request tối đa 1 phút/user |
| Concurrent jobs (API, per user) | 2 | Số job xử lý đồng thời/user |
| Concurrent jobs (server total) | 10 | Tổng concurrent jobs toàn server |
Khi vượt giới hạn → HTTP 429 Too Many Requests. Client nên:
- Wait & retry: chờ 5-10s rồi thử lại
- Exponential backoff: retry với delay 2s, 4s, 8s, 16s... nếu vẫn 429
- Dùng async_mode=true cho TTS/STT job dài — tránh blocking + tối ưu throughput
- Liên hệ admin để tăng quota nếu cần
📌 Rate limits có thể được admin điều chỉnh per-user. Xem limits hiện tại của bạn trong /dashboard.
Endpoints
| Method | Endpoint | Mô tả |
|---|---|---|
| GET | /api/v1/health | Kiểm tra trạng thái server |
| GET | /api/v1/options | Lấy danh sách tùy chọn voice |
| POST | /api/v1/tts/design | Tạo voice từ mô tả |
| POST | /api/v1/tts/clone | Clone voice từ audio mẫu |
| POST | /api/v1/tts/auto | Tự động tạo voice |
| GET | /api/v1/voices | Lấy danh sách giọng đã lưu |
| POST | /api/v1/tts/saved | Tạo voice từ giọng đã lưu (voice_id) |
| POST | /api/v1/tts/dialogue | Hội thoại đa giọng (Multi-Voice Dialogue) |
| POST | /api/v1/stt | Chuyển giọng nói thành text (STT v1, Whisper HuggingFace) |
| POST | /api/v2/stt | ⚡ STT v2 (FasterWhisper - nhanh hơn 4-5x, có VAD) |
| POST | /api/v1/srt-to-voice | SRT → Voice (timing-synced TTS) |
| POST | /api/v1/audio-separate | Tách vocals khỏi nhạc nền (Demucs) |
| GET | /api/v1/job/{job_id} | Kiểm tra trạng thái job (async mode) — dùng chung cho v1 + v2 |
| POST | /api/v1/job/{job_id}/cancel | Hủy job (graceful cho processing, partial refund theo % cues đã xong) |
1. Text-to-Speech (TTS)
POST /api/v1/tts/design — Tạo voice từ mô tả
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
text | string | ✅ | Nội dung cần đọc |
language | string | ❌ | Ngôn ngữ: vietnamese, english, chinese, japanese, korean, french... |
gender | string | ❌ | male / female |
age | string | ❌ | child, young adult, middle-aged, elderly |
pitch | string | ❌ | low pitch, moderate pitch, high pitch |
style | string | ❌ | normal, cheerful, sad, angry, whisper... |
instruct | string | ❌ | Mô tả trực tiếp (thay thế gender/age/pitch/style) |
speed | float | ❌ | Tốc độ đọc (default: 1.0) |
num_step | int | ❌ | Số bước diffusion (default: 32, cao hơn = chất lượng hơn) |
guidance_scale | float | ❌ | Guidance scale (default: 2.0) |
denoise | bool | ❌ | Khử nhiễu (default: true) |
async_mode | bool | ❌ | Chế độ async: trả về job_id thay vì chờ audio (default: false) |
📥 Response: audio/wav file | Nếu async_mode=true: JSON {job_id, status, credits_used}
POST /api/v1/tts/clone — Clone voice từ audio mẫu
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
text | string | ✅ | Nội dung cần đọc |
ref_audio | file | ✅ | File audio mẫu để clone giọng (wav, mp3, flac...) |
language | string | ❌ | Ngôn ngữ: vietnamese, english, chinese... |
speed | float | ❌ | Tốc độ đọc (default: 1.0) |
num_step | int | ❌ | Số bước diffusion (default: 32) |
denoise | bool | ❌ | Khử nhiễu (default: true) |
async_mode | bool | ❌ | Chế độ async: trả về job_id thay vì chờ audio (default: false) |
📥 Response: audio/wav file | Nếu async_mode=true: JSON {job_id, status, credits_used}
POST /api/v1/tts/auto — Tự động tạo voice
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
text | string | ✅ | Nội dung cần đọc |
language | string | ❌ | Ngôn ngữ (default: auto) |
speed | float | ❌ | Tốc độ đọc (default: 1.0) |
num_step | int | ❌ | Số bước diffusion (default: 32) |
denoise | bool | ❌ | Khử nhiễu (default: true) |
async_mode | bool | ❌ | Chế độ async: trả về job_id thay vì chờ audio (default: false) |
📥 Response: audio/wav file | Nếu async_mode=true: JSON {job_id, status, credits_used}
POST /api/v1/tts/saved — Tạo voice từ giọng đã lưu
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
text | string | ✅ | Nội dung cần đọc |
voice_id | int | ✅ | ID giọng đã lưu (lấy từ GET /api/v1/voices) |
language | string | ❌ | Ngôn ngữ (default: auto) |
speed | float | ❌ | Tốc độ đọc (default: 1.0) |
num_step | int | ❌ | Số bước diffusion (default: 32) |
denoise | bool | ❌ | Khử nhiễu (default: true) |
async_mode | bool | ❌ | Chế độ async: trả về job_id thay vì chờ audio (default: false) |
📥 Response: audio/wav file | Nếu async_mode=true: JSON {job_id, status, credits_used}
POST /api/v1/tts/dialogue — Hội thoại đa giọng
Tạo audio hội thoại với nhiều nhân vật, mỗi nhân vật dùng 1 giọng đã lưu. Tính phí theo tổng ký tự lời thoại (không tính #TênNhânVật).
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
text | string | ✅ | Kịch bản hội thoại. Format: #TênNhânVật trên 1 dòng, lời thoại bên dưới |
char_voice_map | string (JSON) | ✅ | JSON mapping nhân vật → voice_id. VD: {"MC": 1, "Guest": 2} |
language | string | ❌ | Ngôn ngữ (default: auto) |
speed | float | ❌ | Tốc độ đọc (default: 1.0) |
num_step | int | ❌ | Số bước diffusion (default: 32) |
denoise | bool | ❌ | Khử nhiễu (default: true) |
async_mode | bool | ❌ | Chế độ async (default: false) |
// text format:
#MC
Xin chào các bạn, hôm nay chúng ta sẽ nói về AI.
#Guest
Cảm ơn MC, tôi rất vui được ở đây.
#MC
Vậy anh có thể giới thiệu về mình không?
// char_voice_map:
{"MC": 1, "Guest": 2}
📥 Response: audio/wav file | Nếu async_mode=true: JSON {job_id, status, credits_used}
GET /api/v1/voices — Lấy danh sách giọng đã lưu (có filter)
Query params (đều tùy chọn — combine bằng AND):
| Tham số | Loại | Giá trị / Ghi chú |
|---|---|---|
language | string | BCP 47 code: vi, en, en-US, en-GB, zh-CN, ja, ko, fr, de, es, th, ... (79 mã) |
gender | string | male | female | neutral |
age_range | string | child | teen | young_adult | adult | senior |
country | string | ISO 3166 alpha-2: VN, US, GB, JP, ... (107 mã) |
q | string | Tìm kiếm (LIKE) trên name/notes/instruct — case-insensitive |
// GET /api/v1/voices?language=vi&gender=female&age_range=young_adult
// Response
[
{
"id": 42,
"name": "MC Nữ Trẻ",
"type": "clone", // "clone" | "design"
"instruct": "female, young adult, warm",
"ref_text": "Xin chào...",
"is_public": false,
"language": "vi", // BCP 47 code
"gender": "female", // enum
"age_range": "young_adult", // enum
"country": "VN", // ISO 3166 alpha-2
"notes": "Giọng miền Nam", // free text
"created_at": "2026-07-09 10:30:00"
}
]
💡 Giới hạn lưu voice: mỗi tài khoản có hạn mức voice lưu (mặc định 3). Việc lưu giọng mới phải thực hiện qua giao diện web (POST /voices/save, cookie-auth). Khi đã đầy, request lưu trả về HTTP 409. Liên hệ admin để nâng hạn mức.
POST /api/v1/voices/match — Auto-map profile → voice_id
Gửi profile fields (bất kỳ format: tên đầy đủ, ISO, range strings, ...) — server normalize + áp dụng
priority-degraded matching: thử match tất cả 4 fields, nếu empty thì drop
country → thử lại → drop age_range → thử lại → chỉ còn language.
Trả về voice_id đầu tiên khớp + confidence level.
| Field | Loại | Accepted formats |
|---|---|---|
language | string | BCP 47 (en, en-US), TTS name (english), localized (tiếng việt) |
gender | string | Male, male, M, F, Female, Nam, Nữ, neutral, ... |
age | string \| number | "25-32", 28, "4-6", "young_adult", "middle-aged", ... |
country | string | "United States of America", "USA", "US", "Hoa Kỳ", "CHN", ... |
prefer_user_voices | bool | default true. Ưu tiên voices anh sở hữu trước public. |
include_reference_clones | bool | default true. Set false để loại bỏ voices có clone_source='reference' (chất lượng thấp hơn). |
profile_id | any | Optional. Echo lại trong response để pair kết quả với profile của anh. |
// Request
POST /api/v1/voices/match
Authorization: Bearer sk-ov-...
Content-Type: application/json
{
"profile_id": 56455,
"language": "en",
"gender": "Male",
"age": "25-32",
"country": "United States of America"
}
// Response 200
{
"profile_id": 56455,
"voice_id": 42,
"voice": {
"id": 42, "name": "MC Male US",
"type": "clone", "language": "en-US",
"gender": "male", "age_range": "young_adult",
"country": "US", "is_public": false
},
"confidence": "high", // "high"|"medium"|"low"|"none"
"matched_on": ["language", "gender", "age_range", "country"],
"dropped": [], // fields degraded to find a match
"normalized": {
"language": "en", "gender": "male",
"age_range": "young_adult", "country": "US"
},
"alternatives": [ // up to 3 more voices with same confidence
{"id": 45, "name": "...", ...}
]
}
// Response khi không tìm được match nào
{
"voice_id": null, "voice": null,
"confidence": "none",
"matched_on": [], "dropped": ["language","gender","age_range","country"],
"normalized": {...}, "alternatives": []
}
Confidence levels:
high = tất cả fields đều match |
medium = ≥2 fields match, 1 field bị drop |
low = chỉ language match |
none = không voice nào khớp.
POST /api/v1/voices/match/batch — Batch match nhiều profiles
Match nhiều profiles cùng lúc (tối đa 200 mỗi request). Hữu ích để map một bảng danh sách profile.
// Request
POST /api/v1/voices/match/batch
Authorization: Bearer sk-ov-...
Content-Type: application/json
{
"profiles": [
{"profile_id": 56455, "language": "en", "gender": "Male", "age": "4-6", "country": "USA"},
{"profile_id": 56456, "language": "en", "gender": "Female", "age": "25-32", "country": "USA"},
{"profile_id": 56460, "language": "it", "gender": "Male", "age": "25-32", "country": "Italy"}
]
}
// Response 200
{
"results": [
{"profile_id": 56455, "voice_id": 12, "confidence": "high", "matched_on": [...], ...},
{"profile_id": 56456, "voice_id": 15, "confidence": "medium", "dropped": ["country"], ...},
{"profile_id": 56460, "voice_id": null, "confidence": "none", ...}
],
"stats": {
"total": 3, "matched": 2, "unmatched": 1,
"by_confidence": {"high": 1, "medium": 1, "low": 0, "none": 1}
}
}
⚠️ Nếu số profiles > 200 → HTTP 400. Chia thành nhiều batch nếu cần.
POST /voices/analyze-audio — 🤖 AI-detect metadata từ audio
Upload audio → LLM (Gemini/GPT-4o) phân tích + return language/gender/age/country
+ confidence per field. Cookie-auth (login qua web). Admin phải bật
llm_voice_analysis_enabled = 1 + config API key trong admin panel trước.
| Field | Loại | Ghi chú |
|---|---|---|
audio | file | WAV/MP3/M4A/OGG/FLAC. Max 20MB (Gemini inline limit). |
// POST /voices/analyze-audio (multipart)
// Response (success)
{
"success": true,
"provider": "gemini",
"model": "gemini-2.5-flash",
"latency_ms": 2340,
"normalized": {
"language": "vi", // BCP 47 (đã normalize)
"gender": "female", // 'male' | 'female' | 'neutral'
"age_range": "young_adult",
"country": "VN" // ISO 3166 (empty nếu LLM không confident)
},
"confidence": {
"language": 0.95, "gender": 0.9,
"age_range": 0.72, "country": 0.85
},
"accent_notes": "Southern Vietnamese, warm tone",
"transcription_snippet": "Xin chào các bạn...",
"tokens_in": 500, "tokens_out": 100,
"raw": { /* LLM's raw JSON before normalization */ }
}
// Response (LLM/config error — HTTP 200 always, check `success`)
{
"success": false,
"error": "API key not configured for gemini. Vào admin → Cài đặt → AI Providers.",
"normalized": {...all None...}, "confidence": {...all 0...}
}
Supported providers: gemini (native audio ✅),
openai (GPT-4o audio ✅). Grok + DeepSeek chỉ text — nếu chọn
cho voice analysis sẽ error "does not support audio".
Setup keys: Admin → Cài đặt → 🤖 AI Providers → paste API keys.
1.1. Async Job Flow (Khuyến nghị cho văn bản dài)
Thêm async_mode=true vào bất kỳ TTS endpoint nào. Server trả về job_id ngay lập tức, bạn poll trạng thái để lấy audio khi xong.
# Step 1: Submit job
curl -X POST "https://your-domain/api/v1/tts/design" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "text=Nội dung dài cần xử lý..." \
-F "async_mode=true"
# Response:
# {"job_id": "abc123def456", "status": "queued", "credits_used": 2.5}
# Step 2: Poll status (mỗi 3-5 giây)
curl "https://your-domain/api/v1/job/abc123def456" \
-H "Authorization: Bearer YOUR_API_KEY"
# Response (đang xử lý):
# {"job_id": "abc123def456", "status": "processing"}
# Response (hoàn thành):
# {"job_id": "abc123def456", "status": "completed", "audio_url": "/audio/tts_xxx.wav"}
# Step 3: Download audio
curl -O "https://your-domain/audio/tts_xxx.wav" \
-H "Authorization: Bearer YOUR_API_KEY"
💡 Lưu ý: Nếu job thất bại, credits sẽ được hoàn lại tự động. Status: queued → processing → completed/failed.
2. Speech-to-Text (STT) — Audio & Video
POST /api/v1/stt — Chuyển audio/video thành text + SRT
Upload file audio HOẶC video để chuyển thành text. Video được auto-extract audio qua ffmpeg. Hỗ trợ 70+ ngôn ngữ. Output dạng JSON / SRT / VTT / TXT.
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
audio_file | file | ✅ | File audio (wav, mp3, flac, m4a, ogg...) hoặc video (mp4, mov, mkv, webm, avi, flv, wmv, m4v...). Tự động phát hiện qua extension. |
language | string | ❌ | Ngôn ngữ audio (default: auto detect). Ví dụ: vietnamese, english, japanese, korean... |
output_format | string | ❌ | json (default) — JSON full data · srt — tải file .srt · vtt — tải file .vtt · txt — tải file .txt |
async_mode | bool | ❌ | Chế độ async: trả về job_id thay vì chờ kết quả (default: false). Lưu ý: audio > 2 phút tự động chuyển sang async mode. |
async_mode=true.
Response trả job_id thay vì kết quả trực tiếp. Client cần poll GET /api/v1/job/{job_id} để lấy kết quả.File ≤ 2 phút vẫn hoạt động sync như bình thường (backward compatible).
📥 Response (Sync — file ≤ 2 phút): JSON | SRT | VTT | TXT tuỳ output_format.
// Response — Sync mode (audio ≤ 2 phút)
{
"text": "Nội dung được nhận diện từ audio...",
"language": "vietnamese",
"duration_secs": 45.2,
"credits_used": 135.00,
"media_type": "audio",
"srt_url": "/audio/stt_abc.srt"
}
📥 Response (Async — file > 2 phút hoặc async_mode=true):
// Step 1: Submit — trả job_id ngay lập tức
{
"job_id": "abc123def456",
"status": "queued",
"credits_used": 752.50,
"media_type": "video",
"duration_secs": 1200.0,
"async": true
}
// Step 2: Poll GET /api/v1/job/abc123def456
{
"status": "completed",
"job_type": "stt",
"text": "Nội dung...",
"language": "vietnamese",
"duration_secs": 1200.0,
"srt_url": "/audio/stt_abc.srt"
}
• Audio: 3000 credits / phút
• Video: 3600 credits / phút (bao gồm chi phí extract audio)
Giới hạn:
• Audio: max 50MB, 2000s
• Video: max 100MB, 1800s
⚡ 2b. Speech-to-Text v2 (FasterWhisper) — Nhanh hơn 4-5x
• Engine: FasterWhisper (CTranslate2 backend) thay vì HuggingFace pipeline
• Tốc độ: 4-5x nhanh hơn (audio 20 phút: v1 ~8 phút → v2 ~2 phút)
• VAD filter: cắt silence tự động → giảm hallucination, tăng tốc thêm
• LUÔN async (kể cả file ngắn < 2 phút): trả
job_id ngay, poll GET /api/v1/job/{id} đến khi status=completed để lấy text + srt_url• Không có sync mode như v1 — luôn trả JSON với
job_id, không bao giờ trả file trực tiếp• Hoàn toàn độc lập với
/api/v1/stt — không ảnh hưởng tool đang dùng v1
Khi gửi
vad_filter hay word_timestamps trong form-data:• ✅ ĐÚNG:
true, false, 1, 0, yes, no• ❌ SAI:
True (Pythonic), giá trị viết hoa khác — server sẽ ignore và dùng default từ Admin• Tốt nhất: dùng
1/0 để chắc chắn cross-platform
POST /api/v2/stt — Chuyển audio/video thành text + SRT (FasterWhisper)
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
audio_file | file | ✅ | File audio (wav, mp3, flac, m4a, ogg) hoặc video (mp4, mov, mkv, webm, avi, flv...). Auto-detect. |
language | string | ❌ | Ngôn ngữ (default: auto). Ví dụ: vietnamese, english, chinese, japanese... |
output_format | string | ❌ | json (default) — full data trong job poll · srt/vtt/txt |
beam_size | int | ❌ | Beam search size (1=greedy fastest, 5=balance default, 10=quality). Override Admin default |
vad_filter | bool | ❌ | Bật VAD lọc silence. Default: lấy từ Admin (mặc định true). Format chấp nhận: true/false, 1/0, yes/no |
temperature | float | ❌ | Sampling temp (0.0=deterministic, 0.4=variety). Default: lấy từ Admin (mặc định 0.0) |
initial_prompt | string | ❌ | Prompt giúp Whisper nhận diện thuật ngữ riêng. Default: lấy từ Admin (mặc định trống). VD: "Đây là podcast về AI, có nhắc tên Lương, Hùng" |
word_timestamps | bool | ❌ | Word-level timestamps (cần cho SRT chuẩn). Default: lấy từ Admin (mặc định true). Format giống vad_filter |
📥 Response (luôn async):
// Step 1: Submit
{
"job_id": "abc123def456",
"status": "queued",
"media_type": "audio",
"engine": "faster-whisper"
}
// Step 2: Poll GET /api/v1/job/abc123def456 → khi completed:
{
"status": "completed",
"job_type": "stt_v2",
"text": "Nội dung được nhận diện...",
"language": "vi",
"duration_secs": 1200.0,
"srt_file": "stt_xxx.srt",
"srt_url": "/audio/stt_xxx.srt",
"engine": "faster-whisper"
}
• Web: 1500 credits/phút
• API: 2500 credits/phút
Lưu ý: Video v2 KHÔNG tính phụ phí extract (khác với v1). Tính theo độ dài audio sau khi extract.
Giới hạn: dùng chung với v1 (audio max 50MB/2000s, video max 100MB/1800s)
Engine settings (Admin → STT v2): model size, compute type, beam size, VAD threshold, temperature, lazy unload
Cancel job đang chạy (apply cho cả v1 + v2)
Hỗ trợ cả 3 trạng thái với behavior khác nhau:
queued/picked: hủy ngay + refund 100% credits.processing(job đang chạy): server set flagcancel_requested, worker thoát graceful trong ~30 giây. Partial refund = (1 - done/total) × credits đã trừ. Vd: 50/100 cues đã xong → refund 50%.completed/failed/cancelled: trả 400 (đã terminal).
💡 Tự động cancel: nếu client bỏ poll status >30s (queued/picked) hoặc >120s (processing), server tự động cancel + refund (orphan detection).
curl -X POST "https://your-domain/api/v1/job/abc123/cancel" \
-H "Authorization: Bearer YOUR_API_KEY"
# Response cases:
# Queued/picked job:
# {"success":true,"status":"cancelled","credits_refunded":12.5,"message":"Job cancelled"}
#
# Processing job (graceful):
# {"success":true,"status":"cancel_requested",
# "message":"Cancellation requested — job will stop within ~30s; partial refund will apply"}
# → Then poll GET /api/v1/job/abc123 → status='cancelled', error='... at cue X/Y'
Job status response — progress fields
Response của GET /api/v1/job/{id} khi status là queued/picked/processing bao gồm:
{
"job_id": "abc123",
"status": "processing",
"job_type": "srt2voice",
"progress_done": 25, // cues/chunks đã xử lý
"progress_total": 50, // tổng cues/chunks
"progress_pct": 50, // = round(done * 100 / total)
"cancel_requested": true // chỉ có nếu user/orphan đã trigger cancel
}
# Khi completed:
{"job_id":"abc123","status":"completed","audio_url":"/audio/...","credits_used":12.5}
# Khi cancelled (sau partial refund):
{"job_id":"abc123","status":"cancelled","error":"Cancelled by user at cue 25/50 = 50%"}
⚠️ Best Practices khi build tool API client (QUAN TRỌNG)
3 lỗi phổ biến cần tránh:
1. ❌ Hardcode file size limit cho TẤT CẢ file
Server có 2 limit khác nhau: audio = 50MB, video = 100MB (mặc định, admin chỉnh được). Tool client nên detect video/audio theo extension và dùng limit phù hợp, hoặc bỏ luôn check client-side để server tự reject.
// ❌ SAI:
const long MAX_BYTES = 50 * 1024 * 1024; // chỉ phù hợp audio
// ✅ ĐÚNG:
bool isVideo = IsVideoFile(filename); // check by extension
long maxBytes = isVideo ? 500*1024*1024 : 50*1024*1024;
if (file.Length > maxBytes) throw new Exception("File too large");
// ✅ HOẶC: Bỏ check client, để server reject (KISS, không lệch khi admin đổi setting)
2. ❌ Throw exception khi text rỗng
Audio im lặng / nhạc nền không lời / file lỗi → text có thể rỗng. KHÔNG nên throw cứng nhắc, hãy tolerant: chỉ báo lỗi khi cả text lẫn srt_url đều rỗng.
// ❌ SAI:
if (string.IsNullOrWhiteSpace(result.Text)) throw new Exception("Text rỗng!");
// ✅ ĐÚNG:
if (string.IsNullOrWhiteSpace(result.Text) && string.IsNullOrEmpty(result.SrtUrl))
throw new Exception("Audio không có nội dung nhận diện được");
// → Cho phép có SRT mà text rỗng (hoặc ngược lại) — vẫn show user
3. ❌ Không retry khi SRT download fail
Endpoint /audio/{filename} KHÔNG yêu cầu Bearer auth (nhưng gửi cũng không sao). Network glitch có thể fail 1 request.
Nên retry 1-2 lần với delay 2s. KHÔNG fallback về chunks vì v2 response không có field này (chỉ có srt_file + srt_url).
// ✅ Retry 2 lần:
for (int retry = 0; retry < 2; retry++) {
try { srtBytes = await DownloadAsync(srtUrl); break; }
catch (Exception ex) {
Logger.Warn($"SRT download fail (retry {retry+1}/2): {ex.Message}");
if (retry == 1) throw;
await Task.Delay(2000);
}
}
💡 Khuyến nghị thêm cho client production
- HTTP timeout dài: set
30 phútthay vì default (~100s) — đủ cho file audio/video dài upload + transcribe. - Polling interval: 3 giây ổn định cho hầu hết case. Có thể dùng exponential backoff (2s → 4s → 8s) nếu muốn tiết kiệm request.
- Cancel khi tool exit: trap signal đóng tool → POST
/api/v1/job/{id}/cancelđể refund credits, tránh job orphan trên server (server tự cleanup sau 5 phút nhưng tốn credits). - Retry khi network error: poll endpoint thì retry vô hạn (job có thể chạy 5-30 phút), nhưng submit endpoint thì retry tối đa 3 lần với backoff.
- Cache job_id local: nếu tool restart giữa chừng, có thể resume polling thay vì gửi lại file (tiết kiệm credits).
Curl examples — STT v2
# ────── STT v2 cơ bản ────── curl -X POST "https://your-domain/api/v2/stt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "language=vietnamese" # → {"job_id":"abc","status":"queued","engine":"faster-whisper"} # ────── Poll kết quả ────── curl "https://your-domain/api/v1/job/abc" \ -H "Authorization: Bearer YOUR_API_KEY" # → {"status":"completed","text":"...","srt_url":"/audio/stt_xxx.srt"} # ────── STT v2 với custom params (override Admin defaults) ────── # Lưu ý: bool dùng 1/0 để chắc chắn (true/false cũng OK) curl -X POST "https://your-domain/api/v2/stt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "language=vietnamese" \ -F "beam_size=10" \ -F "vad_filter=1" \ -F "temperature=0.0" \ -F "word_timestamps=1" \ -F "initial_prompt=Đây là podcast về AI, công nghệ, có tên Lương, Hùng, Minh" # ────── STT v2 với video ────── curl -X POST "https://your-domain/api/v2/stt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "language=auto" # ────── Workflow đầy đủ (bash polling) ────── JOB=$(curl -s -X POST "https://your-domain/api/v2/stt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" | jq -r .job_id) echo "Job: $JOB" while true; do STATUS=$(curl -s "https://your-domain/api/v1/job/$JOB" -H "Authorization: Bearer YOUR_API_KEY") S=$(echo "$STATUS" | jq -r .status) if [ "$S" = "completed" ] || [ "$S" = "failed" ]; then echo "$STATUS" | jq . break fi echo "Status: $S, polling..." sleep 3 done
Python example — Production-ready (apply best practices)
import os, time, requests
from pathlib import Path
BASE = "https://your-domain"
API_KEY = "YOUR_API_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Best Practice: detect video by extension
VIDEO_EXTS = {".mp4", ".mov", ".mkv", ".webm", ".avi", ".flv", ".wmv", ".m4v"}
def is_video(filename):
return Path(filename).suffix.lower() in VIDEO_EXTS
def stt_v2(audio_path, language="vietnamese", **engine_overrides):
"""STT v2 với best practices đầy đủ."""
fname = os.path.basename(audio_path)
file_size = os.path.getsize(audio_path)
# Best Practice 1: detect video → dùng limit phù hợp
max_mb = 500 if is_video(fname) else 50
if file_size > max_mb * 1024 * 1024:
raise ValueError(f"File quá lớn: {file_size/1024/1024:.1f}MB > {max_mb}MB")
# Submit (timeout 30 phút cho file lớn)
with open(audio_path, "rb") as f:
data = {"language": language}
data.update({k: str(v) for k, v in engine_overrides.items() if v is not None})
resp = requests.post(f"{BASE}/api/v2/stt",
headers=HEADERS, files={"audio_file": f}, data=data,
timeout=1800) # 30 phút
resp.raise_for_status()
job = resp.json()
job_id = job["job_id"]
print(f"Job: {job_id}")
# Poll với retry network error (job có thể chạy lâu)
while True:
try:
r = requests.get(f"{BASE}/api/v1/job/{job_id}",
headers=HEADERS, timeout=30)
r.raise_for_status()
st = r.json()
except requests.RequestException as e:
print(f"Network glitch, retry in 5s: {e}")
time.sleep(5)
continue
if st["status"] == "completed":
text = st.get("text", "")
srt_url = st.get("srt_url", "")
# Best Practice 2: tolerant với text rỗng (chỉ throw khi BOTH rỗng)
if not text and not srt_url:
raise RuntimeError("Audio không có nội dung nhận diện được")
# Download SRT với retry
srt_content = None
if srt_url:
for attempt in range(2):
try:
srt_resp = requests.get(BASE + srt_url, timeout=60)
srt_resp.raise_for_status()
srt_content = srt_resp.text
break
except Exception as e:
if attempt == 1:
print(f"SRT download fail (đã thử 2 lần): {e}")
else:
time.sleep(2)
return {"text": text, "srt": srt_content,
"duration_secs": st.get("duration_secs"),
"language": st.get("language"),
"credits_used": st.get("credits_used"),
"credits_remaining": st.get("credits_remaining")}
if st["status"] in ("failed", "cancelled"):
raise RuntimeError(f"Job {st['status']}: {st.get('error', 'unknown')}")
print(f"Status: {st['status']}, polling...")
time.sleep(3)
# Usage
result = stt_v2("podcast.mp3", language="vietnamese",
beam_size=5, vad_filter=1,
initial_prompt="Đây là podcast về AI")
print("Text:", result["text"][:200])
print("Credits used:", result["credits_used"])
if result["srt"]:
Path("output.srt").write_text(result["srt"], encoding="utf-8")
print("SRT saved to output.srt")
3. SRT → Voice (Timing-synced TTS)
POST /api/v1/srt-to-voice — Tạo voice từ file SRT khớp timing
Upload file .srt + chọn voice đã lưu → hệ thống tạo audio MP3 với giọng TTS khớp với timing từng đoạn sub. Luôn chạy async mode — trả về job_id, poll /api/v1/job/{id} để lấy audio_url khi xong.
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
srt_file | file | ✅ | File SRT (encoding UTF-8 khuyến nghị). Format chuẩn WebVTT/SRT với timestamps HH:MM:SS,mmm |
voice_id | int | ✅ | ID giọng đã lưu (từ GET /api/v1/voices) |
language | string | ❌ | Ngôn ngữ TTS target (default: auto). Ví dụ: vietnamese, english, japanese... |
📥 Response (async): JSON {job_id, cue_count, total_duration, credits_used}. Poll GET /api/v1/job/{job_id} đến khi status=completed → response có audio_url.
// Initial response
{
"job_id": "abc123def456",
"status": "queued",
"credits_used": 15.60,
"cue_count": 42,
"total_duration": 180.5
}
// Poll /api/v1/job/abc123def456 when completed:
{
"job_id": "abc123def456",
"status": "completed",
"audio_url": "/audio/srt2voice_xxx.mp3",
"progress_pct": 100
}
Giới hạn:
• Max SRT file: 500KB
• Max cues/job: 500
• Formats: SRT chuẩn (.srt UTF-8)
Timing behavior: Hybrid mode (default) — không cắt chữ cuối câu. Audio có thể dài hơn SRT gốc ±5% để bảo toàn ý nghĩa.
💡 Use case: video dubbing (transcribe video → dịch SRT sang ngôn ngữ khác → gen voice mới khớp timing), voiceover từ kịch bản, audiobook từ subtitle...
4. Audio Source Separation (Demucs)
POST /api/v1/audio-separate — Tách giọng nói khỏi nhạc nền
Upload file audio HOẶC video → tách thành 2 stems: vocals (giọng nói) và instrumental (nhạc nền). Sử dụng Demucs htdemucs (Meta Research). Luôn chạy async mode — trả về job_id, poll /api/v1/job/{id} để lấy URL khi xong.
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
audio_file | file | ✅ | File audio (mp3, wav, flac, m4a...) hoặc video (mp4, mov, mkv...). Tự động extract audio nếu là video. |
mode | string | ❌ | all (default) — tách cả vocals + instrumental · vocals — chỉ giọng nói · instrumental — chỉ nhạc nền |
📥 Response (async): JSON {job_id, mode, duration_secs, credits_used}. Poll job → completed → response có vocals_url + instrumental_url.
// Initial response
{
"job_id": "abc123def456",
"status": "queued",
"mode": "all",
"duration_secs": 180.5,
"credits_used": 13500.0
}
// Poll /api/v1/job/abc123def456 when completed:
{
"job_id": "abc123def456",
"status": "completed",
"vocals_url": "/audio/sep_xxx_vocals.mp3",
"instrumental_url": "/audio/sep_xxx_instrumental.mp3"
}
Giới hạn:
• Max file: 100MB
• Max duration: 1200s (20 phút)
• Output: MP3 192kbps stereo
💡 Use case: tách vocals cho karaoke, isolate giọng nói để STT chính xác hơn, remix nhạc, podcast cleanup, voice cloning từ video có nhạc nền.
⚠️ Lưu ý: Demucs train trên music data (giọng hát + nhạc cụ). Quality TỐT NHẤT khi audio có cả giọng + nhạc rõ ràng. Cho speech + ambient noise (không phải music), kết quả có thể kém.
Mã lỗi
| HTTP Code | Mô tả |
|---|---|
200 | Thành công |
400 | Lỗi request (thiếu param, file quá lớn, audio quá dài, text quá dài...) |
401 | API key không hợp lệ hoặc thiếu header Authorization |
402 | Không đủ credits |
429 | Quá nhiều request đồng thời (vượt giới hạn concurrent) |
500 | Lỗi server nội bộ |
Ví dụ (curl)
# ────── TTS: Voice Design ────── curl -X POST "https://your-domain/api/v1/tts/design" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "text=Xin chào, đây là giọng nói AI" \ -F "language=vietnamese" \ -F "gender=female" \ -F "age=young adult" \ -F "pitch=moderate pitch" \ --output voice.wav # ────── TTS: Voice Clone ────── curl -X POST "https://your-domain/api/v1/tts/clone" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "text=Nội dung cần đọc" \ -F "language=vietnamese" \ -F "[email protected]" \ --output cloned.wav # ────── TTS: Auto ────── curl -X POST "https://your-domain/api/v1/tts/auto" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "text=Hello world, this is AI voice" \ -F "language=english" \ --output auto.wav # ────── TTS: Saved Voice ────── curl -X POST "https://your-domain/api/v1/tts/saved" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "text=Xin chào, đây là giọng đã lưu" \ -F "voice_id=1" \ -F "language=vietnamese" \ --output saved_voice.wav # ────── TTS: Multi-Voice Dialogue ────── curl -X POST "https://your-domain/api/v1/tts/dialogue" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F 'text=#MC Xin chào các bạn! #Guest Cảm ơn MC, tôi rất vui.' \ -F 'char_voice_map={"MC": 1, "Guest": 2}' \ -F "language=vietnamese" \ --output dialogue.wav # ────── List Saved Voices ────── curl "https://your-domain/api/v1/voices" \ -H "Authorization: Bearer YOUR_API_KEY" # ────── STT: Audio ngắn (≤2 phút) → sync JSON ────── curl -X POST "https://your-domain/api/v1/stt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "language=vietnamese" # → trả kết quả ngay: {"text":"...","duration_secs":45.2,...} # ────── STT: Audio dài (>2 phút) → auto-async ────── curl -X POST "https://your-domain/api/v1/stt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "audio_file=@podcast_20min.mp3" # → trả job_id: {"job_id":"abc123","status":"queued","async":true} # → poll: curl .../api/v1/job/abc123 -H "Authorization: ..." # ────── STT: Force async (bất kể duration) ────── curl -X POST "https://your-domain/api/v1/stt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "async_mode=true" # ────── STT: Video → SRT (sync, file ngắn) ────── curl -X POST "https://your-domain/api/v1/stt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "language=vietnamese" \ -F "output_format=srt" \ -o transcription.srt # ────── STT: Video → VTT (for web players) ────── curl -X POST "https://your-domain/api/v1/stt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "output_format=vtt" \ -o subtitles.vtt # ────── STT: Video → TXT only (plain transcript) ────── curl -X POST "https://your-domain/api/v1/stt" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "output_format=txt" \ -o transcript.txt # ────── SRT → Voice: timing-synced TTS ────── # Step 1: Submit SRT + voice → get job_id curl -X POST "https://your-domain/api/v1/srt-to-voice" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "voice_id=1" \ -F "language=vietnamese" # Response: {"job_id":"abc123...","status":"queued","cue_count":42,"credits_used":15.6} # Step 2: Poll until completed curl "https://your-domain/api/v1/job/abc123def456" \ -H "Authorization: Bearer YOUR_API_KEY" # Response (completed): {"status":"completed","audio_url":"/audio/srt2voice_xxx.mp3","progress_pct":100} # Step 3: Download final MP3 curl -O "https://your-domain/audio/srt2voice_xxx.mp3" \ -H "Authorization: Bearer YOUR_API_KEY" # ────── Audio Separate: tách vocals + instrumental ────── # Step 1: Submit audio/video → get job_id curl -X POST "https://your-domain/api/v1/audio-separate" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "mode=all" # Response: {"job_id":"abc123...","status":"queued","mode":"all","credits_used":13500.0} # Step 2: Poll until completed curl "https://your-domain/api/v1/job/abc123def456" \ -H "Authorization: Bearer YOUR_API_KEY" # Response: {"status":"completed","vocals_url":"/audio/sep_xxx_vocals.mp3","instrumental_url":"/audio/sep_xxx_instrumental.mp3"} # Step 3: Download both stems curl -O "https://your-domain/audio/sep_xxx_vocals.mp3" -H "Authorization: Bearer YOUR_API_KEY" curl -O "https://your-domain/audio/sep_xxx_instrumental.mp3" -H "Authorization: Bearer YOUR_API_KEY" # ────── Audio Separate: chỉ tách vocals (mode=vocals) ────── curl -X POST "https://your-domain/api/v1/audio-separate" \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F "mode=vocals"
Ví dụ (Python)
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://your-domain"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# ── TTS: Design ──
r = requests.post(f"{BASE}/api/v1/tts/design", headers=HEADERS, data={
"text": "Xin chào, đây là giọng nói AI",
"language": "vietnamese", "gender": "female",
"age": "young adult", "pitch": "moderate pitch",
})
with open("voice.wav", "wb") as f: f.write(r.content)
# ── TTS: Clone ──
r = requests.post(f"{BASE}/api/v1/tts/clone", headers=HEADERS,
data={"text": "Nội dung cần đọc", "language": "vietnamese"},
files={"ref_audio": open("sample.wav", "rb")},
)
with open("cloned.wav", "wb") as f: f.write(r.content)
# ── TTS: Multi-Voice Dialogue ──
script = """#MC
Xin chào các bạn, hôm nay chúng ta sẽ nói về AI.
#Guest
Cảm ơn MC, tôi rất vui được ở đây."""
import json
r = requests.post(f"{BASE}/api/v1/tts/dialogue", headers=HEADERS, data={
"text": script,
"char_voice_map": json.dumps({"MC": 1, "Guest": 2}),
"language": "vietnamese",
})
with open("dialogue.wav", "wb") as f: f.write(r.content)
# ── STT: Audio → JSON ──
r = requests.post(f"{BASE}/api/v1/stt", headers=HEADERS,
files={"audio_file": open("recording.wav", "rb")},
data={"language": "vietnamese"},
)
result = r.json()
print(result["text"]) # "Nội dung được nhận diện..."
print(result["duration_secs"]) # 45.2
print(result["credits_used"]) # 1.51
print(result["media_type"]) # "audio"
# ── STT: Video → SRT download ──
r = requests.post(f"{BASE}/api/v1/stt", headers=HEADERS,
files={"audio_file": open("video.mp4", "rb")},
data={"language": "vietnamese", "output_format": "srt"},
)
with open("subtitles.srt", "wb") as f: f.write(r.content)
print("Credits used:", r.headers.get("X-Credits-Used"))
print("Duration:", r.headers.get("X-Duration-Secs"), "s")
print("Media type:", r.headers.get("X-Media-Type")) # "video"
# ── SRT → Voice: timing-synced TTS (async workflow) ──
import time
# Step 1: Submit SRT + voice
r = requests.post(f"{BASE}/api/v1/srt-to-voice", headers=HEADERS,
files={"srt_file": open("subtitles.srt", "rb")},
data={"voice_id": 1, "language": "vietnamese"},
)
job = r.json()
job_id = job["job_id"]
print(f"Job created: {job_id}, {job['cue_count']} cues, {job['credits_used']} credits")
# Step 2: Poll until completed
while True:
r = requests.get(f"{BASE}/api/v1/job/{job_id}", headers=HEADERS)
j = r.json()
print(f" Status: {j['status']}, progress: {j.get('progress_pct', 0)}%")
if j["status"] in ("completed", "failed", "cancelled"):
break
time.sleep(3)
# Step 3: Download MP3
if j["status"] == "completed":
r = requests.get(f"{BASE}{j['audio_url']}", headers=HEADERS)
with open("output.mp3", "wb") as f: f.write(r.content)
print(f"Saved output.mp3 ({len(r.content)} bytes)")
# ── Audio Separate: tách vocals + instrumental ──
r = requests.post(f"{BASE}/api/v1/audio-separate", headers=HEADERS,
files={"audio_file": open("song.mp3", "rb")},
data={"mode": "all"},
)
job = r.json()
print(f"Separate job: {job['job_id']}, {job['credits_used']} credits, {job['duration_secs']}s")
# Poll until completed
while True:
r = requests.get(f"{BASE}/api/v1/job/{job['job_id']}", headers=HEADERS)
j = r.json()
print(f" Status: {j['status']}")
if j["status"] in ("completed", "failed", "cancelled"):
break
time.sleep(5)
# Download both stems
if j["status"] == "completed":
if j.get("vocals_url"):
r = requests.get(f"{BASE}{j['vocals_url']}", headers=HEADERS)
with open("vocals.mp3", "wb") as f: f.write(r.content)
print("Saved vocals.mp3")
if j.get("instrumental_url"):
r = requests.get(f"{BASE}{j['instrumental_url']}", headers=HEADERS)
with open("instrumental.mp3", "wb") as f: f.write(r.content)
print("Saved instrumental.mp3")
🚀 Sẵn sàng tích hợp?
Đăng ký miễn phí để nhận API key và bắt đầu gọi API ngay.
Tạo tài khoản miễn phí →Đã có tài khoản? Đăng nhập