REST API
API cho phép đọc văn bản từ code của bạn. Gói của bạn phải có quyền API (xem bảng giá); không có thì lệnh gọi bằng key trả 403 API_DISABLED.
Tạo API key
- Đăng nhập, vào Hồ sơ › API, bấm tạo key.
- Đặt tên, chọn scope (quyền) — chỉ chọn quyền bạn cần:
| Scope | Cho phép |
|---|---|
tts:generate | Tạo job đọc (POST /api/v1/tts, OpenAI speech, tool MCP tts_speak), huỷ job, tạo lại |
tts:read | Xem trạng thái job và tải audio (GET /api/v1/tts/{id}, /audio), tool MCP tts_get, history_list |
- Tuỳ chọn: giới hạn IP (tối đa 20 địa chỉ/dải CIDR), số ngày hết hạn, mặc định có lưu text/audio hay không.
- Key dạng
sk-saitts-…chỉ hiện một lần — lưu ngay vào nơi an toàn. Mỗi tài khoản có tối đa 10 key; lộ key thì xoá và tạo key mới.
Thiếu scope → 403 SCOPE_MISSING; gọi từ IP ngoài danh sách → 403 IP_NOT_ALLOWED.
Xác thực
Gửi key trong header của mọi request:
Authorization: Bearer sk-saitts-YOUR_KEYTrong các ví dụ dưới đây key được đọc từ biến môi trường SAITTS_API_KEY.
Tìm model và giọng
Hai endpoint công khai (không cần key):
GET http://saitts.supermeo.com/api/v1/models— danh sách model (key, ngôn ngữ, định dạng, giới hạn).GET http://saitts.supermeo.com/api/v1/models/{key}/voices?lang=vi&q=…— giọng của một model; dùng trườngsluglàmvoice.
Đọc văn bản: POST /api/v1/tts
Body JSON:
| Trường | Bắt buộc | Ý nghĩa |
|---|---|---|
model | có | mã model lấy từ GET /api/v1/models, ví dụ tts01 |
voice | có | slug giọng |
text | có | văn bản (tính ký tự sau chuẩn hoá) |
format | không | mp3, wav… (trong các định dạng model hỗ trợ) |
sample_rate | không | 8000–48000 nếu model cho phép |
params | không | tham số riêng của model |
seed | không | số nguyên ≥ 0 để lặp lại kết quả |
wait | không | số giây chờ đồng bộ, 0–60 |
store_text, store_audio | không | ghi đè mặc định của key: lưu text vào lịch sử, giữ audio trong kho tạm |
Kết quả:
- Xong trong
waitgiây →200: headerAcceptlàaudio/*hoặc*/*(mặc định của curl) → nhận thẳng bytes audio;Accept: application/json→ nhận JSON của job. - Chưa xong hoặc
wait: 0→202với{"id", "status", "stream_url"}: hỏi trạng thái rồi tải audio (cách 2). - Job lỗi trong lúc chờ → mã lỗi tương ứng (xem Mã lỗi).
Cách 1 — chờ và nhận audio luôn
# body.json (lưu UTF-8):
# {"model": "tts01", "voice": "minh-triet", "text": "Xin chào, đây là SaiTTS.", "format": "mp3", "wait": 30}
curl -X POST http://saitts.supermeo.com/api/v1/tts \
-H "Authorization: Bearer $SAITTS_API_KEY" \
-H "Content-Type: application/json; charset=utf-8" \
-H "Accept: audio/mpeg" \
-H "Idempotency-Key: hello-0001" \
--data-binary @body.json -o hello.mp3Cách 2 — xếp hàng, hỏi trạng thái, tải về
POST http://saitts.supermeo.com/api/v1/ttsvới"wait": 0→202kèmid.GET http://saitts.supermeo.com/api/v1/tts/{id}mỗi 1–2 giây cho tới khistatuslàsucceeded(hoặcfailed/cancelled). Trườngprogresscho biết tiến độ.GET http://saitts.supermeo.com/api/v1/tts/{id}/audiotải bytes audio; thêm?download=1để có tên file gợi ý. Chưa xong →409 NOT_READY; đã hết hạn trong kho tạm →410 RESULT_EXPIRED.
curl -s -X POST http://saitts.supermeo.com/api/v1/tts \
-H "Authorization: Bearer $SAITTS_API_KEY" \
-H "Content-Type: application/json; charset=utf-8" \
--data-binary '{"model":"tts01","voice":"minh-triet","text":"Hello from SaiTTS","wait":0}'
# → {"id": "JOB_ID", "status": "queued", ...}
curl -s http://saitts.supermeo.com/api/v1/tts/JOB_ID -H "Authorization: Bearer $SAITTS_API_KEY"
curl -s http://saitts.supermeo.com/api/v1/tts/JOB_ID/audio -H "Authorization: Bearer $SAITTS_API_KEY" -o out.mp3Huỷ và tạo lại
DELETE http://saitts.supermeo.com/api/v1/tts/{id}huỷ job đang chờ/đang chạy và hoàn lại ký tự đã giữ chỗ (204). Job đã kết thúc →409 INVALID_STATE.POST http://saitts.supermeo.com/api/v1/tts/{id}/regenerate(body tuỳ chọn{"wait": 30, "format": "wav"}) đọc lại job cũ; miễn ký tự nếu job gốc trong 24 giờ và bạn còn lượt (3 lần/24 giờ).
Gửi lại an toàn với Idempotency-Key
Khi gọi bằng API key, thêm header Idempotency-Key: <chuỗi duy nhất> (mỗi lần tạo một chuỗi) vào POST /api/v1/tts hoặc OpenAI speech. Gửi lại cùng key + cùng body (ví dụ sau khi mất mạng) sẽ trả lại job cũ thay vì tạo và tính ký tự lần nữa — response có header X-SaiTTS-Idempotent-Replay: true. Cùng key mà body khác → 409 IDEMPOTENCY_CONFLICT; lần đầu còn đang chạy → 409 IDEMPOTENCY_IN_PROGRESS.
Tương thích OpenAI
POST http://saitts.supermeo.com/api/openai/v1/audio/speech — dùng thư viện OpenAI có sẵn, chỉ đổi base_url và key. Endpoint luôn trả bytes audio (chờ tối đa 60 giây; quá giờ → 504 kèm header x-saitts-job-id).
model: key model SaiTTS, hoặc tên OpenAItts-1,tts-1-hd,gpt-4o-mini-tts(quản trị viên ánh xạ sang một model SaiTTS).voice: slug giọng SaiTTS. Tên giọng OpenAI (alloy,nova…) chỉ dùng được khi quản trị viên đã ánh xạ; chưa ánh xạ → lỗi giọng không tồn tại.response_format:mp3(mặc định),opus,wav,flac,pcm—aackhông hỗ trợ.speed0.25–4 chỉ có tác dụng với model có tham số tốc độ;instructionsbị bỏ qua.- Mặc định không giữ audio trong kho tạm (gửi
store_audio: truequaextra_bodynếu cần). - Danh sách:
GET http://saitts.supermeo.com/api/openai/v1/modelsvàGET http://saitts.supermeo.com/api/openai/v1/voices?model=…. - Lỗi theo dạng của OpenAI:
{"error": {"message", "type", "code", "param"}}.
curl -X POST http://saitts.supermeo.com/api/openai/v1/audio/speech \
-H "Authorization: Bearer $SAITTS_API_KEY" \
-H "Content-Type: application/json; charset=utf-8" \
--data-binary '{"model":"tts01","voice":"minh-triet","input":"Hello from SaiTTS","response_format":"mp3"}' \
-o speech.mp3Mã lỗi và thử lại
Lỗi của /api/v1 có dạng {"error": {"code": "…", "message": "…", "details": …}} kèm header X-Request-Id (gửi mã này khi báo lỗi).
| HTTP | code | Nên làm |
|---|---|---|
| 401 | UNAUTHORIZED | Key sai, hết hạn hoặc đã xoá |
| 403 | API_DISABLED, SCOPE_MISSING, IP_NOT_ALLOWED, FORBIDDEN_MODEL | Kiểm tra gói, scope của key, IP, model được phép |
| 404 | MODEL_NOT_FOUND, VOICE_NOT_FOUND, NOT_FOUND | Sửa model/giọng/id |
| 409 | NOT_READY, INVALID_STATE, IDEMPOTENCY_CONFLICT | Chờ job xong / đừng gửi lại body khác |
| 410 | RESULT_EXPIRED | Audio đã hết hạn trong kho tạm — tạo lại |
| 422 | INVALID_PARAMS, TEXT_TOO_LONG, VOICE_NOT_AVAILABLE_FOR_MODEL | Sửa request (xem details); text dài thì chia nhỏ |
| 429 | QUOTA_EXCEEDED | Hết ký tự; details.reset_at cho biết lúc reset — đừng thử lại ngay |
| 429 | RATE_LIMITED | Gọi quá nhanh; chờ theo header Retry-After |
| 429 | CONCURRENCY_LIMIT | Quá số job chạy cùng lúc của gói; chờ job trước xong |
| 503 | NO_WORKER, WORKER_LOST | Tạm thời; thử lại sau vài giây |
| 504 | TIMEOUT | Hết giờ chờ; hỏi lại trạng thái job thay vì tạo job mới |
Thử lại: chỉ thử lại với 429 RATE_LIMITED, 503, 504 và lỗi mạng — chờ tăng dần (1 s, 2 s, 4 s…) và luôn gửi Idempotency-Key để không bị tính ký tự hai lần.
Giới hạn tốc độ
- Ngoài hạn mức ký tự, mỗi gói có giới hạn số job chạy cùng lúc và số ký tự mỗi lần đọc — xem bảng giá.
- Giới hạn chống lạm dụng:
POST /api/v1/ttskhoảng 60 request/phút;/api/openai/v1và/api/mcpkhoảng 150 request/phút. - Lịch sử (
/api/v1/history) và thống kê dùng (/api/v1/me/usage) chỉ dành cho trang web đã đăng nhập; với key, dùng tool MCPhistory_listvàaccount_usage.
REST API
The API reads text aloud from your own code. Your plan must include API access (see pricing); without it, calls made with a key return 403 API_DISABLED.
Create an API key
- Sign in, go to Profile › API and create a key.
- Name it and choose its scopes (permissions) — only the ones you need:
| Scope | Allows |
|---|---|
tts:generate | Creating jobs (POST /api/v1/tts, OpenAI speech, MCP tool tts_speak), cancelling, regenerating |
tts:read | Reading job status and downloading audio (GET /api/v1/tts/{id}, /audio), MCP tools tts_get, history_list |
- Optional: an IP allowlist (up to 20 addresses/CIDR ranges), an expiry in days, and whether text/audio are stored by default.
- The key (
sk-saitts-…) is shown only once — store it somewhere safe right away. An account can have up to 10 keys; if a key leaks, delete it and create a new one.
Missing scope → 403 SCOPE_MISSING; a call from an IP outside the allowlist → 403 IP_NOT_ALLOWED.
Authentication
Send the key in a header on every request:
Authorization: Bearer sk-saitts-YOUR_KEYThe examples below read the key from the SAITTS_API_KEY environment variable.
Find models and voices
Two public endpoints (no key needed):
GET http://saitts.supermeo.com/api/v1/models— the models (key, languages, formats, limits).GET http://saitts.supermeo.com/api/v1/models/{key}/voices?lang=vi&q=…— the voices of a model; use theslugfield asvoice.
Speak text: POST /api/v1/tts
JSON body:
| Field | Required | Meaning |
|---|---|---|
model | yes | model code from GET /api/v1/models, for example tts01 |
voice | yes | voice slug |
text | yes | the text (characters counted after normalisation) |
format | no | mp3, wav… (one the model supports) |
sample_rate | no | 8000–48000 when the model allows it |
params | no | model-specific parameters |
seed | no | integer ≥ 0 to repeat a result |
wait | no | seconds to wait synchronously, 0–60 |
store_text, store_audio | no | override the key defaults: keep the text in history, keep the audio in the temporary store |
Responses:
- Done within
waitseconds →200: with anAcceptofaudio/*or*/*(the curl default) you get the audio bytes; withAccept: application/jsonyou get the job as JSON. - Not done yet, or
wait: 0→202with{"id", "status", "stream_url"}: poll the status, then download (option 2). - A job that fails while you wait → the matching error code (see Errors).
Option 1 — wait and get the audio
# body.json (saved as UTF-8):
# {"model": "tts01", "voice": "minh-triet", "text": "Hello from SaiTTS.", "format": "mp3", "wait": 30}
curl -X POST http://saitts.supermeo.com/api/v1/tts \
-H "Authorization: Bearer $SAITTS_API_KEY" \
-H "Content-Type: application/json; charset=utf-8" \
-H "Accept: audio/mpeg" \
-H "Idempotency-Key: hello-0001" \
--data-binary @body.json -o hello.mp3Option 2 — queue, poll, download
POST http://saitts.supermeo.com/api/v1/ttswith"wait": 0→202with anid.GET http://saitts.supermeo.com/api/v1/tts/{id}every 1–2 seconds untilstatusissucceeded(orfailed/cancelled).progressshows how far along it is.GET http://saitts.supermeo.com/api/v1/tts/{id}/audioreturns the audio bytes; add?download=1for a suggested file name. Not done →409 NOT_READY; expired from the temporary store →410 RESULT_EXPIRED.
curl -s -X POST http://saitts.supermeo.com/api/v1/tts \
-H "Authorization: Bearer $SAITTS_API_KEY" \
-H "Content-Type: application/json; charset=utf-8" \
--data-binary '{"model":"tts01","voice":"minh-triet","text":"Hello from SaiTTS","wait":0}'
# → {"id": "JOB_ID", "status": "queued", ...}
curl -s http://saitts.supermeo.com/api/v1/tts/JOB_ID -H "Authorization: Bearer $SAITTS_API_KEY"
curl -s http://saitts.supermeo.com/api/v1/tts/JOB_ID/audio -H "Authorization: Bearer $SAITTS_API_KEY" -o out.mp3Cancel and regenerate
DELETE http://saitts.supermeo.com/api/v1/tts/{id}cancels a queued or running job and refunds its reserved characters (204). A finished job →409 INVALID_STATE.POST http://saitts.supermeo.com/api/v1/tts/{id}/regenerate(optional body{"wait": 30, "format": "wav"}) reads an earlier job again; free of characters when the original is less than 24 hours old and you have free regenerations left (3 per 24 hours).
Safe retries with Idempotency-Key
With an API key, add an Idempotency-Key: <unique string> header (one string per creation) to POST /api/v1/tts or OpenAI speech. Sending the same key and body again (say, after a network error) returns the original job instead of creating and charging a new one — the response carries X-SaiTTS-Idempotent-Replay: true. Same key with a different body → 409 IDEMPOTENCY_CONFLICT; first request still running → 409 IDEMPOTENCY_IN_PROGRESS.
OpenAI-compatible endpoint
POST http://saitts.supermeo.com/api/openai/v1/audio/speech — use the official OpenAI libraries, changing only base_url and the key. It always answers with audio bytes (waits up to 60 seconds; after that → 504 with an x-saitts-job-id header).
model: a SaiTTS model key, or the OpenAI namestts-1,tts-1-hd,gpt-4o-mini-tts(the administrator maps them to a SaiTTS model).voice: a SaiTTS voice slug. OpenAI voice names (alloy,nova…) work only once the administrator has mapped them; otherwise you get a voice-not-found error.response_format:mp3(default),opus,wav,flac,pcm—aacis not supported.speed0.25–4 only applies to models with a speed parameter;instructionsis ignored.- By default the audio is not kept in the temporary store (send
store_audio: truethroughextra_bodyif you need it). - Listings:
GET http://saitts.supermeo.com/api/openai/v1/modelsandGET http://saitts.supermeo.com/api/openai/v1/voices?model=…. - Errors follow the OpenAI shape:
{"error": {"message", "type", "code", "param"}}.
curl -X POST http://saitts.supermeo.com/api/openai/v1/audio/speech \
-H "Authorization: Bearer $SAITTS_API_KEY" \
-H "Content-Type: application/json; charset=utf-8" \
--data-binary '{"model":"tts01","voice":"minh-triet","input":"Hello from SaiTTS","response_format":"mp3"}' \
-o speech.mp3Error codes and retries
Errors from /api/v1 look like {"error": {"code": "…", "message": "…", "details": …}} with an X-Request-Id header (include it when you report a problem).
| HTTP | code | What to do |
|---|---|---|
| 401 | UNAUTHORIZED | Wrong, expired or deleted key |
| 403 | API_DISABLED, SCOPE_MISSING, IP_NOT_ALLOWED, FORBIDDEN_MODEL | Check the plan, the key scopes, the IP, the allowed models |
| 404 | MODEL_NOT_FOUND, VOICE_NOT_FOUND, NOT_FOUND | Fix the model/voice/id |
| 409 | NOT_READY, INVALID_STATE, IDEMPOTENCY_CONFLICT | Wait for the job / do not resend a different body |
| 410 | RESULT_EXPIRED | The audio expired from the temporary store — regenerate |
| 422 | INVALID_PARAMS, TEXT_TOO_LONG, VOICE_NOT_AVAILABLE_FOR_MODEL | Fix the request (see details); split long text |
| 429 | QUOTA_EXCEEDED | Out of characters; details.reset_at says when it resets — do not retry right away |
| 429 | RATE_LIMITED | Too many calls; wait as the Retry-After header says |
| 429 | CONCURRENCY_LIMIT | Your plan's concurrent-job limit; wait for running jobs |
| 503 | NO_WORKER, WORKER_LOST | Temporary; retry after a few seconds |
| 504 | TIMEOUT | Timed out; poll the job instead of creating a new one |
Retries: retry only on 429 RATE_LIMITED, 503, 504 and network errors — back off (1 s, 2 s, 4 s…) and always send an Idempotency-Key so characters are never charged twice.
Rate limits
- Besides the character quota, each plan limits concurrent jobs and characters per request — see pricing.
- Abuse limits:
POST /api/v1/ttsabout 60 requests/minute;/api/openai/v1and/api/mcpabout 150 requests/minute. - History (
/api/v1/history) and usage (/api/v1/me/usage) are for the signed-in website only; with a key, use the MCP toolshistory_listandaccount_usage.