Chuyển tới nội dung chính

Tổng quan Cloud API

VieNeu Cloud API biến văn bản tiếng Việt thành giọng nói qua HTTPS. Bạn gửi text và một voice id, bạn nhận lại audio — không phải tải model, không phải thuê GPU.

Hai sản phẩm khác nhau

Phần này nói về API đám mây tại api.vieneu.io. Phần SDK nói về gói chạy on-device tách biệt, chạy model ngay trên máy bạn. Hai bên dùng chung bộ giọng và cái tên, ngoài ra không chung gì nữa: cài khác nhau, tính phí khác nhau, code khác nhau. Nếu bạn đang tích hợp VieNeu vào một sản phẩm, gần như chắc chắn bạn cần phần này.

Nếu bạn muốn nghe được audio trước khi đọc bất cứ thứ gì ở đây, Bắt đầu nhanh chỉ gồm một key, một giọng và một lời gọi.

Tài liệu tham chiếu đầy đủ​

Mọi endpoint, mọi field, mọi lỗi đều nằm trong API reference, dựng từ đặc tả OpenAPI do chính server sinh ra. Không cần đăng nhập.

Chính tài liệu đó cũng được phục vụ dưới dạng file thuần tại /openapi.json — trỏ openapi-generator, oapi-codegen, Kiota hay công cụ tương đương trong ngôn ngữ của bạn vào đó là bạn có ngay một client đã sinh sẵn kiểu dữ liệu:

curl -O https://docs.vieneu.io/openapi.json

File này sinh ra từ chính định nghĩa route của API, và CI sẽ đánh trượt build nếu bản đã commit không còn khớp với chúng — nên đặc tả không thể âm thầm tụt lại sau phần khai báo trong code.

Đó là một bảo đảm hẹp hơn ta tưởng, và nên nói thẳng ra: phép kiểm so bản JSON đã commit với các decorator, chứ không so với thứ server thực sự làm. Một decorator mô tả sai một endpoint sẽ cho ra một đặc tả sai kèm một phép kiểm màu xanh — và đó đúng là cách endpoint nhân bản giọng vẫn tiếp tục khai báo quyền chọn engine suốt nhiều tháng sau khi server đã bắt đầu từ chối nó. Chỗ nào tài liệu này và đặc tả nói khác nhau về hành vi, các trang ở đây mới là bản được viết dựa trên code đang chạy.

Đặc tả chỉ bao phủ /api/v1 — các endpoint dashboard, thanh toán và quản trị của chính ứng dụng không nằm trong hợp đồng công khai.

Base URL​

https://api.vieneu.io/api/v1

Xác thực​

Mọi request đều mang API key của bạn, đặt kiểu nào cũng được:

-H "Authorization: Bearer vn_sk_..."     # or
-H "X-API-Key: vn_sk_..."

Key bắt đầu bằng vn_sk_ là key thật; key vn_test_ hoạt động y hệt nhưng chặn mỗi request ở 100 từ. Tạo loại nào cũng được tại trang Nhà phát triển trong web app VieNeu — chuỗi key gốc chỉ hiện đúng một lần, lúc tạo, và không bao giờ hiện lại.

Key luôn được cấp dựa trên một token grant đang hoạt động, nên tài khoản mới phải mở một grant trước thì trang mới chịu cấp key; không có grant, thao tác tạo trả về 403 kèm No active API token grant found. Trang Nhà phát triển có bản dùng thử 7 ngày miễn phí để mở grant đó, mỗi tài khoản một lần.

Hai cách tổng hợp giọng nói​

Đồng bộ — bạn nhận thẳng các byte audio trong response:

curl https://api.vieneu.io/api/v1/audio/speech \
-H "Authorization: Bearer $VIENEU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "input": "Xin chào, đây là VieNeu.", "voice": "Ngọc Lan" }' \
--output speech.mp3

Đây là endpoint tương thích OpenAI — đường vào nhanh nhất nếu bạn đã có sẵn client OpenAI, và là lựa chọn dành cho text ngắn.

Bất đồng bộ — với text dài, gửi một job rồi poll nó:

