{"openapi":"3.1.0","info":{"title":"VoiceWrap API","version":"1.0.0","description":"Text-to-speech in 11 languages (vi, en, zh, ja, ko, fr, de, es, it, pt, ru), each read by native voices; numbers, percentages and currency are spelled out in each language's conventions. Authenticate with `Authorization: Bearer sk_live_...` (create keys at https://voicewrap247.com/app/keys). Text costs 1 credit per character in Vietnamese, 2 in English, French, German, Spanish, Italian, Portuguese and Russian, 5 in Chinese, Japanese and Korean; dubbing a video or audio file costs 20000 credits per minute of the source; failed jobs are refunded. MCP server: https://voicewrap247.com/api/mcp. Agent guide: https://voicewrap247.com/llms.txt.","termsOfService":"https://voicewrap247.com/terms","contact":{"name":"VoiceWrap","url":"https://voicewrap247.com"}},"externalDocs":{"description":"Full reference for AI agents","url":"https://voicewrap247.com/llms-full.txt"},"servers":[{"url":"https://voicewrap247.com"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"API key, sk_live_..."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}},"Job":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","running","succeeded","failed"]},"progress":{"type":"number","description":"0 to 1 while running"},"queue_position":{"type":["integer","null"],"description":"Jobs ahead in the queue, if not started"},"voice_id":{"type":"string","format":"uuid"},"chars":{"type":"integer","description":"Length of the text in characters"},"cost":{"type":"integer","description":"Credits charged: the characters (runs of whitespace counted once) times the language's weight"},"format":{"type":"string","enum":["mp3","wav"]},"language":{"type":"string","description":"ISO 639-1 code the text was read in"},"audio_url":{"type":["string","null"],"description":"Signed URL to play, valid 1 hour"},"download_url":{"type":["string","null"],"description":"Signed URL that downloads the file, valid 1 hour"},"duration_seconds":{"type":["number","null"]},"error":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"}}},"Voice":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"key":{"type":"string","description":"Accent-free slug, also accepted as `voice`"},"description":{"type":["string","null"],"description":"Gender · accent · style"},"type":{"type":"string","enum":["system","cloned"]},"language":{"type":["string","null"],"description":"ISO 639-1 language a built-in voice reads (only that one); null for cloned voices, which read any"},"gender":{"type":["string","null"],"enum":["female","male",null]},"preview_url":{"type":["string","null"]}}},"DubbingJob":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","running","succeeded","failed"]},"progress":{"type":"number","description":"0 to 1: up to 0.3 transcribing and separating the background, 0.4 translated, 0.85 voiced, then mixing"},"started":{"type":"boolean","description":"Whether a server has picked the job up"},"source_name":{"type":["string","null"]},"is_video":{"type":"boolean","description":"Whether a dubbed MP4 is made (video sources)"},"source_language":{"type":["string","null"],"description":"ISO 639-1; null when left to detection"},"target_language":{"type":"string","enum":["vi","en","zh","ja","ko","fr","de","es","it","pt","ru"]},"voice":{"type":["string","null"],"description":"\"clone\", or the id of the voice that reads the dub"},"duration_seconds":{"type":["number","null"],"description":"Length of the source"},"cost":{"type":"integer","description":"Credits charged: 20000 per minute of the source, billed per second with a one-minute minimum per job"},"files":{"type":"object","description":"Signed URLs, valid 1 hour, once succeeded (else null); fetch the job again for fresh ones","properties":{"video":{"type":["string","null"],"description":"Dubbed MP4; null for audio sources"},"audio":{"type":["string","null"],"description":"Dubbed MP3"},"subtitles":{"type":["string","null"],"description":"Translated SRT subtitles"}}},"lines":{"type":["array","null"],"description":"The transcript line by line, once succeeded. Left out of GET /api/v1/dubbing","items":{"type":"object","properties":{"start":{"type":"number","description":"Seconds"},"end":{"type":"number","description":"Seconds"},"text":{"type":"string","description":"Original line"},"translation":{"type":"string"}}}},"error":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"}}}}},"paths":{"/api/v1/audio/speech":{"post":{"operationId":"createSpeech","summary":"OpenAI-compatible: returns the audio file directly","description":"Drop-in for OpenAI's POST /v1/audio/speech: point an OpenAI SDK's base_url at https://voicewrap247.com/api/v1. Waits for long text (up to ~4.5 minutes).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","maxLength":5000},"voice":{"type":"string","description":"Voice name (accents and case optional, e.g. \"Ngọc Huyền\", \"ngoc-huyen\" or \"Emma\"), voice id, or an OpenAI voice name (alloy, ash, ballad, coral, echo, fable, nova, onyx, sage, shimmer, verse). Each built-in voice is a native speaker and reads only its own language, which it sets; cloned voices read any supported language. OpenAI names map to a native voice of the same gender in the request's language (fixed voices for Vietnamese). Default: the language's default voice (vi: Hải Đăng, en: Emma, zh: Vivian, ja: Anna, ko: Sohee, fr: Camille, de: Lena, es: Lucía, it: Giulia, pt: Beatriz, ru: Natasha). See GET /api/v1/voices."},"response_format":{"type":"string","enum":["mp3","wav"],"default":"mp3"},"speed":{"type":"number","minimum":0.25,"maximum":4,"default":1,"description":"Clamped to 0.5–2"},"language":{"type":"string","enum":["vi","en","zh","ja","ko","fr","de","es","it","pt","ru"],"description":"ISO 639-1 language to read the text in. Default: a built-in voice's own language; with a cloned voice or no voice, detected from the text. A built-in voice reads only its own language: a different value returns 400 voice_language_mismatch. A code (or detected language) outside this list returns 400 unsupported_language."},"model":{"type":"string","description":"Ignored"}}}}}},"responses":{"200":{"description":"Audio bytes","headers":{"x-voicewrap-job-id":{"schema":{"type":"string"}},"x-voicewrap-cost":{"schema":{"type":"integer"}}},"content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}},"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invalid request (invalid_request), unsupported_language, or voice_language_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Voice or job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Synthesis failed; credits refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"504":{"description":"Still running; poll GET /api/v1/tts/{id}"}}}},"/api/v1/tts":{"post":{"operationId":"createTtsJob","summary":"Create speech; returns a job with a signed audio URL","description":"Text up to 500 characters returns 200 with the finished job. Longer text (or async=true) returns 202; poll GET /api/v1/tts/{id}.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":5000},"voice":{"type":"string","description":"Voice name (accents and case optional, e.g. \"Ngọc Huyền\", \"ngoc-huyen\" or \"Emma\"), voice id, or an OpenAI voice name (alloy, ash, ballad, coral, echo, fable, nova, onyx, sage, shimmer, verse). Each built-in voice is a native speaker and reads only its own language, which it sets; cloned voices read any supported language. OpenAI names map to a native voice of the same gender in the request's language (fixed voices for Vietnamese). Default: the language's default voice (vi: Hải Đăng, en: Emma, zh: Vivian, ja: Anna, ko: Sohee, fr: Camille, de: Lena, es: Lucía, it: Giulia, pt: Beatriz, ru: Natasha). See GET /api/v1/voices."},"speed":{"type":"number","minimum":0.5,"maximum":2,"default":1},"format":{"type":"string","enum":["mp3","wav"],"default":"mp3"},"language":{"type":"string","enum":["vi","en","zh","ja","ko","fr","de","es","it","pt","ru"],"description":"ISO 639-1 language to read the text in. Default: a built-in voice's own language; with a cloned voice or no voice, detected from the text. A built-in voice reads only its own language: a different value returns 400 voice_language_mismatch. A code (or detected language) outside this list returns 400 unsupported_language."},"async":{"type":"boolean","description":"Force background processing"}}}}}},"responses":{"200":{"description":"Finished job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"202":{"description":"Running job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"400":{"description":"Invalid request (invalid_request), unsupported_language, or voice_language_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Voice or job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Synthesis failed; credits refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tts/{id}":{"get":{"operationId":"getTtsJob","summary":"Job status and audio URLs","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"400":{"description":"Invalid request (invalid_request), unsupported_language, or voice_language_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Voice or job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Synthesis failed; credits refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/voices":{"get":{"operationId":"listVoices","summary":"System voices and your cloned voices","parameters":[{"name":"language","in":"query","required":false,"schema":{"type":"string","enum":["vi","en","zh","ja","ko","fr","de","es","it","pt","ru"]},"description":"Only the built-in voices that read this language, plus your cloned voices"}],"responses":{"200":{"description":"Voices","content":{"application/json":{"schema":{"type":"object","properties":{"voices":{"type":"array","items":{"$ref":"#/components/schemas/Voice"}}}}}}},"400":{"description":"Invalid request (invalid_request), unsupported_language, or voice_language_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Voice or job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Synthesis failed; credits refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"cloneVoice","summary":"Clone a voice from 3–15 s of speech you have the right to use","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["name","ref_text","consent","file"],"properties":{"name":{"type":"string","maxLength":60},"ref_text":{"type":"string","description":"Exact transcript of the clip"},"consent":{"type":"string","enum":["true"]},"file":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"Created voice"},"400":{"description":"Invalid request (invalid_request), unsupported_language, or voice_language_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Voice or job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Synthesis failed; credits refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/dubbing/uploads":{"post":{"operationId":"createDubbingUpload","summary":"Get a signed URL to upload a file to dub","description":"Returns a signed upload_url: PUT the file's bytes to it with its Content-Type, then pass upload_path to POST /api/v1/dubbing. Up to 50 MB; send source_url there instead for public or bigger files.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename","content_type","size"],"properties":{"filename":{"type":"string","maxLength":200,"description":"Its extension tells video from audio"},"content_type":{"type":"string","pattern":"^(audio|video)/","description":"MIME type, audio/* or video/*"},"size":{"type":"integer","minimum":1,"maximum":52428800,"description":"Bytes"}}}}}},"responses":{"200":{"description":"Signed upload","content":{"application/json":{"schema":{"type":"object","properties":{"upload_path":{"type":"string","description":"Pass to POST /api/v1/dubbing"},"upload_url":{"type":"string","description":"PUT the file here"},"token":{"type":"string"}}}}}},"400":{"description":"Invalid request (invalid_request), unsupported_language, or voice_language_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Voice or job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Synthesis failed; credits refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/dubbing":{"get":{"operationId":"listDubbingJobs","summary":"Your 20 most recent dubbing jobs (without lines)","responses":{"200":{"description":"Jobs","content":{"application/json":{"schema":{"type":"object","properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/DubbingJob"}}}}}}},"400":{"description":"Invalid request (invalid_request), unsupported_language, or voice_language_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Voice or job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Synthesis failed; credits refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createDubbingJob","summary":"Dub a video or audio file into another language","description":"Transcribes the speech, translates it, reads each line in the target language and mixes it over the original background. Outputs a dubbed MP4 (video sources), an MP3 and translated SRT subtitles. Costs 20000 credits per minute of the source, billed per second with a one-minute minimum per job, up to 15 minutes; failed jobs are refunded. Returns 202; poll GET /api/v1/dubbing/{id}. Errors: 400 consent_required, unreadable_media, too_long; 404 upload_not_found; 502 dubbing_failed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["target_language"],"properties":{"upload_path":{"type":"string","description":"From POST /api/v1/dubbing/uploads. Give this or source_url"},"source_url":{"type":"string","format":"uri","description":"Public http(s) URL of the file. Give this or upload_path"},"source_name":{"type":"string","maxLength":200,"description":"Display name"},"target_language":{"type":"string","enum":["vi","en","zh","ja","ko","fr","de","es","it","pt","ru"],"description":"Language to dub into"},"source_language":{"type":"string","enum":["vi","en","zh","ja","ko","fr","de","es","it","pt","ru"],"description":"Spoken language of the source. Default: detected. Must differ from target_language"},"voice":{"type":"string","default":"clone","description":"\"clone\" keeps the speaker's own voice (needs consent: true); or the name or id of a built-in voice that reads target_language, or of one of your cloned voices"},"consent":{"type":"boolean","description":"Required true with voice \"clone\": you have the right to use the voice of the person in the file"}}}}}},"responses":{"202":{"description":"Running job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DubbingJob"}}}},"400":{"description":"Invalid request (invalid_request), unsupported_language, or voice_language_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Voice or job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Synthesis failed; credits refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/dubbing/{id}":{"get":{"operationId":"getDubbingJob","summary":"Dubbing job status, file URLs and lines","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DubbingJob"}}}},"400":{"description":"Invalid request (invalid_request), unsupported_language, or voice_language_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Voice or job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Synthesis failed; credits refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me":{"get":{"operationId":"getAccount","summary":"Account and credit balance","responses":{"200":{"description":"Account","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"credits_balance":{"type":"integer"}}}}}},"400":{"description":"Invalid request (invalid_request), unsupported_language, or voice_language_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Voice or job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Synthesis failed; credits refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}