Text-to-Speech
Turn text into spoken audio using the OpenAI Audio API format. One synchronous call: send the text, receive the whole MP3 back. Uses the same API key as the chat endpoints.
Base URL: https://api.techopenclaw.com — authentication uses the same Authorization: Bearer toc-..... header as the chat endpoints (details in API Reference).
Synthesize speech. The response body is the raw audio file (Content-Type: audio/mpeg), not JSON — save it straight to disk.
| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | TTS model name from the LLM Catalog. Replace tts-avevoice-1.5-max in the examples with a speech model available to your account. |
input | string | Yes | The text to speak, 1–4096 characters. Characters are also the billing unit — longer input costs more, nothing else does. |
voice | string / object | Yes | The voice to use — what counts as a valid value depends on the model family (see Models & voices below): tts-avevoice* models take a voice name (e.g. Aanya), tts-eleven* models take a voice id (e.g. elevenlabs_EXAVITQu4vr4xnSDxMaL). A custom voice can also be passed as { "id": "voice_..." } — including your own cloned voice id. |
response_format | string | No | mp3 (the default). Other formats are not supported — the provider rejects them with 400. Streaming is not supported either — the audio comes back whole, never as SSE. |
speed | number | No | Playback speed, 0.25–4.0 (default 1.0). Forwarded to the engine; an explicit settings.speed wins over it. |
language | string | Depends | Language for the synthesis engine (e.g. vi, en). An extension beyond the OpenAI format. Required by tts-avevoice and tts-avevoice-fast; optional for every other speech model. If a language is unavailable for a model, the error message names it. |
settings | object | No | Engine-specific settings, forwarded to the engine as-is. Use it when the engine has knobs the OpenAI format does not carry. |
curl https://api.techopenclaw.com/v1/audio/speech \
-H "Authorization: Bearer toc-....." \
-H "Content-Type: application/json" \
-d '{
"model": "tts-avevoice-1.5-max",
"input": "Chào mừng bạn đến với Techopenclaw.",
"voice": "Aanya"
}' \
--output speech.mp3curl https://api.techopenclaw.com/v1/audio/speech \
-H "Authorization: Bearer toc-....." \
-H "Content-Type: application/json" \
-d '{
"model": "tts-avevoice",
"input": "Xin chào, đây là giọng đọc tiếng Việt.",
"voice": "Aanya",
"language": "vi"
}' \
--output speech.mp3curl https://api.techopenclaw.com/v1/audio/speech \
-H "Authorization: Bearer toc-....." \
-H "Content-Type: application/json" \
-d '{
"model": "tts-eleven-v4",
"input": "Chào mừng bạn đến với Techopenclaw.",
"voice": "elevenlabs_Jxl0M2KOcLjO5ReRA5rH"
}' \
--output speech.mp3Works with the OpenAI SDK by pointing base_url at Techopenclaw:
from openai import OpenAI
client = OpenAI(api_key="toc-.....", base_url="https://api.techopenclaw.com/v1")
with client.audio.speech.with_streaming_response.create(
model="tts-avevoice-1.5-max",
voice="Aanya",
input="Chào mừng bạn đến với Techopenclaw.",
) as response:
response.stream_to_file("speech.mp3")Models & voices
What a valid voice looks like — and whether language is required — depends on the model family:
| Models | voice value | language |
|---|---|---|
tts-avevoice, tts-avevoice-fast | Voice name — e.g. Aanya | Required (e.g. "vi", "en") |
tts-avevoice-1.5-max, tts-avevoice-1.5-mini | Voice name — e.g. Aanya | Optional |
All tts-eleven-* models | Voice id — e.g. elevenlabs_EXAVITQu4vr4xnSDxMaL | Optional |
Voice examples per family (the catalogues live on the provider side and can change). To speak with your own voice instead, clone it from a sample:
| voice | Language |
|---|---|
Aanya | Hindi (hi-IN) |
Aarav | Hindi (hi-IN) |
Abby | English US |
Aditya | English IN |
Alistair | English GB |
Alain | French (fr-FR) |
Alvaro | Spanish (es-ES) |
Camila | Spanish (es-MX) |
Annika | German (de-DE) |
Alyona | Russian (ru-RU) |
Asuka | Japanese (ja-JP) |
Beatriz | Portuguese BR |
Andoy | English PH |
Amara | English NG |
| voice | Voice |
|---|---|
elevenlabs_Jxl0M2KOcLjO5ReRA5rH | Dat — Northern Vietnamese |
elevenlabs_njal9ZYZB2BSvEp2g2Fe | Cuong Nguyen — Calm Narrator (Vietnamese) |
elevenlabs_EXAVITQu4vr4xnSDxMaL | Sarah — Mature, Reassuring |
elevenlabs_pNInz6obpgDQGcFmaJgB | Adam — Deep |
elevenlabs_cgSgspJ2msm6clMCkdW9 | Eric — Smooth, Trustworthy |
elevenlabs_onwK4e9ZLuTAKqWW03F9 | Daniel — Steady Broadcaster |
elevenlabs_JBFqnCBsd6RMkjVDRZzb | George — Storyteller |
elevenlabs_IKne3meq5aSn9XLyUdCD | Charlie — Deep, Confident |
elevenlabs_pFZP5JQG7iQjIQuC4Bku | Lily — Velvety Actress |
elevenlabs_SAz9YHcvj6GT2YYXdXww | River — Relaxed, Neutral |
elevenlabs_Xb7hH8MSUJpSbSDYk0k2 | Alice — Educator |
elevenlabs_N2lVS1w4EtoT3dr4eOWO | Callum — Husky Trickster |
Voice cloning
Upload a short, clean sample of a voice and get back a voice id you can speak with anywhere a voice name is accepted. A clone belongs to the API key that created it — other keys never see it. Available on the tts-avevoice* models; tts-eleven-* models cannot clone.
Clone a voice from an audio sample. multipart/form-data only — JSON is rejected. Returns the new voice with its id.
| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | A speech model that supports cloning: tts-avevoice, tts-avevoice-fast, tts-avevoice-1.5-max, tts-avevoice-1.5-mini. Cloned voices made on one model work across these four. tts-eleven-* models cannot clone (402). |
name | string | Yes | A label for the cloned voice, up to 64 characters. Shown when you list your clones. |
language | string | Yes | The language of the sample and the voice (e.g. vi-VN, en). Always required for cloning — a request without it is rejected with 400. |
file | file | Yes | The audio sample: mp3 or wav, at most 4 MB, roughly 10–30 seconds of one voice speaking clearly with no background music. |
curl https://api.techopenclaw.com/v1/audio/voices \ -H "Authorization: Bearer toc-....." \ -F "model=tts-avevoice" \ -F "name=my-voice" \ -F "language=vi-VN" \ -F "[email protected];type=audio/mpeg"
{
"id": "6214a0dd-076e-4508-bfe3-f2328f5bb653",
"object": "voice",
"name": "my-voice",
"status": "processing",
"model": "tts-avevoice",
"owned": true,
"created_at": 1791253089
}import requests
resp = requests.post(
"https://api.techopenclaw.com/v1/audio/voices",
headers={"Authorization": "Bearer toc-....."},
data={"model": "tts-avevoice", "name": "my-voice", "language": "vi-VN"},
files={"file": ("sample.mp3", open("sample.mp3", "rb"), "audio/mpeg")},
)
voice_id = resp.json()["id"]List voices. With ?type=clone you get only the clones your key owns; without it, the system voice catalogue of the provider.
curl "https://api.techopenclaw.com/v1/audio/voices?type=clone" \ -H "Authorization: Bearer toc-....."
{
"object": "list",
"data": [
{
"id": "6214a0dd-076e-4508-bfe3-f2328f5bb653",
"object": "voice",
"name": "my-voice",
"type": "clone",
"models": ["tts-avevoice"],
"owned": true,
"status": "processing"
}
],
"page": 1,
"page_size": 1,
"has_more": false,
"complete": true
}Delete one of your clones. Deleting a voice that belongs to another key returns 404.
curl -X DELETE "https://api.techopenclaw.com/v1/audio/voices/6214a0dd-076e-4508-bfe3-f2328f5bb653" \ -H "Authorization: Bearer toc-....."
{
"id": "6214a0dd-076e-4508-bfe3-f2328f5bb653",
"object": "voice",
"deleted": true
}Speak with the clone by passing its id as the voice object:
curl https://api.techopenclaw.com/v1/audio/speech \
-H "Authorization: Bearer toc-....." \
-H "Content-Type: application/json" \
-d '{
"model": "tts-avevoice",
"input": "Đây là giọng nói đã được nhân bản từ mẫu âm gốc.",
"voice": { "id": "6214a0dd-076e-4508-bfe3-f2328f5bb653" },
"language": "vi"
}' \
--output speech.mp3| Rule | Detail |
|---|---|
| Content type | multipart/form-data — a JSON body is rejected with 400. Send it like a file upload form, not a JSON payload. |
| Ownership | A clone belongs to the API key that created it. Other keys cannot list, speak with, or delete it — they get 404 as if the voice did not exist. |
| Ready state | A fresh clone returns status: "processing". It becomes usable once the engine finishes — usually within a minute; speaking with it before then either succeeds or fails un-billed, safe to retry. |
| Limits | Sample up to 4 MB; name up to 64 characters; input text when speaking: 1–4096 characters as usual. |
Voice-clone billing
| Action | Billing |
|---|---|
| Create a clone (201) | Charged once: the model's flat credits-per-clone rate (see Pricing Policy). A failed create is not charged. |
| Speak with a cloned voice | Same per-character rate as any other speech on that model — no extra fee for using a clone. |
| List clones / delete a clone | Free. |
Voice-clone error codes
| Code | Meaning | Action |
|---|---|---|
400 | Not multipart, a missing model/name/language/file, a name over 64 characters, or a sample over 4 MB | Read error.message — it names the offending field. language is required for cloning even on models where it is optional for speech |
401 | Invalid or missing API key | Check your toc- key |
402 | Not enough credits for the clone price, or the model does not support cloning | Top up, or switch to one of the tts-avevoice* models |
403 | Money-wallet keys cannot clone voices | Use a credit key |
404 | Unknown model, model is not a speech model, or the voice id belongs to another key / does not exist | Verify the model in the LLM Catalog; only list and use clones created by your own key |
529 | The cloning engine is busy or failed | Retry — respect Retry-After when present. A failed create is not charged |
Billing
Speech is priced per character: price = the model's credits-per-character rate × the number of characters in input. Rates per model are shown in the Pricing Policy. You are charged only after the audio is delivered.
| Outcome | Billing |
|---|---|
200 | Charged: the model's credit-per-character rate × characters in input |
404 / 529 — wrong model, or synthesis failed or timed out | Not charged |
Error Codes
| Code | Meaning | Action |
|---|---|---|
400 | Malformed request — missing model/input/voice, input over 4096 characters, a response_format the provider rejects, speed outside 0.25–4.0, or a missing language on a model that requires it | Read error.message — it names the offending field |
401 | Invalid or missing API key | Check your toc- key |
402 | The model has no per-character price configured | If it persists on a listed model, the combo is missing its price — contact support |
404 | Unknown model, or the model is not configured for speech | Verify the model name against the LLM Catalog |
422 | The selected voice was not found | Use a voice id from the voice tables below — plain names only work on tts-avevoice* models |
529 | Provider busy, synthesis failed, or the request timed out | Retry — respect Retry-After when present. Failed or timed-out synthesis is not charged |