curl -X POST https://api.vieneu.io/api/v1/tts \
-H "Authorization: Bearer $VIENEU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "text": "…", "voiceId": "Ngọc Lan" }'
# → { "jobId": "…", "status": "queued" }
# hoặc, khi đúng đoạn audio đó đã có sẵn (trúng cache):
# { "jobId": "…", "status": "completed", "audioUrl": "https://…" } — không cần poll

curl https://api.vieneu.io/api/v1/tts/<jobId> \
-H "Authorization: Bearer $VIENEU_API_KEY"
# → { "status": "completed", "audioUrl": "https://…" }

Còn khi bạn cần audio bắt đầu phát trước lúc toàn bộ text sinh xong, hãy dùng streaming.

Toàn bộ operation​

Ba mươi hai operation trên hai mươi bảy path — trọn bề mặt công khai. Mỗi cái làm gì, và không nói gì về field của nó: request body chỉ nằm ở API reference mà thôi, vì thêm một bản mô tả API là thêm một thứ phải giữ cho đúng, và cái cũ đi bao giờ cũng không phải bản sinh tự động.

Trang reference cho phép deep-link theo operation id, nên operationId bên dưới cũng chính là cách bạn nhảy thẳng tới một mục — /api-reference#operation/createTtsJob — và là cách bạn gọi tên nó cho một client sinh tự động.

Tổng hợp giọng nói

OperationoperationIdChức năng
POST /v1/audio/speechcreateSpeechTổng hợp tương thích OpenAI, đồng bộ hoặc streaming
POST /v1/ttscreateTtsJobGửi một job bất đồng bộ
GET /v1/tts/{jobId}getTtsJobPoll job để lấy trạng thái và URL tải về
POST /v1/tts/streamstreamSpeechStreaming theo khung, độ trễ thấp
POST /v1/dialoguecreateDialogueHội thoại nhiều người nói trong một lời gọi
POST /v1/dubcreateDubLồng lại giọng cho một bản ghi có sẵn
POST /v1/srtcreateSrtDubLồng tiếng một file phụ đề thành track khớp timecode
POST /v1/vapi/speechvapiSpeechWebhook giọng tuỳ chỉnh của Vapi — PCM thô cho voice agent

Giọng và metadata

OperationoperationIdChức năng
GET /v1/voiceslistVoicesDanh mục giọng, kèm giọng nhân bản của riêng bạn khi có xác thực. Không cần key
GET /v1/audio/voiceslistOpenAiVoicesChỉ id, dành cho client tương thích OpenAI. Cái này có cần key
GET /v1/engineslistEnginesCác engine đang chạy, tính năng và hệ số tính phí. Không cần key
GET /v1/emotion-tagslistEmotionTagsKiểu đọc và thẻ chỉ dẫn đặt ngay trong text, theo từng engine. Không cần key

Số dư và mức dùng

OperationoperationIdChức năng
GET /v1/balancegetBalanceSố token key này tiêu được ngay lúc này (mỗi lời gọi do một gói trả: gói của key, hoặc một gói khác còn hoạt động khi gói đó không trả được), gói đó và hạn mức ngày / tuần. Không trừ token
GET /v1/usagegetUsageKey này (hoặc cả tài khoản) đã làm gì trong một khoảng thời gian: số lời gọi, token, giây audio, theo ngày / route / giọng, lỗi

Nhân bản giọng — chỉ làm trên web

Giọng nhân bản được tạo trên Studio web tại vieneu.io/#/clone, không qua API này. Tạo giọng là việc có người dẫn: Studio khử nhiễu clip, tự chép lời, và cho bạn NGHE THỬ trước khi lưu. Một clip mẫu tồi làm hỏng MỌI lần tạo tiếng sau đó với giọng ấy chứ không chỉ một request, nên API không còn nhận đường tắt này nữa. POST /v1/voices, DELETE /v1/voices/{voiceId}, POST /v1/clone, POST /v1/upload và POST /v1/prepare trả 410 kèm code: "CLONE_WEB_ONLY" và không tính một token nào.

