curl --location 'https://api.inworld.ai/voices/v1/voices/<voice-id>:localize' \
--header "Authorization: Basic $INWORLD_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"targetLanguage": "de-DE"
}'import base64
import requests
voice_id = "<voice-id>"
url = f"https://api.inworld.ai/voices/v1/voices/{voice_id}:localize"
headers = {
"Authorization": "Basic <api-key>",
"Content-Type": "application/json"
}
payload = {"targetLanguage": "de-DE"}
response = requests.post(url, json=payload, headers=headers)
data = response.json()
# Save each candidate preview to a WAV file so you can listen
for c in data["candidates"]:
with open(f"candidate_{c['candidateId']}.wav", "wb") as f:
f.write(base64.b64decode(c["previewAudio"]))
print(data["draftVoiceId"], [c["candidateId"] for c in data["candidates"]])const voiceId = '<voice-id>';
const url = `https://api.inworld.ai/voices/v1/voices/${voiceId}:localize`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Basic <api-key>',
'Content-Type': 'application/json',
},
body: JSON.stringify({ targetLanguage: 'de-DE' }),
});
const data = await response.json();
console.log(data.draftVoiceId, data.candidates.map((c) => c.candidateId));{
"draftVoiceId": "your_workspace_id__localize-70f3d90e",
"candidates": [
{
"candidateId": "293127bb4b69",
"engine": "engine_b",
"previewAudio": "<base64-wav-bytes>",
"transcription": "Hallo Welt"
},
{
"candidateId": "f81e64399e72",
"engine": "engine_a",
"previewAudio": "<base64-wav-bytes>",
"transcription": "Hallo Welt"
}
]
}{
"code": 3,
"message": "This voice has 20 languages and the maximum is 20",
"details": []
}Localize a voice
Generate localized prompt candidates that make an existing voice speak a target language while keeping the same speaker identity. Returns multiple candidates on a draft; listen to them and confirm your pick with Approve a localization. Unapproved drafts expire after 3 days.
curl --location 'https://api.inworld.ai/voices/v1/voices/<voice-id>:localize' \
--header "Authorization: Basic $INWORLD_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"targetLanguage": "de-DE"
}'import base64
import requests
voice_id = "<voice-id>"
url = f"https://api.inworld.ai/voices/v1/voices/{voice_id}:localize"
headers = {
"Authorization": "Basic <api-key>",
"Content-Type": "application/json"
}
payload = {"targetLanguage": "de-DE"}
response = requests.post(url, json=payload, headers=headers)
data = response.json()
# Save each candidate preview to a WAV file so you can listen
for c in data["candidates"]:
with open(f"candidate_{c['candidateId']}.wav", "wb") as f:
f.write(base64.b64decode(c["previewAudio"]))
print(data["draftVoiceId"], [c["candidateId"] for c in data["candidates"]])const voiceId = '<voice-id>';
const url = `https://api.inworld.ai/voices/v1/voices/${voiceId}:localize`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Basic <api-key>',
'Content-Type': 'application/json',
},
body: JSON.stringify({ targetLanguage: 'de-DE' }),
});
const data = await response.json();
console.log(data.draftVoiceId, data.candidates.map((c) => c.candidateId));{
"draftVoiceId": "your_workspace_id__localize-70f3d90e",
"candidates": [
{
"candidateId": "293127bb4b69",
"engine": "engine_b",
"previewAudio": "<base64-wav-bytes>",
"transcription": "Hallo Welt"
},
{
"candidateId": "f81e64399e72",
"engine": "engine_a",
"previewAudio": "<base64-wav-bytes>",
"transcription": "Hallo Welt"
}
]
}{
"code": 3,
"message": "This voice has 20 languages and the maximum is 20",
"details": []
}/workspaces/{workspace} is no longer required in the path for simplicity and clarity. When omitted, the workspace is derived from your API key. The previous URL with the full path /voices/v1/workspaces/{workspace}/voices/{voice}:localize would continue to be supported.- Localize (this endpoint) — generates localized prompt candidates and parks them on a hidden draft.
- Approve — you listen to the candidates, pick one, and it becomes the voice’s prompt for that language.
previewAudio and choose by ear.
candidateCount to keep the response small when you don’t need every option. Candidates are ordered by historical approval rates measured across all languages, so asking for fewer gives you the engine that performs best on average — not necessarily the best one for your target language. When quality in a specific language matters, request more candidates and compare.- Drafts expire. An unapproved draft is cleaned up after 3 days. Drafts don’t appear in your voice listings.
- Re-localizing the same voice and language replaces the draft’s candidates — candidate IDs from an earlier response stop resolving. Always approve from the most recent localize response.
- Language limit. A voice can hold at most 20 languages (as reported by
promptLanguages). Localizing a voice at the cap is rejected with400 INVALID_ARGUMENTbefore any synthesis runs. - Region required.
deis rejected wherede-DEis accepted. See Languages for the supported catalog.
429 means the per-tier rate limit was exceeded — safe to retry after backing off.Authorizations
Your API key. Read permissions are required for GET endpoints. Write permissions are required for POST, PATCH, and DELETE endpoints.
For Basic authentication, please populate Basic $INWORLD_API_KEY. You can create a key in one command with the Inworld CLI: inworld workspace add-key.
Path Parameters
Voice ID of the voice to localize. Expected format: {workspace}__{voice}.
[^/]+Body
Request message for LocalizeVoice custom method.
The language to localize the voice into, as a BCP-47 code with a region — e.g. de-DE, es-MX, pt-BR. Matching is case- and separator-insensitive (es_MX and ES-mx are accepted). A bare language code with no region (de) is rejected, and auto is not supported. See Languages for the supported catalog.
Optional pre-translated text in the target language, up to 200 characters. When supplied, the automatic translation step is skipped and the candidates speak this text instead.
Optional. Caps how many candidates are returned, from 1 to 3. Omit (or send 0) to get one candidate per available synthesis engine, which is the default.
Fewer candidates may come back than you ask for: the number of available engines sets the ceiling, and candidateCount can only lower it, never raise it.
Candidates are kept in a fixed order derived from historical approval rates across all languages. That ranking is global, and the strongest engine for any one language can differ from it — so a low candidateCount gives you the historically best-performing engine rather than the best candidate for your particular target language. Request more candidates when localization quality for a specific language matters.
Response
A successful response.
Response message for LocalizeVoice.
Identifier of the draft holding the candidates. Pass it to Approve a localization. Drafts are not listed among your voices and expire after 3 days if never approved.
Localized prompt candidates, one per synthesis engine. Listen to each preview and pick the one that sounds most natural and native.
Show child attributes
Show child attributes