Tài liệu API HVoice
Tích hợp TTS, STT và nhân bản giọng vào sản phẩm của bạn. Base URL API: https://hvoice.app/api/v1
Getting started
- Tạo API key trong Studio → Tích hợp API. Full key chỉ hiện một lần — lưu ngay.
GET /voiceslấyvoice_idvà xemcredit_multiplier.POST /tts/synthesizevớitext+voice_id→ nhậnjob_id.- Poll
GET /tts/jobs/:jobIdđếncompleted→ dùngaudio_url(hoặc nhận webhook).
Authentication
Mọi request TTS/STT/voices gửi:
Authorization: Bearer hv_live_…
•
hv_live_… — API key cho tích hợp (TTS, voices, STT, usage, webhook).• JWT đăng nhập Studio — bắt buộc để
GET/POST/DELETE /account/api-keys (tạo/thu hồi key). Không dùng hv_live_ cho các endpoint quản lý key.
- Tạo key: Studio UI hoặc
POST /account/api-keys(JWT) → response cókey(một lần) +key_prefix. - Liệt kê / thu hồi:
GET/DELETE /account/api-keys/:id(JWT).
curl -X POST https://hvoice.app/api/v1/account/api-keys \
-H "Authorization: Bearer <studio_jwt>" \
-H "Content-Type: application/json" \
-d '{"name":"Production"}'
Limits & billing
- Mỗi
POST /tts/synthesize/ synthesize-srt tối đa 100.000 ký tự. Dài hơn → tách nhiều request. - Rate limit theo gói (mặc định ~30 req/phút; mini ~40 · basic ~60 · standard ~90). Response 429 khi vượt.
- Concurrency: 1 job
processing/ user (+ có thể 1queued). Hàng đợi đầy →QUEUE_FULL. - Trừ ký tự = độ dài text ×
credit_multipliercủa giọng. Job fail → hoàn ký tự đã trừ.
Markup giọng
| Hạng | Hệ số | 1.000 ký tự text |
|---|---|---|
| Standard / free | ×1 | 1.000 credit |
| Clone | ×2.5 | 2.500 credit |
| Luxury | ×2 | 2.000 credit |
| Premium | ×3 | 3.000 credit |
| Platinum | ×4 | 4.000 credit |
Gói dùng thử chỉ dùng một số giọng thư viện. Clone: encode miễn phí; Whisper chỉ trừ nếu không gửi ref_text.
Voices
Danh sách giọng thư viện + clone của bạn. Dùng id làm voice_id. Xem credit_multiplier trước khi trừ.
curl https://hvoice.app/api/v1/voices \ -H "Authorization: Bearer hv_live_…"
{
"voices": [
{
"id": "YOUR_VOICE_ID",
"name": "HN - Ngọc Huyền",
"language": "vi",
"gender": "female",
"preview_url": "/library/audio/….mp3",
"avatar_url": "/…",
"beta_tier": "premium",
"credit_multiplier": 3,
"is_cloned": 0,
"voice_source": "library"
}
],
"provider": "hvoice"
}
| Field | Mô tả |
|---|---|
| id | Truyền vào voice_id khi synthesize. |
| credit_multiplier | Hệ số trừ credit (1–4). |
| beta_tier | standard / luxury / premium / platinum / clone / null. |
| preview_url | URL nghe thử (có thể relative). |
| is_cloned | 1 nếu giọng clone của bạn. |
| voice_source | library hoặc clone. |
Alias: GET /voices/cloned · Preview: GET /voices/:voiceId/preview.
TTS Synthesize
Tạo job TTS bất đồng bộ. Poll job (hoặc webhook) đến completed.
| Field | Bắt buộc | Ghi chú |
|---|---|---|
| text | Có* | Tối đa 100.000 ký tự (*hoặc source_srt). |
| voice_id | Có | Từ GET /voices. |
| format | Không | mp3 (mặc định) hoặc wav. |
| speed | Không | 0.5–2.0 (mặc định 1.0). |
| language | Không | vd. vi, en. |
| title | Không | Tiêu đề job. |
| pitch / pitch_semitones | Không | Cao độ (semitone, khoảng −12…12). |
| volume / volume_pct | Không | Âm lượng %. |
| pause_settings | Không | Ngắt nghỉ theo dấu — xem Pause. |
| source_srt | Không | Chuỗi SRT (thay / kèm text). |
Response tạo job
{
"job_id": "uuid-string",
"status": "processing",
"audio_url": null,
"estimated_time": 60,
"tokens_used": 120,
"credit_multiplier": 3,
"text_len": 40
}
Khi GPU đầy: status: "queued" kèm queue_position, eta_seconds. Credit trừ khi tạo job; hoàn nếu fail.
Pause settings
Giây nghỉ sau mỗi dấu (0–3). Schema đồng bộ Studio.
{
"pause_settings": {
"use_punctuation_pauses": true,
"punctuation_pauses": {
".": 0.45,
",": 0.25,
":": 0.35,
";": 0.3,
"?": 0.45,
"!": 0.45,
"\n": 0.6
}
}
}
Tắt: bỏ field hoặc use_punctuation_pauses: false.
Jobs & parts
| Method | Path | Mô tả |
|---|---|---|
| GET | /tts/jobs | Danh sách (?limit=, max 50) |
| GET | /tts/jobs/:jobId | Poll trạng thái / audio_url |
| POST | /tts/jobs/:jobId/cancel | Hủy queued/processing |
| DELETE | /tts/jobs/:jobId | Xóa job |
| GET | /tts/jobs/:jobId/parts.zip | Các đoạn WAV (zip) |
| GET | /tts/jobs/:jobId/transcript.srt | Tải SRT |
| POST | /tts/jobs/:jobId/parts/:index/retry | Retry một đoạn |
| POST | /tts/jobs/:jobId/parts/retry-failed | Retry mọi đoạn lỗi |
Poll — processing
{
"job_id": "uuid-string",
"status": "processing",
"audio_url": null,
"progress": "Đang tạo · 2/11 đoạn xong · 45%",
"estimated_time": 10
}
Poll — completed
{
"job_id": "uuid-string",
"status": "completed",
"audio_url": "https://hvoice.app/audio/jobs/….mp3",
"duration_seconds": 5.2,
"text_len": 1200,
"tokens_used": 3600
}
status: queued | processing | completed | failed | cancelled. Poll mỗi ~1.5–2s đến trạng thái kết thúc.
SRT → audio
Upload file .srt → một job audio. Tổng ký tự lời cue ≤ 100.000 / request.
curl -X POST https://hvoice.app/api/v1/tts/synthesize-srt \ -H "Authorization: Bearer hv_live_…" \ -F "file=@subtitles.srt" \ -F "voice_id=YOUR_VOICE_ID" \ -F "format=mp3" \ -F "language=vi" \ -F "title=Phụ đề tập 1"
Hoặc JSON POST /tts/synthesize với source_srt.
Voice clone
- Gửi mẫu audio +
ref_text→ nhậnvoice_id. - Đợi vài giây (
enrolling); synthesize lần đầu dùng giọng mới. - TTS với
voice_idclone — trừ theocredit_multiplier(thường ×2.5). - Xóa:
DELETE /voices/:voiceId.
| Field | Bắt buộc | Ghi chú |
|---|---|---|
| audio | Có* | File mẫu (.wav/.mp3…). *Hoặc ref_audio. |
| name | Có | Tên hiển thị. |
| ref_text | Nên có | Lời khớp audio (≥3 ký tự tránh trừ Whisper). |
| language | Không | Mặc định vi. |
| gender | Không | male / female. |
curl -X POST https://hvoice.app/api/v1/voices/clone \ -H "Authorization: Bearer hv_live_…" \ -F "name=Giọng thương hiệu" \ -F "ref_text=Nội dung đọc đúng y như trong file audio mẫu." \ -F "language=vi" \ -F "audio=@/path/to/sample.wav"
Bạn phải có quyền hợp pháp với mẫu giọng — xem Điều khoản · Voice Clone.
Speech to text
curl -X POST https://hvoice.app/api/v1/stt \ -H "Authorization: Bearer hv_live_…" \ -F "audio=@sample.mp3" \ -F "lang=vi"
Audio/video tối đa ~40 MB. STT trừ ký tự theo thời lượng. Field: lang hoặc language. Alias nhận dạng mẫu: POST /voices/transcribe-ref.
Account
curl https://hvoice.app/api/v1/account/usage \ -H "Authorization: Bearer hv_live_…"
Trả số dư ký tự, ledger gần đây, và thông tin gói.
Webhooks
Đăng ký URL HTTPS. Server POST JSON khi job completed hoặc failed.
curl -X PUT https://hvoice.app/api/v1/account/webhook \
-H "Authorization: Bearer hv_live_…" \
-H "Content-Type: application/json" \
-d '{"webhook_url":"https://example.com/hooks/hvoice"}'
GET /account/webhook — xem URL hiện tại. Gửi webhook_url: "" để xoá.
Payload
{
"event": "job.completed",
"job_id": "uuid-string",
"status": "completed",
"audio_url": "https://hvoice.app/audio/jobs/….mp3",
"error": null,
"text_len": 1200,
"tokens_used": 3600
}
- Chỉ chấp nhận
https://. - Timeout phía server ~8 giây. Header:
User-Agent: hvoice-webhook/1,Content-Type: application/json. - Hiện chưa có chữ ký HMAC — xác thực bằng URL bí mật / firewall IP phía bạn.
event:job.completedhoặcjob.failed.
Errors
Body lỗi thường dạng:
{
"error": "Mô tả lỗi",
"code": "QUEUE_FULL",
"needed": 5000,
"balance": 1200
}
| HTTP | code | Ý nghĩa |
|---|---|---|
| 401 | — | Thiếu / sai Bearer; API key không hợp lệ; JWT hết hạn. |
| 401 | — | Cần đăng nhập (JWT) — gọi api-keys bằng hv_live_. |
| 402 | PACK_EXPIRED | Gói hết hạn. |
| 402 | — | Không đủ ký tự / hết credit (needed, balance). |
| 403 | NEED_PHONE | Không bắt buộc (legacy) — trial cấp ngay khi đăng nhập Google. |
| 403 | TRIAL_VOICE_LOCKED | Gói dùng thử không dùng được giọng này. |
| 400 | TRIAL_JOB_TOO_LONG | Job vượt giới hạn trial. |
| 409 | USER_BUSY | Đã có job processing/queued — đợi xong. |
| 409 | QUEUE_FULL | Hàng đợi hệ thống đầy. |
| 409 | — | Tên API key trùng; job không ở trạng thái phù hợp. |
| 429 | — | Vượt rate limit theo gói. |
| 500 | PARTS_INCOMPLETE | Thiếu đoạn sau retry — gọi retry-failed / retry từng đoạn. |
| 503 | — | ASR/Whisper chưa cấu hình; pool GPU trống. |
Code samples
cURL
curl https://hvoice.app/api/v1/voices \
-H "Authorization: Bearer hv_live_…"
curl -X POST https://hvoice.app/api/v1/tts/synthesize \
-H "Authorization: Bearer hv_live_…" \
-H "Content-Type: application/json" \
-d '{
"text": "Xin chào, đây là ví dụ chuyển văn bản thành giọng nói.",
"voice_id": "YOUR_VOICE_ID",
"format": "mp3",
"speed": 1.0,
"language": "vi"
}'
curl https://hvoice.app/api/v1/tts/jobs/JOB_ID \
-H "Authorization: Bearer hv_live_…"
Python — synthesize + poll
import time
import requests
API_KEY = "hv_live_…"
BASE = "https://hvoice.app/api/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def synthesize(text, voice_id, pause_settings=None):
payload = {
"text": text,
"voice_id": voice_id,
"format": "mp3",
"speed": 1.0,
"language": "vi",
}
if pause_settings:
payload["pause_settings"] = pause_settings
r = requests.post(f"{BASE}/tts/synthesize", headers=HEADERS, json=payload, timeout=120)
r.raise_for_status()
return r.json()["job_id"]
def wait_job(job_id, timeout=600):
t0 = time.time()
while time.time() - t0 < timeout:
r = requests.get(f"{BASE}/tts/jobs/{job_id}", headers=HEADERS, timeout=60)
r.raise_for_status()
d = r.json()
if d.get("status") in ("completed", "failed", "cancelled"):
return d
time.sleep(2)
raise TimeoutError(job_id)
job_id = synthesize(
"Xin chào. Đây là ví dụ.",
"YOUR_VOICE_ID",
pause_settings={
"use_punctuation_pauses": True,
"punctuation_pauses": {".": 0.5, ",": 0.2, "\n": 0.6},
},
)
print(wait_job(job_id))
JavaScript — synthesize + poll
const API_KEY = "hv_live_…";
const BASE = "https://hvoice.app/api/v1";
async function synthesize(text, voiceId, pauseSettings) {
const body = {
text,
voice_id: voiceId,
format: "mp3",
speed: 1.0,
language: "vi",
};
if (pauseSettings) body.pause_settings = pauseSettings;
const res = await fetch(`${BASE}/tts/synthesize`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(await res.text());
const { job_id } = await res.json();
for (;;) {
const j = await fetch(`${BASE}/tts/jobs/${job_id}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
}).then((r) => r.json());
if (["completed", "failed", "cancelled"].includes(j.status)) return j;
await new Promise((r) => setTimeout(r, 2000));
}
}
// synthesize("Xin chào, đây là ví dụ.", "YOUR_VOICE_ID")
// .then((j) => console.log("Audio URL", j.audio_url))
// .catch(console.error);