Còn DÙNG giọng nhân bản qua API thì không đổi: giọng hiện trong GET /v1/voices khi bạn gửi kèm key ("kind": "cloned"), và id clone_… của nó là một voiceId bình thường trên POST /v1/tts và POST /v1/tts/stream, giá đúng bằng giọng trong catalogue. Xem Giọng nhân bản.

Webhooks — xem Webhooks để biết hợp đồng sự kiện và một đoạn kiểm chữ ký bạn có thể dán thẳng vào.

OperationoperationIdChức năng
POST /v1/webhookscreateWebhookEndpointĐăng ký một endpoint. Trả về signing secret đúng một lần
GET /v1/webhookslistWebhookEndpointsLiệt kê các endpoint của bạn
GET /v1/webhooks/{endpointId}getWebhookEndpointĐọc một endpoint
DELETE /v1/webhooks/{endpointId}deleteWebhookEndpointXoá một endpoint
GET /v1/webhooks/{endpointId}/deliverieslistWebhookDeliveriesCác lần gửi gần đây, kèm lý do thất bại
POST /v1/webhooks/{endpointId}/rotate-secretrotateWebhookSecretXoay signing secret, cái cũ còn hiệu lực 24 giờ

Sách nói — một giọng dẫn chuyện và tối đa 30 giọng nhân vật đọc các chương viết dưới dạng kịch bản. Sách được tạo ở chế độ nền, mỗi lần một chương, vào lúc cụm GPU rảnh: tính bằng giờ chứ không phải giây. Các cuốn cùng chờ được tạo xen kẽ, mỗi lượt một chương. Mỗi chương bị trừ token theo ký tự khi bắt đầu tạo, được hoàn nếu lỗi, được xử lý âm thanh thành MP3 và lưu vào Thư viện. Các chương không phát sự kiện webhook tts.job.* — hãy theo dõi cả cuốn. Cùng tính năng ngay trong khung chat: Sách nói.

OperationoperationIdChức năng
POST /v1/audiobookscreateAudiobookTạo bản nháp: tên sách, giọng dẫn chuyện, các vai. Chỉ key LIVE
GET /v1/audiobookslistAudiobooksCác sách của bạn, mới nhất trước
GET /v1/audiobooks/{bookId}getAudiobookTiến độ, và link tải (24 giờ) của từng chương đã xong
POST /v1/audiobooks/{bookId}/chaptersaddAudiobookChapterThêm một chương dạng kịch bản, hoặc nối tiếp chương chưa bắt đầu (appendTo)
POST /v1/audiobooks/{bookId}/startstartAudiobookĐưa vào hàng chờ; 402 khi các gói không đủ trả — tổng không đủ, hoặc có chương không gói nào trả được, vì mỗi chương do một gói trả trọn. Cũng dùng để chạy tiếp và tạo lại chương lỗi
POST /v1/audiobooks/{bookId}/cancelcancelAudiobookDừng. Chương chưa bắt đầu không bao giờ bị tính tiền

ID giọng khác nhau theo engine​

Mỗi giọng thuộc về đúng một engine, và từ 2026-09-24 Cloud API chỉ còn đúng một engine: v4, với id là tên hiển thị tiếng Việt — Ngọc Lan, chứ không phải slug. Danh mục v3 đã ngừng dùng slug vieneu-… khó đọc, và hai danh mục gần như không chung id nào, nên một giọng chép từ danh sách v3 cũ giờ không tra ra ở đâu nữa. Nó bị từ chối với 400 chứ không âm thầm ánh xạ sang một giọng v4 — và đó giờ là nguyên nhân phổ biến nhất của các lỗi 400 trên những route tổng hợp giọng nói, với những bên gọi đã hard-code giọng từ trước ngày ngừng, đẩy lên production, rồi chẳng có lý do gì để nhìn lại cái id đó.

Hãy tra id lúc chạy, giới hạn theo đúng engine bạn sắp dùng:

curl -s "https://api.vieneu.io/api/v1/voices?engine=v4"

