{"openapi":"3.1.0","info":{"title":"ResponsiveVoice TTS API","version":"2.0.1","description":"Text-to-speech API with a multilingual built-in voice catalog, streaming support, and bring-your-own-key access to premium provider voices.\n\n## Quick Start (Browser)\n\n```html\n<script src=\"https://cdn.responsivevoice.org/sdk/latest/responsivevoice.js\"></script>\n<script>\n  responsiveVoice.init({ apiKey: 'YOUR_API_KEY' });\n  responsiveVoice.speak('Hello world', 'UK English Female');\n</script>\n```\n","license":{"name":"MIT","url":"https://opensource.org/licenses/MIT"}},"servers":[{"url":"https://texttospeech.responsivevoice.org","description":"Current"}],"components":{"securitySchemes":{"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Public website identifier. Send together with X-API-Secret."},"apiSecretHeader":{"type":"apiKey","in":"header","name":"X-API-Secret","description":"Non-public server credential. Send together with X-API-Key."}},"schemas":{},"parameters":{}},"paths":{"/health":{"get":{"tags":["System"],"summary":"Health check","x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl https://texttospeech.responsivevoice.org/health"}],"responses":{"200":{"description":"Service is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}},"required":["status"]}}}}}}},"/v2/config":{"get":{"tags":["Configuration"],"summary":"Get website configuration","security":[{"apiKeyHeader":[],"apiSecretHeader":[]}],"description":"Returns the website configuration including feature flags and voice settings for the authenticated user. Free-tier users receive all defaults (all features disabled, default voice). Commercial and admin users receive their stored dashboard settings merged with defaults. Feature flags control JS dashboard plugins: welcomeMessage (speaks a greeting on page load), speakSelectedText (reads highlighted text aloud), speakLinks (reads link text on hover), speakInactivity (speaks after a period of user inactivity), speakEndPage (speaks when user scrolls to page bottom), exitIntent (speaks when user moves cursor to leave the page), accessibilityNavigation (keyboard tab navigation with speech), paragraphNavigation (keyboard paragraph-by-paragraph navigation with speech).","x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl https://texttospeech.responsivevoice.org/v2/config -H 'X-API-Key: YOUR_API_KEY' -H 'X-API-Secret: YOUR_API_SECRET'"},{"lang":"javascript","label":"Node.js","source":"import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client';\n\nconst client = new ResponsiveVoiceAPIClient({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' });\nconst config = await client.getConfig();"}],"responses":{"200":{"description":"Website configuration with feature flags, voice settings (name, pitch, rate, volume), and analytics toggle. Free-tier users get all features disabled; paid users get their stored dashboard configuration.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."}},"content":{"application/json":{"schema":{"type":"object","properties":{"features":{"type":"object","properties":{"welcomeMessage":{"type":"object","properties":{"enabled":{"type":"boolean","default":false},"text":{"type":["string","null"],"default":null}},"default":{"enabled":false,"text":null}},"speakSelectedText":{"type":"object","properties":{"enabled":{"type":"boolean","default":false}},"default":{"enabled":false}},"speakLinks":{"type":"object","properties":{"enabled":{"type":"boolean","default":false}},"default":{"enabled":false}},"speakInactivity":{"type":"object","properties":{"enabled":{"type":"boolean","default":false},"text":{"type":["string","null"],"default":null}},"default":{"enabled":false,"text":null}},"speakEndPage":{"type":"object","properties":{"enabled":{"type":"boolean","default":false},"text":{"type":["string","null"],"default":null}},"default":{"enabled":false,"text":null}},"exitIntent":{"type":"object","properties":{"enabled":{"type":"boolean","default":false},"text":{"type":["string","null"],"default":null}},"default":{"enabled":false,"text":null}},"accessibilityNavigation":{"type":"object","properties":{"enabled":{"type":"boolean","default":false}},"default":{"enabled":false}},"paragraphNavigation":{"type":"object","properties":{"enabled":{"type":"boolean","default":false}},"default":{"enabled":false}},"webPlayer":{"type":"object","properties":{"enabled":{"type":"boolean","default":false},"selector":{"type":"string","default":"article"},"paragraphSelector":{"type":"string","default":"p, h2, h3, li"},"position":{"anyOf":[{"type":"string","enum":["inline","before","after"]},{"type":"object","properties":{"target":{"type":"string"},"at":{"type":"string","enum":["inside","before","after"],"default":"inside"}},"required":["target"]}],"default":"before"},"theme":{"anyOf":[{"type":"string","enum":["neutral","responsivevoice"]},{"type":"object","properties":{"bg":{"type":"string"},"fg":{"type":"string"},"muted":{"type":"string"},"accent":{"type":"string"},"accentSoft":{"type":"string"},"hover":{"type":"string"},"border":{"type":"string"},"track":{"type":"string"},"fill":{"type":"string"}}}],"default":"neutral"},"controls":{"type":"object","properties":{"progress":{"type":"boolean","default":true},"time":{"type":"boolean","default":true},"skip":{"type":"boolean","default":true},"speed":{"type":"boolean","default":true},"brand":{"type":"boolean","default":true}},"default":{"progress":true,"time":true,"skip":true,"speed":true,"brand":true}},"navigation":{"type":"object","properties":{"paragraphHighlight":{"type":"boolean","default":true},"paragraphClick":{"type":"boolean","default":true}},"default":{"paragraphHighlight":true,"paragraphClick":true}},"layout":{"type":"object","properties":{"mode":{"type":"string","enum":["shrink","fill"],"default":"shrink"},"display":{"type":"string","enum":["inline","block"],"default":"block"}},"default":{"mode":"shrink","display":"block"}},"miniPlayer":{"type":"object","properties":{"enabled":{"type":"boolean","default":true},"position":{"anyOf":[{"type":"string","enum":["top-left","top-right","bottom-left","bottom-right"]},{"type":"object","properties":{"top":{"type":"string"},"right":{"type":"string"},"bottom":{"type":"string"},"left":{"type":"string"}}}],"default":"bottom-left"},"animation":{"type":"string","enum":["none","fade","slide","pop"],"default":"slide"}},"default":{"enabled":true,"position":"bottom-left","animation":"slide"}},"voice":{"anyOf":[{"type":"string"},{"type":"object","properties":{"regex":{"type":"string"},"flags":{"type":"string"}},"required":["regex"]},{"type":"object","properties":{"name":{"type":"string"},"lang":{"type":"string"},"gender":{"type":"string","enum":["f","m","male","female"]},"isByok":{"type":"boolean"},"provider":{"type":"string"}}}]},"sanitize":{"type":"object","properties":{"enabled":{"type":"boolean","default":true},"exclude":{"type":"array","items":{"type":"string"},"default":[]}},"default":{"enabled":true,"exclude":[]}},"pitch":{"type":"number","minimum":0,"maximum":2},"rate":{"type":"number","minimum":0,"maximum":2},"volume":{"type":"number","minimum":0,"maximum":1}},"default":{"enabled":false,"selector":"article","paragraphSelector":"p, h2, h3, li","position":"before","theme":"neutral","controls":{"progress":true,"time":true,"skip":true,"speed":true,"brand":true},"navigation":{"paragraphHighlight":true,"paragraphClick":true},"layout":{"mode":"shrink","display":"block"},"miniPlayer":{"enabled":true,"position":"bottom-left","animation":"slide"},"sanitize":{"enabled":true,"exclude":[]}}},"welcomeMessageOnce":{"type":"boolean","default":false}},"default":{"welcomeMessage":{"enabled":false,"text":null},"speakSelectedText":{"enabled":false},"speakLinks":{"enabled":false},"speakInactivity":{"enabled":false,"text":null},"speakEndPage":{"enabled":false,"text":null},"exitIntent":{"enabled":false,"text":null},"accessibilityNavigation":{"enabled":false},"paragraphNavigation":{"enabled":false},"webPlayer":{"enabled":false,"selector":"article","paragraphSelector":"p, h2, h3, li","position":"before","theme":"neutral","controls":{"progress":true,"time":true,"skip":true,"speed":true,"brand":true},"navigation":{"paragraphHighlight":true,"paragraphClick":true},"layout":{"mode":"shrink","display":"block"},"miniPlayer":{"enabled":true,"position":"bottom-left","animation":"slide"},"sanitize":{"enabled":true,"exclude":[]}},"welcomeMessageOnce":false}},"voice":{"type":"object","properties":{"name":{"type":"string","default":"UK English Female"},"pitch":{"type":"number","minimum":0,"maximum":2,"default":1},"rate":{"type":"number","minimum":0,"maximum":2,"default":1},"volume":{"type":"number","minimum":0,"maximum":1,"default":1}},"default":{"name":"UK English Female","pitch":1,"rate":1,"volume":1}},"analytics":{"type":"object","properties":{"enabled":{"type":"boolean","default":false}},"default":{"enabled":false}}}}}}},"401":{"description":"API key required — all config requests must be authenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"429":{"description":"Rate limit exceeded. The body `code` is `RATE_LIMIT_EXCEEDED` (per-minute tier limit) or `BURST_RATE_LIMIT_EXCEEDED` (per API key + client IP burst).","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"Retry-After":{"schema":{"type":"integer","description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."},"required":true,"description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}}}}},"/v2/voices":{"get":{"tags":["Voices"],"summary":"List all voices","security":[{"apiKeyHeader":[],"apiSecretHeader":[]}],"description":"Returns the full voice collection: user-facing responsive voices and internal system voices used for voice resolution. When platform parameters are provided, platform-specific availability rules are applied — voices unavailable on the given browser/OS combination are excluded. Also includes premium BYOK (Bring Your Own Key) provider voices when the user has configured API credentials for their website. Each voice in the response includes HATEOAS _links for self-discovery, synthesis, and streaming navigation.","x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl https://texttospeech.responsivevoice.org/v2/voices -H 'X-API-Key: YOUR_API_KEY' -H 'X-API-Secret: YOUR_API_SECRET'"},{"lang":"javascript","label":"Node.js","source":"import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client';\n\nconst client = new ResponsiveVoiceAPIClient({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' });\nconst { voices } = await client.getVoices();"}],"parameters":[{"schema":{"type":"string"},"required":false,"description":"Browser name (e.g. \"Chrome\", \"Firefox\", \"Safari\"). Used to filter voices by platform-specific availability rules.","name":"browser","in":"query"},{"schema":{"type":"string"},"required":false,"description":"Browser version string (e.g. \"120.0\"). Refines platform filtering for version-specific voice support.","name":"browserVersion","in":"query"},{"schema":{"type":"string"},"required":false,"description":"Operating system name (e.g. \"Windows\", \"macOS\", \"Android\", \"iOS\"). Used to filter voices by OS-specific availability.","name":"os","in":"query"},{"schema":{"type":"string"},"required":false,"description":"OS version string (e.g. \"10\", \"14.2\"). Refines platform filtering for version-specific voice support.","name":"osVersion","in":"query"}],"responses":{"200":{"description":"Voice collection containing user-facing voices with HATEOAS navigation links, system voices for internal resolution, and a total count. Supports ETag-based conditional requests.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."}},"content":{"application/json":{"schema":{"type":"object","properties":{"voices":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"flag":{"type":"string"},"gender":{"type":"string","enum":["f","m"]},"lang":{"type":"string"},"voiceIDs":{"type":"array","items":{"type":"number"}},"deprecated":{"type":"boolean"},"isByok":{"type":"boolean"},"provider":{"type":"string"},"_links":{"type":"object","properties":{"self":{"type":"object","properties":{"href":{"type":"string"}},"required":["href"],"ref":"HrefLink","description":"HATEOAS link with absolute URL"},"synthesize":{"type":"object","properties":{"href":{"type":"string"},"method":{"type":"string","enum":["POST"]},"body":{"type":"object","properties":{"voice":{"type":"string"},"lang":{"type":"string"}},"required":["voice"]}},"required":["href","method","body"],"ref":"SynthesizeLink","description":"HATEOAS synthesize action link"},"synthesize:stream":{"type":"object","properties":{"href":{"type":"string"},"method":{"type":"string","enum":["POST"]},"body":{"type":"object","properties":{"voice":{"type":"string"},"lang":{"type":"string"},"stream":{"type":"boolean"}},"required":["voice","stream"]},"accept":{"type":"string","enum":["text/event-stream"]},"premium":{"type":"boolean"}},"required":["href","method","body","accept","premium"],"ref":"SynthesizeStreamLink","description":"HATEOAS streaming synthesize link (HTTP audio streaming)"},"stream:websocket":{"type":"object","properties":{"href":{"type":"string"},"protocol":{"type":"string","enum":["websocket"]},"message":{"type":"object","properties":{"type":{"type":"string"},"voice":{"type":"string"},"lang":{"type":"string"}},"required":["type","voice"]},"premium":{"type":"boolean"}},"required":["href","protocol","message","premium"],"ref":"StreamWebsocketLink","description":"HATEOAS WebSocket stream link"}},"required":["self","synthesize","synthesize:stream","stream:websocket"],"ref":"VoiceLinks","description":"HATEOAS links on a voice resource"}},"required":["name","flag","gender","lang","voiceIDs","_links"],"ref":"VoiceWithLinks","description":"User-facing voice with HATEOAS links"}},"systemVoices":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"},"lang":{"type":"string"},"rate":{"type":"number"},"pitch":{"type":"number"},"timerSpeed":{"type":"number"},"fallbackVoice":{"type":"boolean"},"service":{"type":"string","enum":["g1","g2","g3","g5","gwn","msv","oai"],"ref":"TTSService","description":"Available TTS service backends"},"voiceName":{"type":"string"},"gender":{"type":"string"},"volume":{"type":"number"},"deprecated":{"type":"boolean"}},"required":["id","name"],"ref":"SystemVoice","description":"Low-level system voice mapping"}},"count":{"type":"number"},"_links":{"type":"object","properties":{"self":{"type":"object","properties":{"href":{"type":"string"}},"required":["href"],"ref":"HrefLink","description":"HATEOAS link with absolute URL"}},"required":["self"],"ref":"CollectionLinks","description":"HATEOAS links on the voice collection"}},"required":["voices","systemVoices","count","_links"],"ref":"VoicesListResponse","description":"Voice collection with HATEOAS navigation"}}}},"304":{"description":"Not modified (ETag match)"},"401":{"description":"API key required","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"429":{"description":"Rate limit exceeded. The body `code` is `RATE_LIMIT_EXCEEDED` (per-minute tier limit) or `BURST_RATE_LIMIT_EXCEEDED` (per API key + client IP burst).","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"Retry-After":{"schema":{"type":"integer","description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."},"required":true,"description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}}}}},"/v2/voices/by-language/{lang}":{"get":{"tags":["Voices"],"summary":"Get voices by language","security":[{"apiKeyHeader":[],"apiSecretHeader":[]}],"description":"Returns all voices available for a specific BCP-47 language code (e.g. \"en-US\", \"pt-BR\", \"ja-JP\"). Includes both free and premium voices. Each voice includes HATEOAS _links for navigation.","x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl https://texttospeech.responsivevoice.org/v2/voices/by-language/en-US -H 'X-API-Key: YOUR_API_KEY' -H 'X-API-Secret: YOUR_API_SECRET'"},{"lang":"javascript","label":"Node.js","source":"import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client';\n\nconst client = new ResponsiveVoiceAPIClient({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' });\nconst { voices } = await client.getVoicesByLanguage('en-US');"}],"parameters":[{"schema":{"type":"string"},"required":true,"description":"BCP-47 language code (e.g. \"en-US\", \"fr-FR\", \"zh-CN\"). Case-sensitive.","name":"lang","in":"path"}],"responses":{"200":{"description":"Voices matching the requested language, with count and HATEOAS navigation links. Supports ETag-based conditional requests.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."}},"content":{"application/json":{"schema":{"type":"object","properties":{"language":{"type":"string"},"voices":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"flag":{"type":"string"},"gender":{"type":"string","enum":["f","m"]},"lang":{"type":"string"},"voiceIDs":{"type":"array","items":{"type":"number"}},"deprecated":{"type":"boolean"},"isByok":{"type":"boolean"},"provider":{"type":"string"},"_links":{"type":"object","properties":{"self":{"type":"object","properties":{"href":{"type":"string"}},"required":["href"],"ref":"HrefLink","description":"HATEOAS link with absolute URL"},"synthesize":{"type":"object","properties":{"href":{"type":"string"},"method":{"type":"string","enum":["POST"]},"body":{"type":"object","properties":{"voice":{"type":"string"},"lang":{"type":"string"}},"required":["voice"]}},"required":["href","method","body"],"ref":"SynthesizeLink","description":"HATEOAS synthesize action link"},"synthesize:stream":{"type":"object","properties":{"href":{"type":"string"},"method":{"type":"string","enum":["POST"]},"body":{"type":"object","properties":{"voice":{"type":"string"},"lang":{"type":"string"},"stream":{"type":"boolean"}},"required":["voice","stream"]},"accept":{"type":"string","enum":["text/event-stream"]},"premium":{"type":"boolean"}},"required":["href","method","body","accept","premium"],"ref":"SynthesizeStreamLink","description":"HATEOAS streaming synthesize link (HTTP audio streaming)"},"stream:websocket":{"type":"object","properties":{"href":{"type":"string"},"protocol":{"type":"string","enum":["websocket"]},"message":{"type":"object","properties":{"type":{"type":"string"},"voice":{"type":"string"},"lang":{"type":"string"}},"required":["type","voice"]},"premium":{"type":"boolean"}},"required":["href","protocol","message","premium"],"ref":"StreamWebsocketLink","description":"HATEOAS WebSocket stream link"}},"required":["self","synthesize","synthesize:stream","stream:websocket"],"ref":"VoiceLinks","description":"HATEOAS links on a voice resource"}},"required":["name","flag","gender","lang","voiceIDs","_links"],"ref":"VoiceWithLinks","description":"User-facing voice with HATEOAS links"}},"count":{"type":"number"},"_links":{"type":"object","properties":{"self":{"type":"object","properties":{"href":{"type":"string"}},"required":["href"],"ref":"HrefLink","description":"HATEOAS link with absolute URL"},"allVoices":{"type":"object","properties":{"href":{"type":"string"}},"required":["href"],"ref":"HrefLink","description":"HATEOAS link with absolute URL"}},"required":["self","allVoices"],"ref":"ByLanguageLinks","description":"HATEOAS links on a by-language voice collection"}},"required":["language","voices","count","_links"],"ref":"VoicesByLanguageResponse","description":"Voices for a specific language with HATEOAS navigation"}}}},"304":{"description":"Not modified (ETag match)"},"401":{"description":"API key required","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"429":{"description":"Rate limit exceeded. The body `code` is `RATE_LIMIT_EXCEEDED` (per-minute tier limit) or `BURST_RATE_LIMIT_EXCEEDED` (per API key + client IP burst).","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"Retry-After":{"schema":{"type":"integer","description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."},"required":true,"description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}}}}},"/v2/voices/{name}":{"get":{"tags":["Voices"],"summary":"Get voice by name","security":[{"apiKeyHeader":[],"apiSecretHeader":[]}],"description":"Returns a specific voice by its exact name (e.g. \"UK English Female\"). The response includes HATEOAS _links with a self link, a synthesize link for generating audio with this voice, and a stream link for real-time streaming synthesis.","x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl https://texttospeech.responsivevoice.org/v2/voices/UK%20English%20Female -H 'X-API-Key: YOUR_API_KEY' -H 'X-API-Secret: YOUR_API_SECRET'"},{"lang":"javascript","label":"Node.js","source":"import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client';\n\nconst client = new ResponsiveVoiceAPIClient({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' });\nconst voice = await client.getVoice('UK English Female');"}],"parameters":[{"schema":{"type":"string"},"required":true,"description":"Exact voice name (e.g. \"UK English Female\", \"US English Male\"). URL-encode spaces as %20.","name":"name","in":"path"}],"responses":{"200":{"description":"Voice details with HATEOAS navigation links (self, synthesize, stream). Supports ETag-based conditional requests.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."}},"content":{"application/json":{"schema":{"type":"object","properties":{"voice":{"type":"object","properties":{"name":{"type":"string"},"flag":{"type":"string"},"gender":{"type":"string","enum":["f","m"]},"lang":{"type":"string"},"voiceIDs":{"type":"array","items":{"type":"number"}},"deprecated":{"type":"boolean"},"isByok":{"type":"boolean"},"provider":{"type":"string"}},"required":["name","flag","gender","lang","voiceIDs"],"ref":"Voice","description":"User-facing voice definition"},"_links":{"type":"object","properties":{"self":{"type":"object","properties":{"href":{"type":"string"}},"required":["href"],"ref":"HrefLink","description":"HATEOAS link with absolute URL"},"synthesize":{"type":"object","properties":{"href":{"type":"string"},"method":{"type":"string","enum":["POST"]},"body":{"type":"object","properties":{"voice":{"type":"string"},"lang":{"type":"string"}},"required":["voice"]}},"required":["href","method","body"],"ref":"SynthesizeLink","description":"HATEOAS synthesize action link"},"synthesize:stream":{"type":"object","properties":{"href":{"type":"string"},"method":{"type":"string","enum":["POST"]},"body":{"type":"object","properties":{"voice":{"type":"string"},"lang":{"type":"string"},"stream":{"type":"boolean"}},"required":["voice","stream"]},"accept":{"type":"string","enum":["text/event-stream"]},"premium":{"type":"boolean"}},"required":["href","method","body","accept","premium"],"ref":"SynthesizeStreamLink","description":"HATEOAS streaming synthesize link (HTTP audio streaming)"},"stream:websocket":{"type":"object","properties":{"href":{"type":"string"},"protocol":{"type":"string","enum":["websocket"]},"message":{"type":"object","properties":{"type":{"type":"string"},"voice":{"type":"string"},"lang":{"type":"string"}},"required":["type","voice"]},"premium":{"type":"boolean"}},"required":["href","protocol","message","premium"],"ref":"StreamWebsocketLink","description":"HATEOAS WebSocket stream link"},"collection":{"type":"object","properties":{"href":{"type":"string"}},"required":["href"],"ref":"HrefLink","description":"HATEOAS link with absolute URL"},"byLanguage":{"type":"object","properties":{"href":{"type":"string"}},"required":["href"],"ref":"HrefLink","description":"HATEOAS link with absolute URL"}},"required":["self","synthesize","synthesize:stream","stream:websocket","collection","byLanguage"],"ref":"VoiceDetailLinks","description":"HATEOAS links on a voice detail response"}},"required":["voice","_links"],"ref":"VoiceDetailResponse","description":"Single voice detail with HATEOAS navigation"}}}},"304":{"description":"Not modified — the resource has not changed since the last request (ETag match)"},"401":{"description":"API key required","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"404":{"description":"No voice found with the given name","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"429":{"description":"Rate limit exceeded. The body `code` is `RATE_LIMIT_EXCEEDED` (per-minute tier limit) or `BURST_RATE_LIMIT_EXCEEDED` (per API key + client IP burst).","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"Retry-After":{"schema":{"type":"integer","description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."},"required":true,"description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}}}}},"/v2/text/synthesize":{"get":{"tags":["Synthesis"],"summary":"Synthesize text to speech (GET)","security":[{"apiKeyHeader":[],"apiSecretHeader":[]}],"description":"Converts text to speech audio via query parameters. This endpoint is idempotent and CDN-cacheable — responses include Cache-Control headers with long TTLs (1 day client, 7 days CDN). Prefer this over POST for deterministic requests where caching is beneficial. The same parameters always produce the same audio, making it safe for CDN edge caching.","x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl \"https://texttospeech.responsivevoice.org/v2/text/synthesize?text=Hello+world&voice=US+English+Female\" -H 'X-API-Key: YOUR_API_KEY' -H 'X-API-Secret: YOUR_API_SECRET' --output speech.mp3"},{"lang":"javascript","label":"Node.js","source":"import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client';\n\nconst client = new ResponsiveVoiceAPIClient({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' });\nconst audio = await client.synthesize({ text: 'Hello world', voice: 'US English Female' });"}],"parameters":[{"schema":{"type":"string"},"required":true,"description":"The text to convert to speech. Supports plain text and SSML for compatible engines.","name":"text","in":"query"},{"schema":{"type":"string"},"required":false,"description":"ResponsiveVoice name (e.g. \"UK English Male\"). Resolved server-side to engine + lang. Either voice or lang must be provided.","name":"voice","in":"query"},{"schema":{"type":"string"},"required":false,"description":"BCP-47 language code (e.g. \"en-US\", \"pt-BR\", \"ja-JP\"). Required unless voice is provided.","name":"lang","in":"query"},{"schema":{"type":"string","enum":["g1","g2","g3","g5","gwn","msv","oai"],"ref":"TTSService"},"required":false,"description":"TTS engine code: g1, g2, g3, g5 (standard engines), gwn (Google Cloud WaveNet, BYOK), msv (Azure Speech, BYOK), oai (OpenAI TTS, BYOK). Defaults to g1.","name":"engine","in":"query"},{"schema":{"type":"string"},"required":false,"description":"System voice name for the TTS engine (e.g. \"rjs\"). Use the \"voice\" parameter instead for friendly names like \"UK English Male\".","name":"name","in":"query"},{"schema":{"type":"string","enum":["male","female"],"ref":"VoiceGender"},"required":false,"description":"Preferred voice gender: \"male\" or \"female\". Used when no specific voice name is provided.","name":"gender","in":"query"},{"schema":{"type":["number","null"],"minimum":0,"maximum":2},"required":false,"description":"Voice pitch from 0.0 (lowest) to 2.0 (highest). 1.0 is normal.","name":"pitch","in":"query"},{"schema":{"type":["number","null"],"minimum":0,"maximum":2},"required":false,"description":"Speech rate from 0.0 (slowest) to 2.0 (fastest). 1.0 is normal.","name":"rate","in":"query"},{"schema":{"type":["number","null"],"minimum":0,"maximum":1},"required":false,"description":"Audio volume from 0.0 (silent) to 1.0 (full volume). Default is 1.0.","name":"volume","in":"query"},{"schema":{"type":"string","enum":["mp3","ogg","wav"],"ref":"AudioFormat"},"required":false,"description":"Output audio format. Defaults to the engine's native format (mp3 for most, ogg for g5). Supported: g1/g2/g3 → mp3 only; g5 → ogg only; gwn → mp3, ogg; msv/oai → mp3, ogg, wav.","name":"format","in":"query"}],"responses":{"200":{"description":"Audio binary data. Content-Type matches the requested format (audio/mpeg, audio/ogg, or audio/wav). Includes RV-Cached header indicating cache provenance and RV-Prosody-Applied listing the prosody knobs (pitch/rate/volume) the server applied upstream.","headers":{"RV-Prosody-Applied":{"schema":{"type":"string","description":"Comma-separated subset of {pitch, rate, volume} the server applied upstream for this call. Clients use this to decide whether to apply their own client-side fallback for the remaining knobs."},"required":true,"description":"Comma-separated subset of {pitch, rate, volume} the server applied upstream for this call. Clients use this to decide whether to apply their own client-side fallback for the remaining knobs."},"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."}},"content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"204":{"description":"No content — returned when volume=0 was requested; the server skips upstream synthesis entirely and emits no audio body.","headers":{"RV-Prosody-Applied":{"schema":{"type":"string","description":"Always \"volume\" for 204 responses — server skipped synthesis."},"required":true,"description":"Always \"volume\" for 204 responses — server skipped synthesis."},"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."}}},"400":{"description":"Invalid synthesis request — missing required parameters or values out of range","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"401":{"description":"API key required","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"404":{"description":"No provider available for the requested engine code","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"429":{"description":"Rate limit exceeded. The body `code` is `RATE_LIMIT_EXCEEDED` (per-minute tier limit) or `BURST_RATE_LIMIT_EXCEEDED` (per API key + client IP burst).","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"Retry-After":{"schema":{"type":"integer","description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."},"required":true,"description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"502":{"description":"Upstream TTS provider returned an error during synthesis","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}}}},"post":{"tags":["Synthesis"],"summary":"Synthesize text to speech (POST)","security":[{"apiKeyHeader":[],"apiSecretHeader":[]}],"description":"Converts text to speech audio via JSON request body. To enable real-time streaming, set the `Accept: text/event-stream` header — audio is streamed progressively as it is synthesized (HTTP audio streaming). Streaming requires a premium engine (gwn, msv, or oai) with BYOK credentials configured. Without the streaming header, returns complete audio as a single binary response. POST responses are not CDN-cached (Cache-Control: public, max-age=86400 only).","x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -X POST https://texttospeech.responsivevoice.org/v2/text/synthesize \\\n  -H \"X-API-Key: YOUR_API_KEY\" \\\n  -H \"X-API-Secret: YOUR_API_SECRET\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"text\":\"Hello world\",\"voice\":\"US English Female\"}' \\\n  --output speech.mp3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"voice":{"type":"string"},"lang":{"type":"string"},"engine":{"type":"string","enum":["g1","g2","g3","g5","gwn","msv","oai"],"ref":"TTSService","description":"Available TTS service backends"},"name":{"type":"string"},"gender":{"type":"string","enum":["male","female"],"ref":"VoiceGender","description":"Voice gender classification"},"pitch":{"type":"number","minimum":0,"maximum":2},"rate":{"type":"number","minimum":0,"maximum":2},"volume":{"type":"number","minimum":0,"maximum":1},"format":{"type":"string","enum":["mp3","ogg","wav"],"ref":"AudioFormat","description":"Supported audio output formats"}},"required":["text"],"ref":"SynthesizeRequest","description":"Text-to-speech synthesis request"}}}},"responses":{"200":{"description":"Audio binary data (non-streaming) or an HTTP audio stream (when Accept: text/event-stream is set). Includes RV-Cached header, X-Streaming header for streaming responses, and RV-Prosody-Applied listing knobs the server applied upstream.","headers":{"RV-Prosody-Applied":{"schema":{"type":"string","description":"Comma-separated subset of {pitch, rate, volume} the server applied upstream for this call."},"required":true,"description":"Comma-separated subset of {pitch, rate, volume} the server applied upstream for this call."},"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."}},"content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"204":{"description":"No content — returned when volume=0 was requested; the server skips upstream synthesis entirely.","headers":{"RV-Prosody-Applied":{"schema":{"type":"string","description":"Always \"volume\" for 204 responses."},"required":true,"description":"Always \"volume\" for 204 responses."},"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."}}},"400":{"description":"Invalid synthesis request — missing required fields, invalid JSON, or parameter validation failure","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"401":{"description":"API key required","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"404":{"description":"No provider available for the requested engine, or no streaming provider for streaming requests","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"429":{"description":"Rate limit exceeded. The body `code` is `RATE_LIMIT_EXCEEDED` (per-minute tier limit) or `BURST_RATE_LIMIT_EXCEEDED` (per API key + client IP burst).","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer","description":"Maximum requests permitted for the authenticated key per minute."},"required":true,"description":"Maximum requests permitted for the authenticated key per minute."},"X-RateLimit-Remaining":{"schema":{"type":"integer","description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"required":true,"description":"Requests remaining in the current per-minute window; 0 once the per-minute limit is reached. Reflects the per-minute limit, not the burst check — a burst 429 may report a value above 0."},"Retry-After":{"schema":{"type":"integer","description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."},"required":true,"description":"Seconds to wait before retrying. Dynamic — varies with how far over the limit the request went; honor this header rather than assuming a fixed delay."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}},"502":{"description":"Upstream TTS provider returned an error during synthesis","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"number"},"errors":{}},"required":["message","statusCode"]}},"required":["error"],"ref":"ErrorResponse","description":"API error response"}}}}}}}},"webhooks":{}}