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

Lỗi

Status cho bạn biết mức độ nghiêm trọng. Phần lớn lỗi từ chối còn kèm theo code, và khi có code thì đó là phần duy nhất trong body an toàn để rẽ nhánh — message là câu chữ tự nhiên, một phần bằng tiếng Việt, và tất cả đều có thể được viết lại.

Điều cần biết trước tiên là không phải lỗi nào cũng có code. Phần lớn các cách một lời gọi /v1 có thể bị từ chối là có code; một số ít chỉ trả về status kèm một câu mô tả. Trang này nói rõ cái nào thuộc nhóm nào, vì một client viết theo giả định rằng lỗi nào cũng có code sẽ lặng lẽ rơi xuống nhánh mặc định ở những lỗi không có.

Hai dạng body​

Các endpoint gốc trả về dạng của nền tảng:

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

POST /v1/audio/speech thì trả về dạng của OpenAI, để một client OpenAI đọc được mà không cần lớp chuyển đổi nào:

{ "error": { "message": "…", "type": "rate_limit_exceeded", "param": null, "code": null } }

Khác biệt chỉ có thế: một route, một envelope. Mọi thứ còn lại trên /v1 — kể cả /v1/vapi/speech, vốn phục vụ một bên thứ ba — đều dùng dạng thứ nhất.

Mọi body dạng gốc đều lặp lại status ở statusCode, và thêm code cùng các trường đi kèm khi lỗi từ chối có mang code:

{ "statusCode": 429,
"message": "Server is at capacity. Please try again in a few minutes.",
"code": "QUEUE_FULL", "traceId": "0f7c…" }

Trước đây một body có code thì lại thiếu statusCode — tức là đổi lấy một lý do máy đọc được thì mất đi một trường bạn vẫn luôn đọc được. Khoảng trống đó đã khép lại: giờ mọi body dạng gốc đều có trường này. Dòng status của HTTP vẫn là bản chuẩn — chỗ nào đọc được từ response thì hãy đọc từ đó.

Những lỗi từ chối có kèm code​

Hạn mức và grant​

Sáu route tính tiền theo ký tự hoặc theo thời lượng — POST /v1/tts, /v1/tts/stream, /v1/dialogue, /v1/dub, /v1/srt và /v1/vapi/speech — đều trả lời một lỗi hạn mức kèm code, và hai lỗi tự phục hồi thì nói rõ khi nào:

codeStatusNghĩa là gìCần làm gì
GRANT_EXPIRED403Gói token đứng sau key đã quá hạn.Gia hạn. Thử lại không giúp được gì, nạp thêm cũng vậy.
GRANT_TOKENS_EXHAUSTED403Số dư của grant nhỏ hơn giá của request này. Kèm remaining và required.Nạp thêm. Thử lại không giúp được gì.
GRANT_DAILY_LIMIT429Trần ngày của gói đã dùng hết. Kèm resetAt, remaining, required.Chờ tới resetAt.
GRANT_WEEKLY_LIMIT429Trần tuần của gói đã dùng hết. Kèm resetAt, remaining, required.Chờ tới resetAt.
CONCURRENT_CONFLICT429Hai request của chính bạn tranh nhau cùng một số dư và request này thua.Thử lại ngay — lỗi này tự hết.

resetAt theo chuẩn ISO 8601, và nó thay cho việc đọc mốc thời gian ra từ message. Hãy dùng nó thay vì đoán khoảng chờ, và thay vì mời người dùng nâng gói cho một thứ sẽ tự reset sau một giờ.

Ranh giới 403/429 là bản thô của cùng quyết định đó: 403 nghĩa là thử lại không giúp được gì dù trông có vẻ nhất thời, còn 429 thì rồi sẽ được. Thứ status không nói được là khi nào — lỗi hạn mức không kèm Retry-After (header đó đến từ lớp rate limit, không phải lớp tính tiền). code mới là thứ tách một pha tranh nhau đáng thử lại ngay khỏi một cái trần phải chờ, còn resetAt là thứ nói phải chờ bao lâu.

FREE_DAILY_LIMIT và GRANT_INACTIVE có trong danh sách code nội bộ của nền tảng nhưng không xuất hiện ở đây: một API key luôn tính tiền trên một grant nên không chạm tới phần miễn phí, còn grant không còn hoạt động thì trả 403 kèm câu chữ, không có code.

Năng lực phục vụ​