Không cần key. GET /v1/voices không lọc cũng trả về đúng danh mục v4 đó (cộng thêm các bản nhân bản v4 của chính bạn khi gửi kèm key); ?engine=v3 trả 400 kèm thông báo ngừng engine dẫn ở phần Engine.

Engine​

Request có thể mang thêm engine. Từ 08:00 ngày 2026-09-24 (GMT+7), giá trị duy nhất Cloud API còn nhận là v4, cũng chính là mặc định, nên bạn có thể bỏ hẳn trường này. v3 đã ngừng phục vụ từ sáng hôm đó: bất kỳ request nào nêu nó — trên POST /v1/tts, /v1/tts/stream, /v1/audio/speech, /v1/dialogue, /v1/dub, /v1/srt, hay dưới dạng GET /v1/voices?engine=v3 — đều bị từ chối với 400 trước khi bất cứ khoản nào bị tính, và thông báo nói rõ lý do:

Engine "v3" was retired on 2026-09-24. Use engine "v4" and a voice from GET /v1/voices?engine=v4 — the two catalogues share no ids.

Nếu bạn từng ghim v3, cách sửa gồm ba phần: gửi "engine": "v4" (hoặc không gửi gì), lấy một voice id từ GET /v1/voices?engine=v4 thay vì dùng lại id cũ, và đăng ký lại trên Studio bất kỳ bản nhân bản nào bạn đã tạo trên v3. Audio bạn đã tạo trên v3 không bị ảnh hưởng — các mục trong thư viện và link tải vẫn hoạt động.

Engine tính phí theo hệ số riêng, nhân lên trên đơn giá mỗi ký tự:

EngineHệ số
v43×

Con số này đặt theo từng bản triển khai và có thể thay đổi, nên bảng trên là ảnh chụp một thời điểm, không phải cam kết. GET /v1/engines trả về giá trị đang chạy — không cần API key. Nếu bạn đang dự trù chi phí cho một khối lượng lớn, hãy đọc từ đó thay vì từ trang này:

curl -s https://api.vieneu.io/api/v1/engines
{ "globalMultiplier": 1.3,
"engines": [
{ "key": "v4", "isDefault": true, "sampleRate": 48000, "billingMultiplier": 3,
"features": ["generate", "clone", "dialogue", "stream", "dub", "srt"] }
] }

Hệ số thứ hai​

globalMultiplier trong phản hồi đó là một đòn bẩy toàn nền tảng, nằm trên hệ số của từng engine. Giá đầy đủ của một yêu cầu là:

token = ký tự × hệ số engine × globalMultiplier

Nó thường nằm ở 1 suốt những quãng dài — chính vì thế mà rất dễ bỏ sót, và cũng chính vì thế mà nó được trả về ngay cạnh hệ số engine chứ không nằm ở một trang tài liệu khác. Tại thời điểm viết, nó là 1.3. Dự trù chi phí chỉ theo hệ số engine thì con số của bạn sẽ thiếu đúng bằng nó trên bất kỳ bản triển khai nào đã chỉnh đòn bẩy này.

(Còn một hệ số thứ ba — phụ phí tinh chỉnh văn bản bằng AI — chỉ áp cho những yêu cầu có bật bước đó, không thuộc giá gốc.)

features cũng là cách bạn kiểm tra bằng code xem một engine làm được gì — stream có trong danh sách của v4, nên mọi stream đều chạy ở đó và tính phí theo hệ số v4, giờ cũng là hệ số của mọi lời gọi khác. Mỗi giọng thuộc về đúng một engine — GET /v1/voices?engine=v4 liệt kê danh mục của engine đó. Truyền một giọng thuộc danh mục v3 đã ngừng sẽ bị từ chối chứ không âm thầm thay bằng giọng khác.

