Tài liệu
Tài liệu API
Tạo giọng nói, ảnh và video bằng một lời gọi HTTP. Mọi ví dụ dưới đây chạy thật — bạn chỉ cần thay API key của mình vào là dùng được ngay.
https://api.shopapi.vnNhấn Ctrl + K để tìm nhanh trong tài liệu.Bắt đầu
Bắt đầu nhanh
Ba bước, khoảng hai phút, là bạn có file audio đầu tiên.
- Lấy API key. Bạn đăng ký tài khoản rồi vào Bảng điều khiển → API key để tạo key mới. Key bắt đầu bằng
sk_live_và chỉ hiện đúng một lần — bạn chép ngay và cất vào biến môi trường. - Gọi lời gọi đầu tiên. Chép đoạn code bên cạnh, thay key của bạn vào rồi chạy. Bạn nhận về ngay một
job_idkèm mã202, không phải chờ. - Lấy kết quả. Hỏi trạng thái bằng
GET /v1/jobs/{id}, hoặc nghe luồng realtime để biết tiến độ ngay khi có thay đổi. Khistatuslàsucceeded, link tải nằm ởoutput.url.
Bạn chỉ trả tiền cho phần thật sự dùng
Ví dụ lời gọi đầu tiên
curl -X POST https://api.shopapi.vn/v1/tts \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX" \
-d '{"text":"Xin chào Việt Nam"}'{
"id": "job_x7k2m9p4qr8s",
"object": "job",
"type": "tts",
"status": "queued",
"estimated_cost": "200000000",
"estimated_seconds": 12,
"queue_position": 1,
"created_at": "2026-08-03T10:30:00Z"
}Xác thực
Mọi lời gọi đều mang API key trong header Authorization.
Bạn gửi kèm header Authorization: Bearer sk_live_... trong mỗi yêu cầu. Không có key, hoặc key sai, bạn nhận về lỗi 401 invalid_api_key.
Key chỉ hiện đúng một lần lúc tạo. Chúng tôi không lưu key nguyên văn — trong cơ sở dữ liệu chỉ có bản băm cùng 8 ký tự đầu để bạn nhận ra key nào là key nào. Nếu bạn làm mất key thì không ai xem lại được, kể cả chúng tôi; bạn thu hồi key cũ và tạo key mới.
Key là mật khẩu tài khoản của bạn: đừng đặt vào code chạy trên trình duyệt hay ứng dụng di động, cũng đừng đẩy lên kho code công khai. Hãy để key ở phía máy chủ, đọc từ biến môi trường.
Trình duyệt và code dùng token khác nhau
sk_live_. Hai loại token không dùng lẫn cho nhau được.Ví dụ header xác thực
curl https://api.shopapi.vn/v1/balance \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"{
"error": {
"code": "invalid_api_key",
"message": "Key này sai hoặc đã bị thu hồi. Bạn tạo key mới trong bảng điều khiển rồi thay vào code.",
"type": "authentication_error",
"param": null,
"request_id": "req_4d7f2a9c1b0e"
}
}Đơn vị tiền: micro-VND
Mọi số tiền trong API là micro-VND và luôn được truyền dưới dạng chuỗi.
1₫ = 1.000.000 µVND. Ví dụ "957000000" nghĩa là 957 đồng. Muốn ra số đồng, bạn chia cho 1.000.000.
Sao lại rắc rối vậy? Vì giá một giây audio nhỏ hơn một đồng rất nhiều. Nếu làm tròn đến đồng ở từng job, sai số cộng dồn qua hàng triệu job sẽ thành con số lớn. Dùng đơn vị nhỏ hơn một triệu lần thì mọi phép cộng trừ đều là số nguyên, không bao giờ lệch.
Và vì sao là chuỗi chứ không phải số? Vì JavaScript không biểu diễn chính xác số nguyên lớn: số dư 2.500.000₫ là 2.500.000.000.000 µVND, vượt xa vùng an toàn của kiểu số thông thường. Bạn hãy đọc nó bằng kiểu số nguyên lớn (BigInt, int, decimal) rồi mới tính.
| Giá trị trong JSON | Bạn hiểu là | Ghi chú |
|---|---|---|
"1000000" | 1₫ | Đơn vị nhỏ nhất mà bạn thường gặp: đúng một đồng |
"200000000" | 200₫ | Giá một phút giọng đọc |
"957000000" | 957₫ | Tiền thực trừ của job trong ví dụ ở mục Xem một job |
"2500000000000" | 2.500.000₫ | Số dư ví trong ví dụ ở mục Số dư |
Đừng dùng số thực để tính tiền
parseFloat hay float sẽ làm tròn sai ở những con số lớn. Bạn giữ nguyên chuỗi, đổi sang số nguyên lớn khi cần cộng trừ, và chỉ chia 1.000.000 ở bước cuối cùng khi hiển thị cho người xem.Ví dụ quy đổi micro-VND
{
"cost": "957000000", // 957₫ đã trừ
"refunded": "43000000" // 43₫ trả lại phần thừa
}// Đọc số tiền: chia cho 1.000.000
const vnd = Number(BigInt(job.cost) / 1000000n); // 957
// Gửi số tiền: nhân lên rồi đổi sang chuỗi
const amount = (500000n * 1000000n).toString(); // "500000000000"ID có tiền tố
Nhìn tiền tố là biết ngay ID đó thuộc loại nào — tiện khi đọc log lúc nửa đêm.
Mọi ID đều là chuỗi ngẫu nhiên có tiền tố theo loại đối tượng, ví dụ job_x7k2m9p4qr8s. ID không mang thứ tự và không đoán được, nên bạn cứ lưu nguyên chuỗi, đừng cắt tiền tố ra.
| Tiền tố | Đối tượng | Ví dụ |
|---|---|---|
usr_ | Người dùng | usr_7k2m9p4qr8sd |
job_ | Job — mỗi lần bạn gọi API tạo ra một job | job_x7k2m9p4qr8s |
key_ | API key | key_3n8v2c5xq1wz |
led_ | Bút toán sổ cái — mỗi dòng thay đổi số dư | led_a1b2c3d4e5f6 |
wkr_ | Worker — máy đang chạy job | wkr_voice_vm01 |
acc_ | Account trong kho nội bộ | acc_5t6y7u8i9o0p |
lea_ | Phiếu mượn account | lea_2q3w4e5r6t7y |
txn_ | Giao dịch nạp tiền | txn_p9q8r7s6t5u4 |
Chống gửi trùng (Idempotency)
Gửi lại cùng một yêu cầu bao nhiêu lần cũng chỉ tạo đúng một job — và chỉ bị trừ tiền một lần.
Mạng ở Việt Nam đôi khi rớt giữa chừng: yêu cầu của bạn đã tới nơi nhưng phản hồi không về được. Lúc đó bạn không biết job đã tạo hay chưa. Nếu gửi lại mà không có gì bảo vệ, bạn sẽ tạo hai job và trả tiền hai lần.
Vì vậy mọi endpoint tạo job đều nhận header Idempotency-Key. Bạn tự sinh một chuỗi duy nhất cho mỗi ý định tạo job (khuyến nghị dùng uuid) và gửi kèm. Nếu chúng tôi đã xử lý key đó rồi, lần gọi sau nhận lại đúng job cũ thay vì tạo job mới.
Dùng lại key cũ nhưng đổi nội dung body thì bạn nhận lỗi 409 idempotency_conflict — đây là hàng rào an toàn, tránh việc một key vô tình đại diện cho hai job khác nhau.
Mẹo thực tế
Ví dụ header chống gửi trùng
curl -X POST https://api.shopapi.vn/v1/tts \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX" \
-H "Idempotency-Key: 9d1f2c84-4b7a-4f0e-9a2d-6c5b3e8a1f47" \
-H "Content-Type: application/json" \
-d '{"text":"Xin chào Việt Nam"}'Giới hạn tần suất
Mỗi phản hồi đều kèm ba header cho biết bạn còn bao nhiêu lượt gọi.
Bạn đọc X-RateLimit-Remaining để biết còn bao nhiêu lượt trong cửa sổ hiện tại, và X-RateLimit-Reset (dấu thời gian Unix) để biết khi nào bộ đếm làm mới. Vượt giới hạn thì bạn nhận 429 rate_limit_exceeded kèm header Retry-After — chờ đúng số giây đó rồi gọi lại là được.
Giới hạn tính theo hạng tài khoản. Ba con số quan trọng: số yêu cầu mỗi phút, số job được chạy cùng lúc, và số job mỗi ngày.
| Hạng | Yêu cầu / phút | Job chạy cùng lúc | Job / ngày |
|---|---|---|---|
freeMiễn phí | 10 | 1 | 20 |
starterKhởi động | 60 | 5 | 1.000 |
proChuyên nghiệp | 300 | 20 | 10.000 |
businessDoanh nghiệp | 1.000 | 100 | Không giới hạn |
Gặp 429 thì làm gì
Retry-After rồi giãn dần khoảng cách giữa các lần thử (backoff) thay vì gọi dồn dập. Cần chạy nhiều hơn nữa thì nâng hạng ở trang Bảng giá.Ví dụ header giới hạn tần suất
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1785312060Tham chiếu API
Tạo job
Ba endpoint, cùng một khuôn mẫu. Gửi xong bạn nhận ngay `job_id` và mã 202 — không phải chờ. Tiền được tạm giữ lúc này, quyết toán khi job xong.
Tạo giọng nói
Biến văn bản tiếng Việt thành file audio.
/v1/ttsBạn gửi văn bản, hệ thống trả về ngay một job đang xếp hàng. Tiền được TẠM GIỮ theo ước tính, khi job xong mới trừ đúng theo số giây audio thực tế và trả lại phần thừa. Job hỏng thì bạn không mất đồng nào.
Tham số trong body
| Tham số | Kiểu | Mô tả |
|---|---|---|
textBắt buộc | string1..100.000 ký tự | Nội dung cần đọc. |
voice_idTuỳ chọn | stringMặc định: vi_female_01 | Giọng đọc. Xem danh sách giọng ở mục Giọng nói. |
speedTuỳ chọn | numberMặc định: 1.00.5..2.0 | Tốc độ đọc. 1.0 là bình thường. |
formatTuỳ chọn | stringMặc định: mp3mp3wav | Định dạng file audio trả về. |
webhook_urlTuỳ chọn | string | Đường dẫn để hệ thống báo về khi job xong, thay vì bạn phải hỏi liên tục. |
Ví dụ code cho Tạo giọng nói
curl -X POST https://api.shopapi.vn/v1/tts \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"text": "Xin chào, đây là giọng đọc tiếng Việt từ ShopAPI.",
"voice_id": "vi_female_01",
"speed": 1,
"format": "mp3"
}'{
"id": "job_x7k2m9p4qr8s",
"object": "job",
"type": "tts",
"status": "queued",
"estimated_cost": "200000000",
"estimated_seconds": 12,
"queue_position": 1,
"created_at": "2026-08-03T10:30:00Z"
}Tạo ảnh
Sinh ảnh từ mô tả bằng chữ.
/v1/images/generationsMỗi ảnh được tính tiền riêng, nên `n: 4` sẽ tạm giữ gấp bốn lần. Chỉ những ảnh ra thành công mới bị tính tiền.
Tham số trong body
| Tham số | Kiểu | Mô tả |
|---|---|---|
promptBắt buộc | string | Mô tả ảnh bạn muốn. Tiếng Việt hoặc tiếng Anh đều được. |
nTuỳ chọn | integerMặc định: 11..8 | Số ảnh cần tạo. Mỗi ảnh tính tiền riêng. |
aspect_ratioTuỳ chọn | stringMặc định: 16:916:99:161:14:33:4 | Tỉ lệ khung ảnh. |
seedTuỳ chọn | integer | Cùng seed và cùng prompt sẽ cho ra ảnh gần giống nhau — tiện khi bạn muốn lặp lại kết quả. |
reference_imagesTuỳ chọn | string[]tối đa 3 ảnh | Ảnh tham chiếu về phong cách hoặc bố cục. |
Ví dụ code cho Tạo ảnh
curl -X POST https://api.shopapi.vn/v1/images/generations \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"prompt": "một con mèo phi hành gia, phong cách điện ảnh",
"n": 1,
"aspect_ratio": "16:9"
}'{
"id": "job_b3n8v2c5xq1w",
"object": "job",
"type": "image",
"status": "queued",
"estimated_cost": "100000000",
"estimated_seconds": 25,
"queue_position": 2,
"created_at": "2026-08-03T10:30:00Z"
}Tạo video
Sinh video ngắn từ mô tả, hoặc từ một ảnh có sẵn.
/v1/videos/generationsNên để `engine: "auto"` — hệ thống tự chọn máy rảnh nhất, và khi một engine gặp sự cố thì job vẫn chạy được ở engine kia. Có `image_url` thì job chuyển sang chế độ ảnh-thành-video.
Tham số trong body
| Tham số | Kiểu | Mô tả |
|---|---|---|
promptBắt buộc | string | Mô tả cảnh quay bạn muốn. |
engineTuỳ chọn | stringMặc định: autoautoveo3seedance | Máy xử lý. Để "auto" cho hệ thống tự chọn. |
durationTuỳ chọn | integerMặc định: 8 (veo3/auto) · 10 (seedance) | Độ dài video, tính bằng giây. Veo3 chỉ nhận 8; Seedance chỉ nhận 10. Bỏ trống thì lấy mặc định theo engine. |
aspect_ratioTuỳ chọn | stringMặc định: 16:916:99:161:14:33:4 | Tỉ lệ khung hình. |
image_urlTuỳ chọn | string | Ảnh khởi đầu. Có ảnh thì video sẽ chuyển động từ chính ảnh đó. |
webhook_urlTuỳ chọn | string | Đường dẫn nhận thông báo khi job xong. |
Ví dụ code cho Tạo video
curl -X POST https://api.shopapi.vn/v1/videos/generations \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"prompt": "máy quay lướt qua thành phố lúc hoàng hôn",
"engine": "auto",
"duration": 8,
"aspect_ratio": "16:9"
}'{
"id": "job_k9d4f7g2hj3l",
"object": "job",
"type": "video",
"status": "queued",
"estimated_cost": "500000000",
"estimated_seconds": 180,
"queue_position": 3,
"created_at": "2026-08-03T10:30:00Z"
}Tham chiếu API
Theo dõi job
Hỏi trạng thái, nghe luồng realtime, hoặc huỷ khi cần.
Xem một job
Lấy trạng thái và kết quả của một job.
/v1/jobs/{id}Khi `status` là `succeeded`, trường `output.url` chứa link tải kết quả, có hạn 7 ngày. Bạn nên tải file về lưu ở nơi của mình thay vì dùng trực tiếp link này lâu dài.
Lỗi hay gặp
Ví dụ code cho Xem một job
curl -X GET https://api.shopapi.vn/v1/jobs/<id> \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"{
"id": "job_x7k2m9p4qr8s",
"status": "succeeded",
"progress": 100,
"output": {
"url": "https://cdn.shopapi.vn/o/2026/08/03/x7k2m9.mp3",
"expires_at": "2026-08-10T10:30:00Z",
"size_bytes": 2847293,
"duration_seconds": 287.4,
"format": "mp3"
},
"usage": {
"characters": 4482,
"audio_seconds": 287.4
},
"cost": "957000000",
"refunded": "43000000",
"created_at": "2026-08-03T10:30:00Z",
"completed_at": "2026-08-03T10:31:12Z"
}Liệt kê job
Danh sách job của bạn, có lọc và phân trang.
/v1/jobsTham số trên query string
| Tham số | Kiểu | Mô tả |
|---|---|---|
statusTuỳ chọn | stringqueuedrunningretryingsucceededfailedcancelledrejected | Lọc theo trạng thái. |
typeTuỳ chọn | stringttsimagevideo | Lọc theo loại dịch vụ. |
limitTuỳ chọn | integerMặc định: 201..100 | Số job mỗi trang. |
cursorTuỳ chọn | string | Con trỏ trang tiếp theo, lấy từ `next_cursor` của lần gọi trước. |
Lỗi hay gặp
Ví dụ code cho Liệt kê job
curl -X GET https://api.shopapi.vn/v1/jobs \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"{
"object": "list",
"data": [
{
"id": "job_x7k2m9p4qr8s",
"object": "job",
"type": "tts",
"status": "succeeded",
"progress": 100
}
],
"has_more": true,
"next_cursor": "job_x7k2m9p4qr8s"
}Huỷ job
Dừng một job đang xếp hàng hoặc đang chạy.
/v1/jobs/{id}/cancelToàn bộ tiền tạm giữ được trả lại ví ngay lập tức.
Lỗi hay gặp
Ví dụ code cho Huỷ job
curl -X POST https://api.shopapi.vn/v1/jobs/<id>/cancel \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"{
"id": "job_x7k2m9p4qr8s",
"status": "cancelled",
"cost": "0",
"refunded": "200000000"
}Theo dõi tiến độ realtime
Luồng SSE đẩy tiến độ về ngay khi có thay đổi.
/v1/jobs/{id}/eventsĐây là cách nhẹ nhất để theo dõi job: bạn mở một kết nối, hệ thống tự đẩy cập nhật về, không cần hỏi lại liên tục. Luồng tự đóng khi job kết thúc.
Lỗi hay gặp
Ví dụ code cho Theo dõi tiến độ realtime
curl -X GET https://api.shopapi.vn/v1/jobs/<id>/events \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"event: job.progress
data: {"job_id":"job_x7k2m9p4qr8s","status":"running","progress":45,"stage":"generating","message":"Đang tạo đoạn 3/7","eta_seconds":30}
event: job.succeeded
data: {"job_id":"job_x7k2m9p4qr8s","status":"succeeded","progress":100,"cost":"957000000"}
Tham chiếu API
Ví và thanh toán
Số dư, sao kê, nạp tiền bằng mã QR ngân hàng.
Số dư
Tiền mặt trong ví và các gói còn hạn.
/v1/balanceMọi số tiền là micro-VND dạng chuỗi: `1 đồng = 1.000.000 µVND`. Chia cho 1.000.000 để ra số đồng. Dùng chuỗi vì JavaScript không biểu diễn chính xác số nguyên lớn.
Lỗi hay gặp
Ví dụ code cho Số dư
curl -X GET https://api.shopapi.vn/v1/balance \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"{
"wallet": "2500000000000",
"entitlements": [
{
"type": "voice_seconds",
"remaining": 648000,
"expires_at": "2027-08-03T00:00:00Z"
}
],
"estimated": {
"voice_minutes": 22500,
"images": 25000,
"videos": 5000
}
}Thống kê chi tiêu
Tổng hợp chi tiêu theo ngày hoặc theo loại dịch vụ.
/v1/usageTham số trên query string
| Tham số | Kiểu | Mô tả |
|---|---|---|
fromTuỳ chọn | string | Ngày bắt đầu, dạng YYYY-MM-DD. |
toTuỳ chọn | string | Ngày kết thúc, dạng YYYY-MM-DD. |
group_byTuỳ chọn | stringMặc định: daydaytype | Gom nhóm theo ngày hay theo loại dịch vụ. |
Lỗi hay gặp
Ví dụ code cho Thống kê chi tiêu
curl -X GET https://api.shopapi.vn/v1/usage \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"{
"object": "usage",
"group_by": "day",
"total_spend": "458000000000",
"total_jobs": 1204,
"buckets": [
{
"key": "2026-08-03",
"spend": "32000000000",
"jobs": 87
}
]
}Sao kê sổ cái
Từng bút toán thay đổi số dư, không bao giờ bị sửa hay xoá.
/v1/ledgerSố dư ví của bạn luôn bằng đúng tổng của sổ cái này. Mọi khoản tạm giữ, trừ tiền, hoàn tiền đều là một dòng riêng, nên bạn tự đối soát được đến từng đồng.
Tham số trên query string
| Tham số | Kiểu | Mô tả |
|---|---|---|
limitTuỳ chọn | integerMặc định: 501..100 | Số dòng mỗi trang. |
cursorTuỳ chọn | string | Con trỏ trang tiếp theo. |
Lỗi hay gặp
Ví dụ code cho Sao kê sổ cái
curl -X GET https://api.shopapi.vn/v1/ledger \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"{
"object": "list",
"data": [
{
"id": "led_a1b2c3d4e5f6",
"type": "CAPTURE",
"amount": "-957000000",
"balance_after": "2499043000000",
"description": "Trừ tiền job job_x7k2m9p4qr8s"
}
],
"has_more": true,
"next_cursor": "led_a1b2c3d4e5f6"
}Tạo yêu cầu nạp tiền
Sinh mã QR VietQR và nội dung chuyển khoản.
/v1/topup/intentBạn quét QR bằng app ngân hàng, tiền vào ví tự động trong khoảng 10 giây. Nội dung chuyển khoản phải giữ nguyên để hệ thống nhận ra đúng tài khoản của bạn.
Tham số trong body
| Tham số | Kiểu | Mô tả |
|---|---|---|
amountBắt buộc | string | Số tiền nạp, đơn vị micro-VND dạng chuỗi. |
Lỗi hay gặp
Ví dụ code cho Tạo yêu cầu nạp tiền
curl -X POST https://api.shopapi.vn/v1/topup/intent \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"amount": "500000000000"
}'{
"id": "txn_p9q8r7s6t5u4",
"object": "topup_intent",
"status": "pending",
"amount": "500000000000",
"bonus": "25000000000",
"credited": "525000000000",
"bonus_percent": 5,
"transfer_content": "SHOPAPI usr7k2m9p4",
"qr_image_url": "https://img.vietqr.io/image/MB-0123456789-compact2.png",
"expires_at": "2026-08-03T11:00:00Z"
}Kiểm tra nạp tiền
Hỏi xem tiền đã vào chưa.
/v1/topup/{txn_id}Giao diện web hỏi lại mỗi 3 giây cho tới khi `status` chuyển sang `succeeded`.
Lỗi hay gặp
Ví dụ code cho Kiểm tra nạp tiền
curl -X GET https://api.shopapi.vn/v1/topup/<txn_id> \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"{
"id": "txn_p9q8r7s6t5u4",
"status": "succeeded",
"credited": "525000000000",
"paid_at": "2026-08-03T10:32:41Z"
}Tham chiếu API
Bảng giá
Xem giá và báo giá trước, không cần đăng nhập.
Bảng giá
Giá hiện hành của cả ba dịch vụ. Không cần đăng nhập.
/v1/pricingVí dụ code cho Bảng giá
curl -X GET https://api.shopapi.vn/v1/pricing{
"object": "pricing",
"currency": "VND",
"rules": [
{
"type": "tts",
"unit": "audio_second",
"unit_price": "3333333",
"display_price": "200₫",
"display_unit": "phút"
},
{
"type": "image",
"unit": "image",
"unit_price": "100000000",
"display_price": "100₫",
"display_unit": "ảnh"
},
{
"type": "video",
"unit": "video",
"unit_price": "500000000",
"display_price": "500₫",
"display_unit": "video"
}
]
}Báo giá trước
Biết trước job sẽ tốn bao nhiêu, trước khi bấm chạy.
/v1/pricing/estimateTham số trong body
| Tham số | Kiểu | Mô tả |
|---|---|---|
typeBắt buộc | stringttsimagevideo | Loại dịch vụ. |
text_lengthTuỳ chọn | integer | Số ký tự — chỉ dùng với `type: "tts"`. |
nTuỳ chọn | integer | Số ảnh — chỉ dùng với `type: "image"`. |
durationTuỳ chọn | integer | Độ dài video — chỉ dùng với `type: "video"`. |
Ví dụ code cho Báo giá trước
curl -X POST https://api.shopapi.vn/v1/pricing/estimate \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"type": "tts",
"text_length": 4482
}'{
"object": "pricing.estimate",
"type": "tts",
"estimated_cost": "1200000000",
"likely_cost": "958000000",
"estimated_seconds": 45,
"breakdown": {
"unit": "phút",
"quantity": 6,
"unit_price": "200000000"
}
}Hướng dẫn
Vòng đời job
Job đi một chiều qua bảy trạng thái. Biết job đang ở đâu là biết tiền của bạn đang ở đâu.
- Đang chạyĐang thử lạiĐang chạyTrục trặc tạm thời, hệ thống tự thử lại
- Đang chạyThất bạiHết cách — đã hoàn tiền
- Đang chạyĐã huỷBạn huỷ — đã hoàn tiền
- Đang xếp hàngBị từ chốiNội dung vi phạm — đã hoàn tiền
| Trạng thái | Nghĩa là gì | Tiền của bạn | Đã kết thúc |
|---|---|---|---|
queuedĐang xếp hàng | Job đã nhận, đang chờ máy rảnh | Đang tạm giữ theo ước tính | Chưa, còn chạy tiếp |
runningĐang chạy | Máy đang xử lý job của bạn | Vẫn đang tạm giữ | Chưa, còn chạy tiếp |
retryingĐang thử lại | Gặp trục trặc tạm thời, hệ thống tự thử lại | Vẫn đang tạm giữ | Chưa, còn chạy tiếp |
succeededHoàn thành | Kết quả đã sẵn sàng để tải về | Trừ đúng phần đã dùng, phần thừa hoàn lại ví | Có, không đổi được nữa |
failedThất bại | Đã hoàn lại toàn bộ tiền tạm giữ | Đã hoàn tiền toàn bộ | Có, không đổi được nữa |
cancelledĐã huỷ | Bạn đã huỷ, tiền đã hoàn lại đầy đủ | Đã hoàn tiền toàn bộ | Có, không đổi được nữa |
rejectedBị từ chối | Nội dung vi phạm quy định, tiền đã hoàn lại | Đã hoàn tiền toàn bộ | Có, không đổi được nữa |
Ba trạng thái nào cũng được hoàn tiền đầy đủ
failed, cancelled và rejected luôn kèm trường refunded bằng đúng số đã tạm giữ, và cost bằng "0". Bạn không cần làm gì để được hoàn — tiền về ví ngay khi job chuyển trạng thái.Theo dõi realtime bằng SSE
Mở một kết nối, hệ thống tự đẩy tiến độ về. Nhẹ hơn nhiều so với hỏi lại liên tục.
GET /v1/jobs/{id}/events trả về luồng Server-Sent Events — một kết nối HTTP giữ mở, mỗi khi job có thay đổi thì một dòng dữ liệu được đẩy về. Luồng tự đóng khi job kết thúc, bạn không cần dọn dẹp gì thêm.
Mỗi sự kiện có tên (job.progress, job.succeeded, job.failed) và phần dữ liệu JSON kèm progress theo phần trăm, stage, message tiếng Việt và eta_seconds.
Nếu ứng dụng của bạn chạy ở phía máy chủ và không cần thấy tiến độ từng bước, dùng webhook sẽ đơn giản hơn: bạn không phải giữ kết nối nào cả.
Ví dụ theo dõi realtime
curl -N https://api.shopapi.vn/v1/jobs/job_x7k2m9p4qr8s/events \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXX"event: job.progress
data: {"job_id":"job_x7k2m9p4qr8s","status":"running","progress":45,"stage":"generating","message":"Đang tạo đoạn 3/7","eta_seconds":30}
event: job.succeeded
data: {"job_id":"job_x7k2m9p4qr8s","status":"succeeded","progress":100,"cost":"957000000"}
Webhook
Khai webhook_url lúc tạo job, hệ thống sẽ gọi về máy chủ của bạn khi job xong.
Bạn đưa webhook_url vào body lúc tạo job. Khi có chuyện xảy ra, chúng tôi gửi một yêu cầu POST tới địa chỉ đó, kèm chữ ký để bạn kiểm chứng.
| Sự kiện | Nghĩa là gì | Khi nào bạn nhận được |
|---|---|---|
job.succeeded | Job chạy xong | Kết quả đã sẵn sàng. Đây là sự kiện bạn cần xử lý chính. |
job.failed | Job thất bại | Job hỏng hoặc bị từ chối. Tiền tạm giữ đã hoàn về ví của bạn. |
job.progress | Tiến độ job | Chỉ gửi khi bạn bật trong phần cài đặt webhook. Job dài sẽ bắn nhiều lần. |
Header trong mỗi lần gọi
POST https://ban-cua-ban.vn/webhooks/shopapi
X-ShopAPI-Signature: t=1785312000,v1=8f3c1d0a5b7e2c94a1f6d8b3e0c7a495
X-ShopAPI-Event: job.succeeded
Content-Type: application/jsont là dấu thời gian Unix lúc ký, v1 là mã HMAC-SHA256 của chuỗi "<t>.<body thô>" với khoá bí mật webhook của bạn.
Kiểm tra chữ ký
Bắt buộc kiểm tra chữ ký trước khi tin vào nội dung, vì địa chỉ webhook của bạn là công khai — bất kỳ ai cũng có thể gửi dữ liệu giả tới đó. Ba điểm dễ sai:
- Ký trên body thô, không phải body đã qua
JSON.parserồi đóng gói lại — chỉ cần lệch một dấu cách là chữ ký khác. - So sánh bằng hàm chống đo thời gian (
hmac.compare_digest,timingSafeEqual,hash_equals), đừng dùng dấu bằng thường. - Từ chối những lần gọi có
tquá cũ (ví dụ hơn 5 phút) để chặn việc phát lại yêu cầu cũ.
Lịch thử lại
Nếu máy chủ của bạn không trả về mã 2xx, chúng tôi gọi lại theo lịch dưới đây. Sau 5 lần thất bại thì dừng hẳn, và lần gọi hỏng được ghi vào nhật ký webhook để bạn xem lại trong bảng điều khiển.
- Lần 1ngay lập tức
- Lần 230 giây
- Lần 32 phút
- Lần 410 phút
- Lần 51 giờ
Trả 200 ngay, xử lý sau
200 trong vòng vài giây, và đẩy phần việc nặng (tải file, ghi cơ sở dữ liệu, gửi mail) sang hàng đợi chạy nền. Xử lý lâu quá thì lần gọi bị coi là thất bại và phải chờ thử lại. Ngoài ra, hãy chống trùng theo job.id: một job có thể được báo nhiều lần (thử lại, hoặc bạn bật thêm job.progress), nên xử lý phải cho ra cùng một kết quả dù chạy bao nhiêu lần.Ví dụ kiểm tra chữ ký webhook
# Webhook được ký bằng HMAC-SHA256. Header có dạng:
# X-ShopAPI-Signature: t=1785312000,v1=<chữ ký>
# X-ShopAPI-Event: job.succeeded
#
# Chuỗi được ký là: "<t>.<toàn bộ body dạng thô>"
# Bạn phải so sánh bằng hàm so sánh chống đo thời gian.{
"event": "job.succeeded",
"created_at": "2026-08-03T10:31:12Z",
"data": {
"job": {
"id": "job_x7k2m9p4qr8s",
"status": "succeeded",
"progress": 100,
"output": {
"url": "https://cdn.shopapi.vn/o/2026/08/03/x7k2m9.mp3",
"expires_at": "2026-08-10T10:30:00Z",
"size_bytes": 2847293,
"duration_seconds": 287.4,
"format": "mp3"
},
"usage": {
"characters": 4482,
"audio_seconds": 287.4
},
"cost": "957000000",
"refunded": "43000000",
"created_at": "2026-08-03T10:30:00Z",
"completed_at": "2026-08-03T10:31:12Z"
}
}
}Bảng mã lỗi
Mọi lỗi đều theo cùng một khuôn, và thông điệp luôn nói bạn cần làm gì tiếp theo.
Khi có lỗi, phần thân phản hồi luôn là một object error với các trường code (mã máy đọc), message (câu tiếng Việt cho người đọc), type, param (tham số gây lỗi, nếu xác định được) và request_id.
Bạn hãy rẽ nhánh xử lý theo code, đừng dò chữ trong message — câu chữ có thể được viết lại cho dễ hiểu hơn, còn code thì không đổi. Khi cần hỗ trợ, gửi kèm request_id để chúng tôi tra đúng lần gọi đó.
| HTTP | Mã lỗi | Ý nghĩa | Bạn nên làm gì |
|---|---|---|---|
| 400 | invalid_requestinvalid_request_errorCần sửa rồi gửi lại | Yêu cầu chưa hợp lệCó tham số bị thiếu hoặc sai định dạng nên chúng tôi chưa xử lý được. | Bạn kiểm tra lại các ô đã nhập, hoặc xem mục tương ứng trong tài liệu API. |
| 401 | invalid_api_keyauthentication_errorCần sửa rồi gửi lại | API key không dùng đượcBạn chưa tạo API key, hoặc key đang dùng bị sai, đã thu hồi, hay dán thiếu một phần. | Vào Bảng điều khiển → API key để tạo key (bấm "Tạo key mới"), rồi dán vào code. Key chỉ hiện đúng một lần lúc tạo — chép ngay, mất thì tạo key khác chứ không xem lại được. |
| 402 | insufficient_balancebilling_errorThử lại được | Số dư không đủVí của bạn chưa đủ tiền để chạy yêu cầu này. Chúng tôi chưa trừ đồng nào và job cũng chưa được tạo. | Bạn nạp tiền bằng mã QR ở mục Nạp tiền — tối thiểu 10.000đ, tiền vào ví trong khoảng 10 giây. Giá: 200đ mỗi phút giọng đọc, 100đ mỗi ảnh, 500đ mỗi video; job hỏng hoàn 100% tiền. |
| 403 | content_rejectedpermission_errorCần sửa rồi gửi lại | Nội dung không được phépNội dung bạn gửi vi phạm quy định sử dụng nên chúng tôi phải từ chối. Tiền đã được hoàn lại đầy đủ. | Bạn sửa lại nội dung rồi gửi lại giúp mình. |
| 403 | permission_deniedpermission_errorCần sửa rồi gửi lại | Khoá API không có quyền nàyKhoá bạn đang dùng bị giới hạn phạm vi, giới hạn địa chỉ IP, hoặc đã chạm hạn mức chi tiêu tháng. Trường `reason` cho biết cụ thể là lý do nào. Ví của bạn không bị trừ. | Bạn vào mục API key để nới ràng buộc, hoặc dùng khoá khác. Nếu không phải bạn gọi thì hãy thu hồi khoá này ngay. |
| 403 | account_suspendedpermission_errorCần sửa rồi gửi lại | Tài khoản đang tạm khoáTài khoản của bạn đang bị tạm khoá nên không tạo được yêu cầu mới. | Bạn kiểm tra email chúng tôi đã gửi, hoặc liên hệ hỗ trợ để mở lại. |
| 404 | not_foundinvalid_request_errorCần sửa rồi gửi lại | Không tìm thấyMục bạn đang tìm không tồn tại hoặc không thuộc tài khoản này. | Bạn kiểm tra lại đường dẫn, hoặc quay về danh sách để chọn lại. |
| 409 | conflictinvalid_request_errorCần sửa rồi gửi lại | Trạng thái đã thay đổiThao tác này không còn hợp lệ với trạng thái hiện tại — ví dụ bạn huỷ một job vừa chạy xong. | Bạn tải lại để xem trạng thái mới nhất rồi thao tác lại. |
| 409 | idempotency_conflictidempotency_errorCần sửa rồi gửi lại | Trùng mã chống lặpBạn dùng lại một Idempotency-Key cũ nhưng nội dung gửi lên đã khác. Chúng tôi không xử lý để tránh tạo trùng job. | Bạn đổi sang một Idempotency-Key mới (khuyến nghị dùng uuid) rồi gửi lại. |
| 422 | unsupported_parameterinvalid_request_errorCần sửa rồi gửi lại | Tham số chưa được hỗ trợGiá trị bạn chọn không nằm trong danh sách engine này chấp nhận. | Ví dụ Veo3 chỉ nhận video 8 giây, Seedance chỉ nhận 10 giây. Bạn chọn lại giúp mình. |
| 429 | rate_limit_exceededrate_limit_errorThử lại được | Bạn gửi hơi nhanhSố yêu cầu vượt quá giới hạn của hạng tài khoản hiện tại. | Bạn chờ vài giây rồi thử lại. Cần chạy nhiều hơn thì nâng hạng ở mục Bảng giá. |
| 500 | internal_errorapi_errorThử lại được | Lỗi từ phía chúng tôiCó sự cố bên hệ thống. Nếu đã tạm giữ tiền thì sẽ được hoàn lại tự động. | Bạn thử lại giúp mình. Nếu vẫn lỗi, gửi mã request_id cho hỗ trợ để tra cứu nhanh. |
| 503 | engine_unavailableapi_errorThử lại được | Hệ thống đang quá tảiCụm xử lý tạm thời bận. Bạn không bị trừ tiền — toàn bộ đã được hoàn lại. | Bạn thử lại sau khoảng một phút, hoặc để engine ở chế độ "auto" để hệ thống tự chọn máy rảnh. |
| 503 | service_unavailableapi_errorThử lại được | Dịch vụ tạm gián đoạnMột thành phần hạ tầng đang có sự cố nên chúng tôi tạm ngừng nhận yêu cầu mới. Ví của bạn không bị ảnh hưởng. | Bạn thử lại sau vài phút. Xem tình trạng hệ thống tại status.shopapi.vn để biết khi nào khôi phục. |
Khuôn dạng body lỗi
{
"error": {
"code": "insufficient_balance",
"message": "Số dư không đủ. Bạn cần nạp thêm 1.000đ để chạy job này.",
"type": "billing_error",
"param": null,
"request_id": "req_9f3k2m7pq1x4",
"required": "1000000000",
"available": "250000000"
}
}Lỗi cấp job
Job hỏng sau khi đã nhận thì lỗi nằm trong chính đối tượng job, không phải mã HTTP.
Lời gọi tạo job thành công (mã 202) nhưng job vẫn có thể hỏng lúc chạy. Khi đó status chuyển thành failed và trường error chứa code, message tiếng Việt và retryable. Danh sách mã cấp job rộng hơn mã HTTP vì nó mô tả chuyện xảy ra bên trong máy xử lý.
Mọi lỗi cấp job đều đã hoàn tiền. Với các mã retryable, bạn tạo lại job là chạy tiếp được — hệ thống sẽ chọn máy khác.
| Mã lỗi job | Ý nghĩa | Bạn nên làm gì |
|---|---|---|
engine_errorChạy lại được | Máy xử lý gặp lỗiEngine chạy nhưng không ra kết quả hợp lệ. Toàn bộ tiền tạm giữ đã hoàn về ví. | Bạn bấm "Chạy lại" — hệ thống sẽ tự chọn một máy khác. |
engine_unavailableChạy lại được | Không còn máy rảnhCả cụm engine đang bận hoặc tạm nghỉ. Bạn không bị trừ tiền. | Bạn thử lại sau ít phút, hoặc để engine ở chế độ "auto". |
content_rejectedCần sửa nội dung | Nội dung bị từ chốiNội dung vi phạm quy định nên job bị dừng. Tiền đã hoàn lại đầy đủ. | Bạn sửa lại nội dung rồi tạo job mới. |
timeoutChạy lại được | Quá thời gian chờJob chạy lâu hơn mức cho phép nên bị dừng. Tiền đã hoàn lại đầy đủ. | Với văn bản dài, bạn thử chia thành nhiều job nhỏ hơn. |
download_failedChạy lại được | Không tải được kết quảEngine có ra file nhưng hệ thống không lấy về được. Tiền đã hoàn lại. | Bạn bấm "Chạy lại" giúp mình. |
upload_failedChạy lại được | Không lưu được kết quảFile tạo xong nhưng lưu trữ gặp sự cố. Tiền đã hoàn lại. | Bạn bấm "Chạy lại" giúp mình. |
account_exhaustedChạy lại được | Hết lượt trong ngàyTài nguyên cho loại job này đã dùng hết hạn mức hôm nay. Bạn không bị trừ tiền. | Bạn thử lại sau 00:00, hoặc đổi sang engine khác. |
cancelled_by_userChạy lại được | Bạn đã huỷ job nàyJob dừng theo yêu cầu của bạn. Toàn bộ tiền tạm giữ đã trả về ví. | Bạn có thể tạo lại job bất cứ lúc nào. |
internal_errorChạy lại được | Lỗi hệ thốngSự cố bên phía chúng tôi. Tiền tạm giữ đã hoàn lại tự động. | Bạn thử lại, nếu vẫn lỗi thì gửi mã job cho hỗ trợ. |
Danh mục giọng đọc
Sáu giọng Việt, đủ ba miền. Bạn truyền voice_id vào body khi tạo job giọng nói.
| voice_id | Tên giọng | Giới tính | Vùng miền | Hợp dùng cho |
|---|---|---|---|---|
vi_female_01 | Ngọc Anh | Nữ | Miền Bắc | Nữ miền Bắc, trong trẻo — hợp đọc tin tức, thuyết minh |
vi_female_02 | Thu Hà | Nữ | Miền Bắc | Nữ miền Bắc, trầm ấm — hợp kể chuyện, audiobook |
vi_male_01 | Minh Quân | Nam | Miền Bắc | Nam miền Bắc, chắc khoẻ — hợp quảng cáo, giới thiệu |
vi_female_03 | Mỹ Duyên | Nữ | Miền Nam | Nữ miền Nam, gần gũi — hợp review, TikTok |
vi_male_02 | Hoàng Nam | Nam | Miền Nam | Nam miền Nam, thân thiện — hợp video bán hàng |
vi_female_04 | Diệu Linh | Nữ | Miền Trung | Nữ miền Trung, nhẹ nhàng — hợp nội dung du lịch |
Không truyền voice_id thì hệ thống dùng mặc định vi_female_01. Bạn nghe thử từng giọng ở trang Playground trước khi đưa vào code.