codeStatusNghĩa là gìCần làm gì
QUEUE_FULL429Hàng đợi job bất đồng bộ đã chạm giới hạn độ sâu.Giãn nhịp rồi gửi lại. Bạn không bị tính tiền — khoản trừ trước được hoàn ngay tại chỗ.
STREAM_CONCURRENCY429Bạn đang giữ số stream mở tối đa cho phép.Đóng bớt một stream, hoặc chờ một stream kết thúc. Không bị tính tiền.
STREAM_BUSY503Mọi node đều khoẻ nhưng năng lực stream đã dùng hết.Kèm theo "fallback": "generate", và đó là một chỉ dẫn thật — đường đi qua hàng đợi có năng lực riêng. Xem Một stream kết thúc thế nào.

QUEUE_FULL chỉ đến với bạn từ POST /v1/tts; các route đồng bộ không xếp hàng đợi.

Nhân bản giọng​

Nhân bản giọng đã chuyển hẳn về Studio web. Các route tạo giọng — POST /v1/voices, DELETE /v1/voices/{voiceId}, POST /v1/clone, POST /v1/upload và POST /v1/prepare — nay chỉ trả một lỗi từ chối, trước khi tính tiền và trước khi chạm tới GPU:

codeStatusNghĩa là gìCần làm gì
CLONE_WEB_ONLY410Giọng nhân bản được tạo tại vieneu.io/#/clone, không qua API. Kèm cloneUrl.Tạo giọng một lần trên Studio. Phần còn lại của hệ tích hợp không đổi: giọng đó hiện trong GET /v1/voices khi bạn gửi kèm key, và id clone_… dùng được làm voiceId trên POST /v1/tts và /v1/tts/stream.

Cố ý dùng 410 chứ không phải 404: các endpoint này từng tồn tại và từng có tài liệu, nên status phải nói rằng chúng đã dời đi, chứ không phải bạn gõ sai đường dẫn. Các code nhân bản cũ (CLONE_QUOTA_EXCEEDED, CLONE_MONTHLY_CAP, CLONE_DAILY_CAP, CLONE_REF_*, CLONE_TRANSCRIPT_DENSITY, CLONE_TOKENS_INSUFFICIENT…) thuộc về các route đó nên không còn tới được một API key nữa — những giới hạn ấy vẫn còn nguyên trên Studio, nơi chúng hiện ra ngay lúc bạn nhân bản.

Việc tạo tiếng bằng một giọng nhân bản không nằm trong nhóm này và không mang code nhân bản nào. Một id clone_… không tồn tại hoặc không phải của bạn sẽ hỏng như một lỗi 400 thường, có message và không có code — xem Giọng nhân bản.

Những lỗi từ chối không có code​

Route tương thích OpenAI​

POST /v1/audio/speech không mang bất kỳ code nào ở trên, dù envelope của OpenAI có sẵn một trường cho việc này: trường code của envelope luôn là null trên route này. Thứ bạn nhận được thay vào đó là type, và nó thô hơn một cách có chủ đích. Lỗi hạn mức được gán type theo status — insufficient_quota cho các 403, tức grant đã cạn hoặc hết hạn, và rate_limit_exceeded cho các 429, tức trần đã dùng hết hoặc một pha tranh nhau. Vậy nên "hết tiền" và "hãy chờ" thì sống sót qua phép chuyển đổi; còn GRANT_EXPIRED với GRANT_TOKENS_EXHAUSTED thì không, và resetAt — thứ cho biết phải chờ bao lâu — cũng không. Mọi lỗi phát sinh trước khi handler chạy — key sai, một trường bị bộ validate từ chối — thì được gán type chỉ theo status, và phép ánh xạ đó gộp 401 với 403 thành một authentication_error duy nhất.

API công khai không bao giờ trả 402​

Không có 402 Payment Required ở bất cứ đâu trên /v1. Mọi đường đi liên quan tới tiền đều chạy qua cùng một bước trừ token, và bước đó chỉ trả lời 403 hoặc 429. Bản spec có kèm một ví dụ 402; còn máy chủ thì không gửi. Một client coi 402 là "hết tiền" sẽ đọc 403 thật thành lỗi phân quyền và ngừng thử lại vì một lý do sai.

Hai phép kiểm văn bản​

Trước khi tiêu bất kỳ token nào, văn bản gửi lên được kiểm hai thứ mà mô hình có đọc thành tiếng cũng không ra nghĩa gì. Cả hai đều trả về 400 trơn, có message và không có code.

