API documentation
Create an API key on the API keys page. A character costs 1 to 5 credits depending on its language (see Languages); credits are refunded if audio generation fails.
For AI agents
Three ways to integrate; pick the one your agent supports. Machine-readable: /llms.txt (guide for LLMs) and /api/v1/openapi.json (OpenAPI 3.1).
1. MCP server
URL https://voicewrap247.com/api/mcp. Tools: list_voices, text_to_speech, get_job, dub_media, get_dubbing, get_balance. Authenticate with OAuth (sign in, no key needed) or an API key. list_voices and text_to_speech take an optional language field (see Languages). dub_media dubs a video or audio file from a public source_url, and get_dubbing returns its progress and files (see Dubbing).
Claude.ai / Claude Desktop: no API key needed
- Settings → Connectors → Add custom connector.
- Name it VoiceWrap, paste the URL
https://voicewrap247.com/api/mcp, click Add. - Click Connect, sign in with Google and choose Allow. Disconnect under API keys & connections.
Other clients use an API key:
claude mcp add --transport http voicewrap https://voicewrap247.com/api/mcp \ --header "Authorization: Bearer sk_live_..."
{
"mcpServers": {
"voicewrap": {
"url": "https://voicewrap247.com/api/mcp",
"headers": { "Authorization": "Bearer sk_live_..." }
}
}
}{
"mcpServers": {
"voicewrap": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://voicewrap247.com/api/mcp",
"--header", "Authorization: Bearer sk_live_..."]
}
}
}2. OpenAI SDK (change base_url)
from openai import OpenAI
client = OpenAI(api_key="sk_live_...", base_url="https://voicewrap247.com/api/v1")
client.audio.speech.create(
model="voicewrap", voice="Ngọc Huyền", input="Xin chào!"
).write_to_file("speech.mp3")import OpenAI from "openai";
const client = new OpenAI({ apiKey: "sk_live_...", baseURL: "https://voicewrap247.com/api/v1" });
const res = await client.audio.speech.create({ model: "voicewrap", voice: "nova", input: "Xin chào!" });
await fs.promises.writeFile("speech.mp3", Buffer.from(await res.arrayBuffer()));3. REST API
See the sections below.
Authentication
Authorization: Bearer sk_live_...
Limited to 30 requests per minute per account. Up to 5,000 characters of text per request.
OpenAI compatible: POST /api/v1/audio/speech
Same format as OpenAI. Returns the audio file directly; long text is waited on until it is done (up to about 4.5 minutes). The model field is ignored. The optional language field is a VoiceWrap extension (see Languages).
curl https://voicewrap247.com/api/v1/audio/speech \
-H "Authorization: Bearer $VOICEWRAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "Xin chào, đây là VoiceWrap.", "voice": "Ngọc Huyền", "response_format": "mp3"}' \
--output speech.mp3OpenAI voice names work too. In Vietnamese they map to: alloy → Ngọc Huyền, ash → Minh Triết, ballad → Ngọc Linh, coral → Thùy Dung, echo → Hải Đăng, fable → Thiện Minh, nova → Mai Anh, onyx → Thiền Tâm Đức, sage → Quỳnh Anh, shimmer → Trúc Ly, verse → Thanh Bình. In other languages, each maps to a native voice of the same gender.
Text to speech (jobs): POST /api/v1/tts
Returns a job with an audio link (expires after 1 hour). Up to 500 characters returns right away (200); longer text, or sending "async": true, returns 202, after which you poll GET /api/v1/tts/:id every 2–5 seconds.
curl https://voicewrap247.com/api/v1/tts \
-H "Authorization: Bearer $VOICEWRAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Giá gói Pro là 100.000 đồng.", "voice": "Hải Đăng", "speed": 1.0, "format": "mp3"}'{
"id": "f0b2...",
"status": "succeeded", // queued | running | succeeded | failed
"progress": 1, // 0..1 while running
"queue_position": null, // jobs ahead of this one while queued
"language": "vi", // language read; unless sent, the voice's own or detected
"audio_url": "https://...", // play, expires after 1 hour
"download_url": "https://...", // download the file
"duration_seconds": 3.4,
"chars": 28, // characters in the text
"cost": 28 // credits charged: characters × language weight
}Languages
The API reads 11 languages. Each built-in voice is a native speaker and reads only its own language; your cloned voices read any of them. Send the optional language field (an ISO 639-1 code) on POST /api/v1/tts, on POST /api/v1/audio/speech (a VoiceWrap extension to the OpenAI format) and on the MCP tool text_to_speech. When it is omitted, a built-in voice sets the language; with a cloned voice or no voice, the language is detected from the text, and with no voice that language's default voice (listed below) reads it. Numbers, percentages and currency are spelled out the way each language says them.
Supported codes, with each language's default voice:
viTiếng Việt · Hải ĐăngenEnglish · Emmazh中文 · Vivianja日本語 · Annako한국어 · SoheefrFrançais · CamilledeDeutsch · LenaesEspañol · LucíaitItaliano · GiuliaptPortuguês · BeatrizruРусский · Natasha
Any other code, including one detected from the text, returns 400 unsupported_language. A built-in voice with a different language returns 400 voice_language_mismatch, listing that language's voices.
Credits per character depend on the language read: cost = characters × weight (a run of whitespace counts once). A Chinese, Japanese or Korean character is a whole syllable, so it costs more.
| Credits per character | Languages |
|---|---|
| ×1 | vi Tiếng Việt |
| ×2 | en English, fr Français, de Deutsch, es Español, it Italiano, pt Português, ru Русский |
| ×5 | zh 中文, ja 日本語, ko 한국어 |
curl https://voicewrap247.com/api/v1/tts \
-H "Authorization: Bearer $VOICEWRAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "こんにちは、VoiceWrap です。", "voice": "Anna", "language": "ja"}'curl https://voicewrap247.com/api/v1/audio/speech \
-H "Authorization: Bearer $VOICEWRAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "Hello from VoiceWrap.", "voice": "nova", "language": "en"}' \
--output hello.mp3client.audio.speech.create(
model="voicewrap", voice="nova", input="Hello from VoiceWrap.",
extra_body={"language": "en"}, # language is a VoiceWrap extension, so it goes in extra_body
).write_to_file("hello.mp3")Voices: GET /api/v1/voices
Pass voice as a name (with or without diacritics, e.g. "ngoc-huyen"), an id, or an OpenAI voice name. When omitted, the default voice for the language is used. Add ?language=ja to list only the voices that read that language (plus your cloned ones). Each voice has language (null for cloned voices, which read every language) and gender.
curl "https://voicewrap247.com/api/v1/voices?language=vi" -H "Authorization: Bearer $VOICEWRAP_API_KEY"
{ "voices": [ { "id": "...", "name": "Ngọc Huyền", "key": "ngoc-huyen",
"description": "Nữ · Miền Bắc · Tự nhiên", "type": "system",
"language": "vi", "gender": "female",
"preview_url": "https://..." } ] }Voice cloning: POST /api/v1/voices
3 to 15 seconds of clear speech, plus an exact transcript of it. Only use your own voice or the voice of someone who has consented.
curl https://voicewrap247.com/api/v1/voices \ -H "Authorization: Bearer $VOICEWRAP_API_KEY" \ -F name="Giọng của tôi" \ -F ref_text="Nội dung chính xác của đoạn ghi âm" \ -F consent=true \ -F file=@sample.wav
Dubbing: POST /api/v1/dubbing
Re-voices a video or audio file in another language: the speech is transcribed, translated and read line by line in the target language, then mixed over the original background (music and effects stay). You get a dubbed MP4 (for video), an MP3 and translated SRT subtitles. Costs 20,000 credits per minute of the source, billed per second with a one-minute minimum per job; up to 15 minutes; failed jobs are refunded.
1. Upload the file
POST /api/v1/dubbing/uploads with the file's name, type and size (up to 50 MB) returns a signed upload_url; PUT the file to it (with its Content-Type), then pass upload_path in step 2. For a public or bigger file, skip this and send source_url instead.
curl https://voicewrap247.com/api/v1/dubbing/uploads \
-H "Authorization: Bearer $VOICEWRAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "intro.mp4", "content_type": "video/mp4", "size": 18874368}'
{ "upload_path": "…/source.mp4", "upload_url": "https://…", "token": "…" }
curl -X PUT "$UPLOAD_URL" -H "Content-Type: video/mp4" --data-binary @intro.mp42. Start the job
POST /api/v1/dubbing returns 202 with the job. Give exactly one of upload_path or source_url.
curl https://voicewrap247.com/api/v1/dubbing \
-H "Authorization: Bearer $VOICEWRAP_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"upload_path": "…/source.mp4", // from step 1 (or source_url)
"source_name": "intro.mp4", // optional display name
"target_language": "en", // language to dub into
"source_language": "vi", // optional; detected
"voice": "clone", // "clone" or a voice name/id
"consent": true // required with "clone"
}
EOF"voice": "clone" (the default) keeps the speaker's own voice and needs "consent": true: you confirm you have the right to use the voice of the person in the file. Otherwise pass the name or id of a built-in voice that reads the target language (GET /api/v1/voices?language=...), or one of your cloned voices.
3. Poll for the result
Poll GET /api/v1/dubbing/:id every 5–10 seconds until status is succeeded or failed; a dub takes a few minutes. File links are signed and expire after 1 hour; fetch the job again for fresh ones. GET /api/v1/dubbing lists your 20 most recent jobs (without lines).
curl https://voicewrap247.com/api/v1/dubbing/8c1e... -H "Authorization: Bearer $VOICEWRAP_API_KEY"
{
"id": "8c1e...",
"status": "succeeded", // queued | running | succeeded | failed
"progress": 1, // 0..1
"source_name": "intro.mp4",
"is_video": true,
"source_language": "vi",
"target_language": "en",
"voice": "clone",
"duration_seconds": 92.4,
"cost": 30800, // credits charged, per second
"files": { // signed links, expire after 1 hour
"video": "https://...", // MP4, null for audio
"audio": "https://...", // MP3
"subtitles": "https://..." // translated SRT
},
"lines": [ // each line: original and translation
{ "start": 0.4, "end": 3.1, "text": "Xin chào các bạn", "translation": "Hi everyone" }
],
"error": null,
"created_at": "2026-10-25T08:00:00Z",
"finished_at": "2026-10-25T08:03:12Z"
}Limits: uploads up to 50 MB, sources up to 15 minutes, 20 jobs per hour. Its own error codes: 400 consent_required, unreadable_media, too_long; 404 upload_not_found; 502 dubbing_failed (credits refunded).
Error codes
{ "error": { "code": "insufficient_credits", "message": "..." } }- 400
invalid_request(e.g. a missing or invalid field) - 400
unsupported_language(a code outside the 11 languages, including one detected from the text) - 400
voice_language_mismatch(a built-in voice with a differentlanguage; the message lists that language's voices) - 401
invalid_api_key,unauthorized - 402
insufficient_credits - 404
voice_not_found(includes the list of voices),job_not_found - 429
rate_limited - 400
consent_required,unreadable_media,too_long, 404upload_not_found, 502dubbing_failed(Dubbing) - 502
synthesis_failed(credits refunded) - 504
still_running(only on /audio/speech; poll the job by id)