> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inworld.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Approve a localization

> Confirm one candidate from a [Localize a voice](https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/localize-voice.md) response. The chosen candidate becomes the voice's prompt for the target language, and the draft is deleted. Approving replaces any existing prompt the voice had for that language.

<Tip>
**Short URL Path:** You can omit `/workspaces/{workspace}` from the path. The service then derives the workspace from your API key. The full path `/voices/v1/workspaces/{workspace}/voices/{voice}:approveLocalization` is still supported.
</Tip>

This is the second call in the localization flow. Start with [Localize a voice](https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/localize-voice.md). The `{voiceId}` in the URL must identify the voice the draft was created from. The service reads the target language from the draft; you do not pass it in this call.

<Note>
**Approve is one-shot.** It sets the chosen candidate as the voice's prompt for that language, replaces any prompt the voice already had for that language, and deletes the draft. Changing your mind afterward means running localize again. There is no way to remove a single language from a voice — re-approving a language replaces its prompt, and only deleting the voice removes everything.
</Note>

<Note>
A `404 NOT_FOUND` on this call usually means the candidate was superseded — localizing the same voice and language again replaces the draft's candidates. Re-run localize and approve from the newest response. A `429` (rate limit) is harmless here: the draft still exists, so the call can simply be retried.
</Note>

After approval, generate speech in the new language by passing the same `voiceId` with the target `language` — the voice now uses its localized prompt for that language. See [Languages](https://docs.inworld.ai/tts/capabilities/multilingual.md#specifying-a-language).

## API reference

**Endpoint:** `POST https://api.inworld.ai/voices/v1/voices/{voiceId}:approveLocalization`

### Authorization

- `Authorization` (string; required) — Your [API key](../../../api-reference/introduction). 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](../../../developer-tools/inworld-cli): `inworld workspace add-key`.

### Path parameters

- `voiceId` (string; required) — Voice ID of the voice the draft was created from. Must match the voice used in the localize call.

### Request body

Content type: `application/json`

- `draftVoiceId` (string; required) — The `draftVoiceId` returned by the localize call.
- `candidateId` (string; required) — The chosen candidate's `candidateId` from the same localize response.

#### Request example

```json
{
  "draftVoiceId": "your_workspace_id__localize-70f3d90e",
  "candidateId": "f81e64399e72"
}
```

### Response

Status: `200`. Content type: `application/json`.

- `voiceId` (string) — Voice ID. SYSTEM voices use a simple name (e.g. `Alex`); IVC voices are workspace-prefixed (`{workspace}__{voice}`).
- `langCode` (enum<string>; options: "EN_US", "ZH_CN", "KO_KR", "JA_JP", "RU_RU", "AUTO", "IT_IT", "ES_ES", "PT_BR", "DE_DE", "FR_FR", "AR_SA", "PL_PL", "NL_NL", "HI_IN", "HE_IL") — Primary language of the voice in upper-snake format (e.g. `EN_US`). Note that when filtering via `lang_code`, you can pass BCP-47 (`en-US`), underscore form (`en_US`), or a language prefix (`en`) — but the response always returns upper-snake.
- `languageCode` (string) — Canonical wire form of the voice's primary language as a single string (e.g. `en-US`, `es-MX`, `vi`). Populated on every read alongside the legacy `langCode` enum. Immutable after creation.
- `displayName` (string) — The human-readable name shown anywhere the voice is listed or selected.
- `description` (string) — Longer blurb that explains the voice's tone, accent, use cases, or other relevant attributes.
- `tags` (string[]) — Free-form labels for filtering, grouping, and discovery (e.g. `british`, `calm`).
- `name` (string) — Resource identifier. Workspace voices use workspaces/{workspace}/voices/{voice}; system and shared voices may use a bare identifier. For synthesis and audio previews, use voiceId unchanged instead of constructing a resource path.
- `source` (enum<string>; options: "SYSTEM", "IVC", "TVD", "PVC", "OTHER") — Voice provenance: SYSTEM (Inworld system voice), IVC (instant voice clone), TVD (voice design), PVC (professional or supplied custom voice), or OTHER. Treat provenance separately from community-sharing and ownership metadata.
- `gender` (enum<string>; options: "male", "female", "neutral", "") — Voice gender (`male`, `female`, `neutral`). Empty string if unspecified. Voices with no gender are excluded when filtering with an explicit `gender =` predicate.
- `ageGroup` (enum<string>; options: "young", "middle_aged", "elderly", "") — Age group of the voice (`young`, `middle_aged`, `elderly`). Empty string if unspecified.
- `categories` (enum<string>[]) — Use-case categories the voice belongs to. Filterable with the `:` (has) operator.
  
  Supported values: `companions`, `enterprise`, `education_training`, `developer_assistants`, `healthcare`, `interactive_media`.
- `promptLanguages` (string[]) — Every language the voice can speak, in BCP-47 format (e.g. `en-US`): its source language first, then each language it has been localized to. A voice without localizations has one entry; a voice whose language is detected automatically has none. Returned by both listing and getting a voice.
- `owned` (boolean) — For a community voice result, whether the calling workspace owns the voice. Interpret alongside community-sharing metadata; this field is not a general test for whether synthesis is permitted.

### Response examples

#### 200: approved

```json
{
  "name": "workspaces/your_workspace_id/voices/my_voice",
  "voiceId": "your_workspace_id__my_voice",
  "langCode": "EN_US",
  "displayName": "My Voice",
  "source": "IVC",
  "promptLanguages": [
    "en-US",
    "de-DE"
  ]
}
```

#### default: staleCandidate

```json
{
  "code": 5,
  "message": "candidate not found",
  "details": []
}
```

### Code examples

#### cURL

```bash
curl --location 'https://api.inworld.ai/voices/v1/voices/<voice-id>:approveLocalization' \
--header "Authorization: Basic $INWORLD_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
  "draftVoiceId": "your_workspace_id__localize-70f3d90e",
  "candidateId": "f81e64399e72"
}'
```

#### Python

```python
import requests

voice_id = "<voice-id>"
url = f"https://api.inworld.ai/voices/v1/voices/{voice_id}:approveLocalization"
headers = {
    "Authorization": "Basic <api-key>",
    "Content-Type": "application/json"
}
payload = {
    "draftVoiceId": "your_workspace_id__localize-70f3d90e",
    "candidateId": "f81e64399e72"
}

response = requests.post(url, json=payload, headers=headers)
print(response.json()["promptLanguages"])
```

#### JavaScript

```javascript
const voiceId = '<voice-id>';
const url = `https://api.inworld.ai/voices/v1/voices/${voiceId}:approveLocalization`;

const response = await fetch(url, {
  method: 'POST',
  headers: {
    'Authorization': 'Basic <api-key>',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    draftVoiceId: 'your_workspace_id__localize-70f3d90e',
    candidateId: 'f81e64399e72',
  }),
});

const voice = await response.json();
console.log(voice.promptLanguages);
```