Streaming chỉ có trên v4, và từ trước khi ngừng v3 đã vậy rồi, vì một lý do đáng nhớ nếu có lúc bạn đem so các nhà cung cấp: v3 chưa bao giờ thực sự stream — nó dồn hết các chunk ra một lượt ở cuối — nên POST /v1/tts/stream từ chối nó thay vì hứa một mức time-to-first-audio mà nó không giữ nổi. Giờ v3 đã rút khỏi mọi route thì cái chốt đó không còn ý nghĩa: một request stream nêu v3 nhận đúng lỗi 400 ngừng engine như mọi request khác, và POST /v1/audio/speech kèm "model": "vieneu-v3" cũng vậy — trước 2026-09-24 route này vẫn nhận, trả 200, rồi dồn cả cụm chunk ra ở cuối mà không có gì trong phản hồi nói cho bạn biết. Nếu bạn ghim engine cho một client stream, hãy ghim v4 — xem Cắm vào app nói giao thức OpenAI.

Giọng nhân bản cũng stream được, miễn là nó được đăng ký trên v4 — mọi bản nhân bản tạo từ 2026-08-28 đều như vậy, còn bản cũ đăng ký trên v3 thì không còn dựng tiếng được nữa cho tới khi đăng ký lại. Xem Giọng nhân bản.

Tính phí​

Việc tổng hợp giọng nói tính phí theo ký tự gửi lên, nên chi phí của một lời gọi có thể suy ra từ chính request, trước khi bạn gửi đi. Tối thiểu 50 ký tự mỗi request. Request không tạo ra được audio sẽ được hoàn tiền tự động.

aiRefine mặc định tắt trên API. Khi tắt, text của bạn được tổng hợp đúng y như lúc gửi. Bật lên ("aiRefine": true) để dùng lượt xử lý AI mà web app đang dùng — công thức, từ viết tắt và tiếng Anh chen vào đều đọc đúng, và nội dung được kiểm duyệt — đổi lại là một khoản phụ phí và thêm một round-trip nữa vào độ trễ.

Bước chuẩn bị văn bản tất định vẫn chạy trong cả hai trường hợp, nên tiếng Việt vẫn được phát âm đúng dù bật hay tắt.

Còn bao nhiêu token? GET /v1/balance trả remaining: số token một lời gọi tiêu được ngay lúc này. Mỗi lời gọi do một gói trả — gói của key nếu gói đó trả được, nếu không thì một gói khác còn hoạt động của tài khoản — nên đây là chỗ trống nhiều nhất của một gói: token còn lại của gói, hoặc ít hơn nếu hạn mức ngày/tuần thấp hơn. plan là gói mà con số đó đo trên, có tổng, phần còn lại của từng hạn mức kèm giờ làm mới (null khi không có hạn mức đó) và ngày hết hạn. spendableTokens là tổng đang chạy: mọi gói trả được còn tiêu được bao nhiêu, mỗi gói tính tới hạn mức của riêng nó, cộng lại — giảm đúng bằng chi phí mỗi lời gọi dù gói nào trả, và bằng remaining khi chỉ một gói trả hết. totalRemainingTokens cộng mọi gói còn hạn của tài khoản. Một lần tổng hợp cần ít nhất 50 × hệ số engine token.

Endpoint duy nhất không tính phí theo ký tự​

POST /v1/dub tính theo độ dài audio đầu ra — 65 token mỗi giây, sàn 150 token — và chỉ trừ khi thành công. Lồng tiếng chạy nhận dạng giọng nói trước khi tổng hợp, nên khối lượng công việc bám theo bản ghi dài bao nhiêu, chứ không theo việc người nói nhét được bao nhiêu ký tự vào đó. Hệ số engine vẫn được nhân thêm lên trên đó.

Vậy nên dub là lời gọi duy nhất mà bạn không thể tính chi phí từ request body trước khi gửi. Mỗi lần tải lên đều được kiểm tra khả năng chi trả theo chính độ dài của nó trước khi cho qua, nên một request bạn không đủ tiền trả sẽ bị từ chối trước khi tốn bất kỳ giây GPU nào.

Hạn mức và request id​

Có hai bộ giới hạn đứng trước bạn, và chúng đếm hai thứ khác nhau. Một hệ tích hợp chỉ biết một trong hai sẽ hiểu sai một nửa số lỗi 429 của mình.