Thông báo bắt đầu bằngQuy tắc
Text appears to be in an unsupported language…Hơn 34% số chữ cái nằm ngoài hệ chữ Latin. Dấu tiếng Việt và đ đều là Latin, nên luật này bắt tiếng Trung, Nhật, Hàn, chữ Kirin và tương tự — kể cả một đoạn vốn là tiếng Việt nhưng có một trích dẫn dài không phải chữ Latin.
Text does not look like readable words…Hoặc có một chuỗi hơn 30 chữ cái liền không ngắt, hoặc hơn 60% số token dạng từ (dài từ bốn chữ cái trở lên, và phải có ít nhất ba token như vậy) không chứa nguyên âm nào.

Dấu câu cắt đứt một chuỗi chữ cái, nên một URL, một địa chỉ email hay một đường dẫn tệp được đo theo từng phần và lọt qua; còn gõ bừa bàn phím thì vẫn tính là một chuỗi liền và không lọt.

Hai phép kiểm này chỉ chạy trên ba route: POST /v1/tts, POST /v1/dialogue và POST /v1/srt. POST /v1/tts/stream, POST /v1/audio/speech, POST /v1/vapi/speech và POST /v1/dub không chạy phép kiểm nào — nên đoạn văn bản mà route này từ chối, route kia vẫn tổng hợp và vẫn tính tiền. Đáng biết trước khi bạn coi một 400 từ một route là bằng chứng rằng văn bản đó hỏng ở mọi nơi.

Số stream đồng thời​

POST /v1/tts/stream từ chối mở stream mới khi bạn đang giữ quá nhiều stream:

Tính theoTrầnKhi vượt
Token grant đứng sau API key của bạn4429, code: STREAM_CONCURRENCY
Một người dùng đã đăng nhập trong web app2429, cùng code đó

Suất được giữ trước khi động tới số dư, nên một stream bị từ chối không để lại dấu vết gì trên token của bạn. Hai chi tiết về cách đếm: đếm theo từng tiến trình backend, và khoá đếm là token grant, không phải API key — nên nhiều key phát hành trên cùng một grant sẽ chia nhau bốn suất chứ không phải mỗi key được bốn suất riêng.

X-Request-Id​

Mọi response đều mang X-Request-Id. Đây chính là id mà log của chúng tôi đánh chỉ mục theo: một giá trị phủ cả request tới API, lời gọi worker mà nó tạo ra, và mọi dòng log do cả hai sinh ra.

Phần lớn body lỗi có lặp lại giá trị đó dưới tên traceId, nhưng bản đáng đọc là cái header — chỉ nó mới luôn có mặt. traceId do bộ lọc exception đóng vào, nên một body được ghi thẳng ra response thì không bao giờ có:

  • POST /v1/audio/speech, ở mọi lỗi. Chính handler tự ghi envelope của OpenAI, nên bộ lọc lẽ ra thêm traceId không hề chạy — mà hình dạng của OpenAI cũng chẳng có trường nào cho nó.
  • POST /v1/vapi/speech, ở lỗi 502 khi worker không sinh ra audio nào. Body đó được ghi giữa chừng response và chỉ mang statusCode với message.

Hãy dẫn nó ra khi gửi yêu cầu hỗ trợ. Không có nó, "một request lỗi khoảng 3 giờ chiều" là một cuộc lục tìm; có nó thì chỉ là một lần tra.

Giá trị này luôn là của chúng tôi. Proxy ở biên đặt X-Request-ID cho mọi request nó chuyển tiếp, vô điều kiện, nên id bạn gửi lên sẽ bị ghi đè chứ không được giữ lại — hãy ghi log cái nhận về cạnh correlation id của bạn, đừng trông đợi id của bạn sống sót.

Tổng hợp status​

StatusÝ nghĩa
400Request sai — giọng không tồn tại, format không hợp lệ, văn bản bị bộ kiểm từ chối, một lỗi từ chối khi nhân bản giọng
401API key thiếu, sai 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 hay tính năng này
413File tải lên quá 10 MB
422Nội dung bị kiểm duyệt từ chối (chỉ khi aiRefine đang bật)
429Rate limit, hạn mức token, độ sâu hàng đợi hoặc số stream đồng thời
500Tổng hợp thất bại. Khoản đã trừ được hoàn tự động
502Mọi worker của engine này đều lỗi. Đã hoàn tiền
503Không có worker cho engine hoặc format được yêu cầu, hoặc năng lực stream đã đầy — thử lại sau chốc lát

429 là status duy nhất có những nguyên nhân chẳng liên quan gì tới nhau đứng sau, và header sẽ cho biết là nguyên nhân nào — xem Giới hạn tần suất.