Bộ giới hạn của ứng dụng đếm theo API key của bạn. Đây là bộ khớp với thứ bạn đã mua: nó tính theo chính cái key, nên rải lời gọi ra nhiều máy cũng không được thêm, và dùng chung mạng văn phòng cũng không mất phần. Hạn mức đặt theo từng route, và mọi route trên /v1 đều tự đặt mức riêng:

RouteMỗi phút
/v1/audio/speech, /v1/tts, /v1/tts/stream, /v1/vapi/speech300
/v1/dialogue20
/v1/dub, /v1/srt10
Quản lý webhook10–30, tuỳ operation
GET /v1/voices, /v1/audio/voices, /v1/engines, /v1/emotion-tags, GET /v1/tts/{jobId}không bị throttle

Khi bộ giới hạn này từ chối bạn, nó nói rõ ra:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 43
Retry-After: 43
X-Request-Id: 0f7c…

X-RateLimit-Reset là lúc cửa sổ đếm lật sang chu kỳ mới; Retry-After là lúc bạn được phép gọi lại. Thường thì hai giá trị trùng nhau, và khi không trùng, hãy theo Retry-After.

Proxy ở biên đếm theo địa chỉ nguồn của bạn. Đứng trước ứng dụng là nginx, thứ chỉ có thể tính theo IP. Trên /api/v1, nó cho phép 3000 request mỗi phút và 300 kết nối đồng thời cho mỗi địa chỉ. Mức đó lỏng có chủ đích — nó là lớp đệm chống DDoS, không phải hạn mức của sản phẩm — nhưng nó có thật, nó dùng chung với mọi người gọi khác đứng sau cùng địa chỉ của bạn, và nó không biết gì về key của bạn. Một khách hàng gọi từ chục cái máy thì ở đó trông như chục khách hàng, còn chục khách hàng đứng sau IP đi ra của một nền tảng tích hợp thì trông như một.

Dấu hiệu nhận biết nằm ở header. Một lỗi 429 có kèm X-RateLimit-* là từ ứng dụng và nói về key của bạn. Một lỗi 429 hoặc 503 trơ trọi, không có những header đó và cũng không có Retry-After, là từ biên và nói về IP của bạn — nginx trả về trang riêng của nó, không kèm header nào của chúng tôi. Khác biệt đó quyết định bạn phải làm gì: trường hợp thứ nhất, giãn nhịp theo key; trường hợp thứ hai, giãn nhịp và xem có bao nhiêu máy đang dùng chung địa chỉ đi ra đó, vì nới thêm số kết nối đồng thời chỉ làm mọi thứ tệ hơn.

Streaming là chỗ điều này quan trọng nhất, vì /v1/tts/stream khớp một khối location nginx khác với phần còn lại của /api/v1 và thừa hưởng một trần kết nối chặt hơn nhiều — xem Hạn mức.

Mọi response, thành công hay thất bại, đều mang X-Request-Id. Hãy dẫn nó ra khi gửi yêu cầu hỗ trợ — log của chúng tôi được đánh chỉ mục theo chính id đó.

Lỗi​

Các endpoint gốc trả về cấu trúc quen thuộc:

{ "statusCode": 403, "message": "Insufficient tokens", "traceId": "0f7c…" }

Riêng /v1/audio/speech trả về cấu trúc của OpenAI, để client OpenAI parse được.

StatusÝ nghĩa
400Request sai định dạng — giọng không tồn tại, format không hợp lệ, hoặc text bị bộ kiểm tra từ chối
401API key thiếu, sai định dạng hoặc đã bị thu hồi
403Hết token, grant đã hết hạn, hoặc gói của bạn không bao gồm engine này
422Nội dung bị bộ kiểm duyệt từ chối (chỉ xảy ra khi bật aiRefine)
429Chạm hạn mức hoặc hết hạn ngạch token — xem phần trên để phân biệt
503Không có worker rảnh cho engine hoặc format được yêu cầu; thử lại sau chốc lát

Lỗi nào có kèm code máy đọc được, lỗi nào không, và với mỗi loại thì phải làm gì — tất cả nằm ở trang Lỗi.