Realtime TTS-2 is live. Built for realtime conversation that feels human. Read the Realtime TTS-2 announcement

Capabilities

Custom pronunciations

Manage custom pronunciations in Portal or as named pronunciation dictionaries through the API.

Pronunciation dictionaries store custom pronunciations for reuse. Each entry contains a written word, its language, and a validated phoneme sequence. This guide covers two saved resources: the workspace dictionary managed in Portal and named dictionaries managed through the public API.

Inline pronunciation is a separate per-request feature: you replace a word in the synthesis text with /IPA/. This does not create a saved dictionary entry. The approaches below have different management and synthesis behavior.

Choose the right approach

Inline pronunciation

  • Manage in: Synthesis text, using /IPA/
  • Lifetime: One request
  • TTS behavior: TTS speaks the inline IPA at that location

Workspace pronunciation dictionary

  • Manage in: Pronunciations in Portal
  • Lifetime: Reusable within one workspace
  • TTS behavior: Portal opts eligible Playground requests in; API requests opt in with enable_custom_pronunciation: true

Named pronunciation dictionaries

  • Manage in: Pronunciation Dictionaries API
  • Lifetime: Reusable named resources within one workspace
  • TTS behavior: Select dictionaries in each TTS request, where named-dictionary synthesis is enabled for your workspace

The Custom pronunciations control inside TTS Playground is session-only. It rewrites matching terms to inline IPA before synthesis and does not add them to the workspace pronunciation dictionary.

Manage the workspace pronunciation dictionary in Portal

Portal presents the workspace's default pronunciation dictionary as individual saved entries. You do not need to create or name the dictionary.

Open Pronunciations

Open a workspace in Inworld Portal. In the workspace sidebar, under Configure, select Pronunciations.

If Pronunciations is not shown, the workspace pronunciation dictionary is not enabled.

Start a pronunciation

Select Add pronunciation, enter exactly one word, and choose the language in which the pronunciation should apply. The same normalized word and language can appear only once.

Provide the pronunciation

Select Suggest IPA to start from a generated suggestion, or enter the phoneme symbols directly. The IPA palette contains the symbols supported for the selected language.

If a microphone action is available, you can record how the word should sound and use the resulting IPA as an editable suggestion. The recording itself is not saved with the pronunciation.

Preview and save

Preview the pronunciation, adjust the phonemes if needed, and select Add. Portal validates the complete phone sequence for the selected language and may normalize supported aliases before saving it.

Use the pronunciation table to search by word or IPA, filter by language, preview an entry, edit its phone sequence, or delete it. Users without workspace asset-edit permission can view and preview entries but cannot create, edit, or delete them.

Apply the workspace pronunciation dictionary in TTS Playground

For example, save the pronunciation of Crete in the workspace dictionary and generate Welcome to Crete. in Playground. The dictionary supplies the pronunciation while you keep the original spelling in your text. With inline IPA, you would instead send Welcome to /kriːt/. directly.

Open TTS Playground

Open TTS Playground from the same workspace that owns the saved pronunciation.

Choose a TTS-2 model

Select Realtime TTS-2 or Realtime TTS-2 Flash. The workspace pronunciation dictionary is not applied to older TTS models.

Set the language

Choose a concrete language that matches the dictionary entry, such as en-US. When the Playground indicates that custom pronunciations require a specific language, leaving Language set to Auto skips the workspace dictionary.

Generate with the original spelling

Enter the normal written word and generate speech. Portal automatically enables the workspace's default pronunciation dictionary for both single-text and multi-turn TTS-2 generation.

Portal's workspace pronunciation entries are not public named dictionaries. The public list and get methods do not return the workspace-default dictionary, and Portal does not list or edit named dictionaries created through the API.

Use the workspace dictionary through the TTS API

To apply the entries saved in Portal, send enable_custom_pronunciation: true in a TTS-2 request. No dictionary ID is needed: the API key identifies the workspace. Use a Standard API key from that workspace and the same region, with an explicit language matching the saved entries.

For example, after saving an English (US) pronunciation for Crete in Portal:

json
{
  "text": "Welcome to Crete.",
  "voiceId": "Ashley",
  "modelId": "inworld-tts-2",
  "language": "en-US",
  "audioConfig": { "audioEncoding": "MP3" },
  "enable_custom_pronunciation": true
}

Send this body to Synthesize speech, authenticated with Authorization: Basic $INWORLD_API_KEY. enableCustomPronunciation is the equivalent JSON field spelling. Keep the original word in text; this is saved-dictionary lookup, not inline IPA.

SelectionWorkspace defaultAPI-created named dictionaries
Request fieldenable_custom_pronunciation: truepronunciationDictionarySettings
Managed inPortal's Pronunciations pagePublic Pronunciation Dictionaries API
IdentifierNone; selected by the API key's workspaceOne to three full returned dictionary names
Selection omitted on a new requestNo default-dictionary lookupNo named-dictionary lookup
Dictionary-loading failureBest-effort: synthesis can continue with the original textThe explicit selection reports a load error

Choose one selector. Do not send enable_custom_pronunciation together with pronunciationDictionarySettings, even when the boolean is false: both fields present is an invalid request. The workspace default cannot be selected by putting its ID into named-dictionary settings, and public dictionary CRUD still cannot read or edit Portal's saved entries.

False or omission disables the workspace default on a new synthesis request or WebSocket context. Empty dictionaries and words or languages with no matching entry leave the text unchanged. Default-dictionary rewriting must be enabled for your workspace in the region you call; HTTP success alone does not prove a replacement was applied. Unlike explicit named-dictionary load failures, an unavailable workspace default can leave speech unchanged. Saved edits may take time to reach synthesis; see Troubleshoot pronunciation differences.

Workspace defaults across synthesis transports

For synchronous, HTTP streaming and async synthesis, put the boolean at the top level of the synthesis request. For batch synthesis, put it inside each item's request; items can opt in independently. Check completed async operations and individual batch results, not just submission success.

For WebSocket synthesis, put enableCustomPronunciation inside create or updateConfig:

json
{
  "contextId": "ctx-1",
  "create": {
    "voiceId": "Ashley",
    "modelId": "inworld-tts-2",
    "language": "en-US",
    "enableCustomPronunciation": true
  }
}

On updateConfig, omission preserves the existing selection; false disables the workspace default. Finish pending text and wait for contextUpdated before sending text under the changed configuration. Use a new context when switching between workspace-default and named-dictionary modes.

Runnable Python and JavaScript pronunciation examples show how to compare baseline and dictionary-selected audio. They use your workspace API key; never send an internal workspace UUID.

Author pronunciation entries

An entry pairs one written word with a language and an ordered sequence of phone symbols. The language selects which symbols are valid; it is not inferred from the word or from a later synthesis request.

Choose a supported language

Use the language selector in Portal's Pronunciations editor to see the available pronunciation languages. After selecting a language, use its IPA palette or Suggest IPA to construct a sequence. The general TTS language list is not a pronunciation-symbol catalog: support for speaking a language does not establish which phone tokens dictionary entries accept.

For the API, send the exact canonical code in languageCode. For example, American English is en-US, not en-us, EN_US, or en. Blank values and auto are not accepted in dictionary entries. Keep the language fixed while authoring a sequence; symbols accepted for one language are not necessarily accepted for another.

Send phone tokens, not an IPA string

phoneSymbols is a JSON array with one complete phone token per element. Portal's slash-delimited display is not the API format.

InputMeaning
["k", "æ", "t"]An American English sequence with three tokens
["/kæt/"]Do not send slash delimiters; use separate phone tokens
["k æ t"]Do not combine whitespace-separated phones into one token

Do not split a sequence by Unicode character. A catalog token can contain multiple characters: if the selected language lists oʊ, send "oʊ" as one element, not "o", "ʊ". The same rule applies to affricates and length-marked vowels. Include a stress marker such as ˈ only if the selected language supports it, and preserve its token boundary from the palette or suggestion.

Prefer the canonical symbols shown in the selected language's palette. Portal's typing shortcuts are an editing convenience, not a list of API-accepted aliases. The service can normalize supported aliases during validation; use the returned phoneSymbols as the saved representation instead of expecting a byte-for-byte echo of the input.

Verified American English examples

These sequences have passed pronunciation API integration tests for en-US. They are a tested subset, not a complete phone inventory or a claim that other languages are unsupported.

Written wordlanguageCodephoneSymbols
Caten-US["k", "æ", "t"]
Shipen-US["ʃ", "ɪ", "p"]

For example, send this body to Create a pronunciation dictionary:

json
{
  "displayName": "Pronunciation examples",
  "pronunciations": [
    {
      "displayHeadword": "Cat",
      "languageCode": "en-US",
      "phoneSymbols": ["k", "æ", "t"]
    },
    {
      "displayHeadword": "Ship",
      "languageCode": "en-US",
      "phoneSymbols": ["ʃ", "ɪ", "p"]
    }
  ]
}

The dictionary API has no public language-catalog, phone-catalog, or standalone validation method. Create and update validate the submitted entries atomically. For other languages or symbols, start with Portal's language-specific palette and validate the completed entries through create or update; do not assume arbitrary IPA is accepted.

Correct an invalid entry

Changing the first example's languageCode to en-us makes the request invalid. Create or update returns HTTP 400 Bad Request / gRPC INVALID_ARGUMENT (code 3) with the message Pronunciation request is invalid. Its google.rpc.BadRequest detail identifies the field:

json
{
  "field": "pronunciation_dictionary.pronunciations[0].language_code",
  "description": "Invalid value"
}

Field paths in error details use protobuf-style snake_case even though request bodies use camelCase. The index is zero-based. Change languageCode back to en-US and resubmit. A phone validation error can similarly identify phone_symbols or a particular element such as phone_symbols[0]; recheck that token against the selected language's palette. A failed validation does not partially create or replace the dictionary.

Manage named dictionaries through the API

The Pronunciation Dictionaries API lets backend applications create and manage reusable, workspace-owned collections of pronunciation entries. A dictionary is one complete resource: create it in one request, retrieve or list it with all entries, and update its metadata or contents atomically.

Use a Standard API key for the workspace that owns the dictionaries. Pronunciation dictionaries use the existing Voices permission: Voices Read permits list and get, while Voices Write permits create, update, and delete as well as reads. There is no separate Custom Pronunciations permission.

Dictionary management and named-dictionary synthesis have separate availability. The synthesis workflow below requires named-dictionary synthesis to be enabled for your workspace in the region you call. Successful dictionary creation alone does not establish that synthesis is enabled. Keep dictionary management and synthesis in the same region.

The API intentionally has no per-entry create, update, delete, import, or batch methods. A synchronous create or update accepts the complete dictionary, up to the limits below. To change entries, send the desired complete pronunciations list to Update a pronunciation dictionary. Entries omitted from that list are deleted; an empty list clears the dictionary.

Select named dictionaries for synthesis

Pass pronunciationDictionarySettings in your TTS request. Each dictionary value is the complete name returned by create, get, or list—not the display name or just the dictionary ID.

  • Select one to three distinct dictionaries from the workspace that owns your API key.
  • Dictionaries are evaluated in request order. When two dictionaries define the same language and normalized word, the first dictionary wins.
  • Use inworld-tts-2 and a concrete supported language matching your entries, such as en-US. Older TTS models reject named-dictionary selection. Omitting the language or using AUTO depends on separate automatic-language availability; use an explicit language for this workflow.
  • Keep the original spelling in text. Entries for other languages or words that do not match do not change that text.
  • Omitting pronunciationDictionarySettings means no named-dictionary selection. It does not select the Portal workspace dictionary automatically. A present settings object must contain at least one dictionary; {} and {"dictionaries": []} are invalid.
  • Omit enable_custom_pronunciation entirely when using named settings. Sending it as false still conflicts with explicit named selection.

Synthesize with a saved dictionary

For a dictionary containing an en-US pronunciation of Crete, send this body to Synthesize speech at POST https://api.inworld.ai/tts/v1/voice. Authenticate with your workspace's Standard API key using Authorization: Basic $INWORLD_API_KEY. Resource names containing placeholders such as {workspace_id} and {dictionary_id} are illustrative: replace the entire value with the complete name returned when creating or retrieving your dictionary.

json
{
  "text": "Welcome to Crete.",
  "voiceId": "Dennis",
  "modelId": "inworld-tts-2",
  "language": "en-US",
  "audioConfig": { "audioEncoding": "MP3" },
  "pronunciationDictionarySettings": {
    "dictionaries": [
      { "dictionary": "workspaces/{workspace_id}/pronunciationDictionaries/{dictionary_id}" }
    ]
  }
}

To give a project dictionary priority over a shared dictionary, put the project dictionary first in dictionaries. Both must belong to the same workspace. Selection references the saved dictionaries, not a fixed version; keep them available until synthesis completes.

Choose a synthesis transport

Each transport uses the same pronunciationDictionarySettings object:

TransportWhere to put the settingsWhere to check failures
SynchronousTop-level request bodyHTTP status and error body
HTTP streamingTop-level request bodyInitial HTTP status and any error object in the stream; HTTP 200 alone does not mean the whole stream succeeded
AsyncTop-level request bodySubmission response, then the operation's error when done is true
BatchEach item's requestSubmission response, operation error, and each item's error in the downloaded results file
WebSocketcreate or updateConfigPer-context result.status and connection-level errors

Each transport retains its own model, input-size, and audio-format requirements. An accepted async or batch operation does not guarantee that its dictionaries can be used: ownership and dictionary loading are checked during execution. A completed batch can contain failed items, so inspect every result. Job errors do not necessarily use the same codes as synchronous failures.

Change a WebSocket selection

Set the selection when creating the context. It applies to subsequent text sent to that context:

json
{
  "contextId": "ctx-1",
  "create": {
    "voiceId": "Dennis",
    "modelId": "inworld-tts-2",
    "language": "en-US",
    "pronunciationDictionarySettings": {
      "dictionaries": [
        { "dictionary": "workspaces/{workspace_id}/pronunciationDictionaries/{dictionary_id}" }
      ]
    }
  }
}

An updateConfig message containing pronunciationDictionarySettings replaces the whole selection; it does not append dictionaries. Other configuration fields can be omitted to preserve them. In this example, use the complete returned name of the replacement dictionary from the same workspace. Wait for contextUpdated before sending text that should use the replacement:

json
{
  "contextId": "ctx-1",
  "updateConfig": {
    "pronunciationDictionarySettings": {
      "dictionaries": [
        { "dictionary": "workspaces/{workspace_id}/pronunciationDictionaries/{replacement_dictionary_id}" }
      ]
    }
  }
}

Omitting pronunciationDictionarySettings from an update preserves the previous selection. An empty object or empty list is rejected, not treated as a reset. To continue without named dictionaries, close the selected context and create a new one without the field. Finish pending text under the old configuration before changing selection.

Handle selection errors

For synchronous synthesis, these errors have the following meanings:

HTTP statusgRPC codeMeaning
400INVALID_ARGUMENT (3)Malformed, duplicate, too many or too few dictionary names, or a dictionary from another workspace
400FAILED_PRECONDITION (9)Named dictionaries are unavailable for the workspace, model, or language configuration
404NOT_FOUND (5)A selected dictionary does not exist or is no longer accessible
503UNAVAILABLE (14)A selected dictionary could not be loaded because a dependency is unavailable
504DEADLINE_EXCEEDED (4)Selected dictionary loading exceeded its deadline

A missing selected dictionary or dictionary-load failure is reported as an error, rather than silently dropping that selection. This is not a guarantee that every word changes: entries still need to match the text and language.

On an established JSON WebSocket, a context error is reported in result.status, for example:

json
{
  "result": {
    "contextId": "ctx-1",
    "status": {
      "code": 5,
      "message": "A selected pronunciation dictionary was not found.",
      "details": []
    }
  }
}

Also handle terminal connection-level error messages. For HTTP streaming, errors after streaming starts are reported in the response stream and cannot change an already-sent HTTP status.

Dictionary limits

ValueLimit
Dictionary display name1–64 Unicode code points; cannot be blank
Entries per dictionary0–1,000
Encoded dictionary size512 KiB
HeadwordOne replaceable lexical token, up to 128 Unicode code points
Phone sequence1–64 supported tokens or aliases
Phone tokenUp to 16 Unicode code points
Dictionaries per list page5

See Author pronunciation entries for language selection, token formatting, verified examples, and validation errors.

Atomic validation and concurrency

Create and update validate every submitted entry before committing any change. Duplicate language/headword identities are rejected after language-aware lexical canonicalization, so spelling or casing variants that resolve to the same token cannot coexist in one dictionary.

Every dictionary response contains an etag. Include the current value when updating or deleting a dictionary. A stale value returns HTTP 409 Conflict instead of overwriting a concurrent change; retrieve the resource again, reconcile the new state, and retry.

Run a complete API example

This Python example creates a temporary named dictionary, reads and renames it, synthesizes speech, replaces its entries, synthesizes again, and deletes the dictionary. It deliberately makes Cat sound like red first, then restores cat, so you can hear the change without changing the input text.

Use a workspace and region where named-dictionary synthesis is enabled. Successful dictionary creation alone does not establish synthesis availability.

Set up your key and workspace

  1. Create a Standard API key with Voices Write. One INWORLD_API_KEY is used for dictionary management and synthesis. An existing key with that permission also works.
  2. In the same workspace, open API Keys and copy Workspace ID at the top of the page. Use that value below, without a workspaces/ prefix. You do not need an internal workspace UUID.
  3. Choose the regional API endpoint for that workspace. Keep every dictionary and synthesis request on the same endpoint.
  4. Install Python 3.10 or later and requests, then set the two non-secret values:
bash
python -m pip install requests
export INWORLD_API_BASE_URL="https://api.inworld.ai"
export INWORLD_WORKSPACE_ID="YOUR_WORKSPACE_ID"

Save the following code as pronunciation_example.py and run python pronunciation_example.py. If INWORLD_API_KEY is not already in your environment, the script prompts for it without echoing it. Paste the copied key unchanged: Basic authentication does not require encoding it again.

This example makes two short, billable synthesis requests and writes before.mp3 and after.mp3 to a new temporary directory. It deletes its dictionary before exiting; the audio files remain for you to play. It does not edit Portal's workspace dictionary or any pre-existing named dictionary.

python
import base64
import getpass
import os
import tempfile
from pathlib import Path
from urllib.parse import quote
from uuid import uuid4

import requests

base_url = os.environ["INWORLD_API_BASE_URL"].rstrip("/")
workspace_id = quote(os.environ["INWORLD_WORKSPACE_ID"], safe="")
api_key = os.environ.get("INWORLD_API_KEY") or getpass.getpass("Inworld API key: ")
collection = f"/pronunciations/v1/workspaces/{workspace_id}/pronunciationDictionaries"
display_name = f"Pronunciation demo {uuid4().hex}"
renamed = f"{display_name} renamed"
output_dir = Path(tempfile.mkdtemp(prefix="pronunciation-example-"))
name = None

red_entry = {
    "displayHeadword": "Cat",
    "languageCode": "en-US",
    "phoneSymbols": ["ɹ", "ɛ", "d"],
}
cat_entry = {**red_entry, "phoneSymbols": ["k", "æ", "t"]}

session = requests.Session()
session.headers["Authorization"] = f"Basic {api_key}"


def request(method, path, *, missing_ok=False, **kwargs):
    response = session.request(
        method, f"{base_url}{path}", timeout=30, allow_redirects=False, **kwargs
    )
    if missing_ok and response.status_code == 404:
        return None
    if not 200 <= response.status_code < 300:
        raise RuntimeError(f"{method} {path}: HTTP {response.status_code}")
    return response.json()


def synthesize(filename):
    result = request("POST", "/tts/v1/voice", json={
        "text": "The Cat sat quietly.",
        "voiceId": "Ashley",
        "modelId": "inworld-tts-2",
        "language": "en-US",
        "seed": 101,
        "audioConfig": {"audioEncoding": "MP3"},
        "pronunciationDictionarySettings": {
            "dictionaries": [{"dictionary": name}]
        },
    })
    audio = base64.b64decode(result["audioContent"], validate=True)
    if not audio:
        raise RuntimeError("Synthesis returned empty audio")
    path = output_dir / filename
    path.write_bytes(audio)
    print(f"Saved {path}")


print(f"Creating temporary dictionary: {display_name}")
try:
    dictionary = request("POST", collection, json={
        "displayName": display_name,
        "pronunciations": [red_entry],
    })
    name = dictionary["name"]  # Keep the full server-assigned resource name.
    path = f"/pronunciations/v1/{name}"
    print(f"Created {name}")
    dictionary = request("GET", path)
    original_entries = dictionary["pronunciations"]

    # Rename only: the explicit mask preserves the entries.
    dictionary = request("PATCH", path, params={"updateMask": "displayName"}, json={
        "displayName": renamed,
        "etag": dictionary["etag"],
    })
    if dictionary["pronunciations"] != original_entries:
        raise RuntimeError("Renaming unexpectedly changed the entries")
    synthesize("before.mp3")  # Expected: "The red sat quietly."

    # Replace the entire list, using the latest returned etag.
    dictionary = request("PATCH", path, params={"updateMask": "pronunciations"}, json={
        "pronunciations": [cat_entry],
        "etag": dictionary["etag"],
    })
    synthesize("after.mp3")  # Expected: "The cat sat quietly."
finally:
    try:
        if name is not None:
            # Re-read only this run's resource, including after a failed update.
            latest = request("GET", path, missing_ok=True)
            if latest is not None:
                if (latest["displayName"] not in (display_name, renamed)
                        or latest["pronunciations"] not in ([red_entry], [cat_entry])):
                    raise RuntimeError("Dictionary changed unexpectedly; inspect before deleting")
                request("DELETE", path, params={
                    "etag": latest["etag"], "allowMissing": "true"
                })
            print(f"Deleted or already absent: {name}")
        else:
            print(f"No resource name received. Reconcile before rerunning: {display_name}")
    except Exception:
        print(f"Cleanup not confirmed. Inspect this resource before retrying: {name}")
        raise
    finally:
        session.close()

Play both files from the printed directory. The same text, voice, model, and language are used for both requests; only the stored phone sequence changes. The example selects the dictionary by its returned name, not its display name, and does not send an ETag to TTS.

Update and retry safely

Use explicit update masks: displayName renames without changing entries, and pronunciations replaces the complete entry list without renaming. To clear all entries, send {"pronunciations": [], "etag": "CURRENT_ETAG"} with updateMask=pronunciations. Never send just the one changed entry unless it should be the dictionary's only entry.

The example does not retry mutations automatically. A timeout or lost response does not prove that a write failed:

Uncertain resultReconcile before retrying
Create returned no resource nameList dictionaries, following nextPageToken until empty. Use the printed unique display name to find candidates, then inspect their names and contents. Do not blindly repeat create or delete resources based only on a matching display name.
Update response was lostGet the known resource and compare its metadata and complete entries with the intended change. If another update is needed, reconcile first and use the current ETag. The example instead attempts to clean up its throwaway dictionary and exits.
Delete response was lostGet the same resource. HTTP 404 confirms it is gone. Otherwise inspect its current state before another conditional delete. allowMissing=true accepts an already-deleted resource but still requires a non-empty ETag and does not bypass a stale-ETag conflict.

Method references: Create, Get, List, Update, and Delete.

Understand which words match

Saved dictionaries match complete, language-aware word tokens, not arbitrary substrings. The pronunciation stage compares each token's normalized spelling and resolved language with the saved entry. It leaves text outside the matched token unchanged.

CaseWhat to expect
A complete wordAn en-US entry for Cat can change Cat in The Cat sat quietly. when that dictionary is selected and the language matches.
A substring inside another tokenA cat entry is not a search-and-replace rule for the letters inside scatter.
Punctuation around a wordPunctuation outside a matched token is preserved. Token boundaries depend on the language; do not assume every punctuation mark splits a word.
Inflections or phrasesDo not assume one entry covers plural forms or a multi-word phrase. Add and test the individual word forms you need; each entry must validate as one lexical token.
Casing, accents, or joined spellingsNormalization is language-specific. Do not assume Cat and CAT, café and cafe, or co-op and coop are equivalent. Start with the saved spelling and test the variants you need.
Existing inline IPAExplicit /IPA/ text is preserved by dictionary rewriting. It is not looked up again as a written word.

Use an explicit synthesis language matching the saved entry while testing. Language-aware normalization is not a promise that related locales or visually similar Unicode spellings are interchangeable. If a spelling variant is rejected as a duplicate during saving, it already resolves to the same word identity for that language; do not add a second entry to work around the error.

Where named-dictionary synthesis is enabled, dictionaries are combined in selection order. If two selected dictionaries define the same normalized word and resolved language, the first wins. Later dictionaries can still supply other words. Existing inline IPA takes precedence over both.

Understand when saved edits take effect

Saving an entry and applying it to new speech are separate steps. The two saved dictionary types have different refresh behavior:

Dictionary typeAfter a successful save
Portal workspace dictionarySynthesis can still use an older cached copy. The standard refresh interval is five minutes. After that interval, a request can start a background refresh while still using the old contents. If refresh fails, older contents can remain in use. Five minutes is not a maximum propagation delay.
Named API dictionarySelected dictionaries are read during synthesis instead of using the workspace dictionary cache. Start a new synthesis after saving to test the new contents; there is no workspace-cache refresh interval to wait for.

A browser reload does not force the workspace dictionary's synthesis cache to refresh. If the workspace dictionary cannot be loaded and no usable cached copy is available, synthesis can continue without applying its entries.

Saving does not change audio that has already been generated or guarantee an update to a request already in progress. Named selections refer to saved resources, not pinned versions: a queued job reads them during execution, and later WebSocket synthesis can read newer contents while retaining the same selected names. Avoid editing or deleting selected dictionaries while a job or conversation that needs consistent pronunciations is running.

Troubleshoot pronunciation differences

Preview sounds right, but Playground does not

Preview or Play on the Pronunciations page sends the phonemes directly as inline IPA. It checks their sound, not whether the written word matches, the entry was saved, or the workspace dictionary has refreshed. An editor preview can also use unsaved values.

To test the saved workspace dictionary:

  1. Confirm the entry is saved in the intended workspace, with the expected word, language, and phonemes.
  2. Open normal TTS Playground in that same workspace. Select Realtime TTS-2 and the entry's explicit language, such as en-US. Do not rely on automatic-language availability for this diagnostic.
  3. If the session-only Custom pronunciations control is shown, leave its temporary entries empty for this test. That control rewrites text to inline IPA before synthesis and can hide whether the saved dictionary was applied.
  4. Enter a short sentence using the saved word's original spelling, without /IPA/, and select Generate for a new request. Replaying existing audio does not test the edit.
  5. If a recent edit is not reflected, allow for the workspace dictionary's asynchronous refresh, then generate again. If the difference persists, check the symptoms below.

Normal Playground generation uses the workspace dictionary, not named dictionaries created through the API. Test a named dictionary through a synthesis request that explicitly selects its complete resource name, in a workspace and region where named-dictionary synthesis is enabled.

Check the symptom

SymptomWhat to check
Speech succeeds but the word is unchangedConfirm the dictionary type and selection, original spelling, explicit language, and TTS-2 model. For named dictionaries, check selection order as well. Successful speech alone does not prove that an entry matched or rewriting completed.
An API-created entry is absent from PortalPortal's workspace dictionary and public named dictionaries are separate. Use Get a dictionary to inspect a named resource; it does not retrieve Portal's workspace-default dictionary.
Dictionary creation works but named synthesis is rejectedManagement and synthesis have separate availability. Use the same workspace and region, an enabled synthesis model, and an explicit supported language. Creating a dictionary does not enable named synthesis.
A request is unauthorized or forbiddenCheck the key belongs to the dictionary's workspace and region. Dictionary reads use Voices Read; mutations require Voices Write. Verify permissions before replacing the key.
A selected named dictionary is missing or cannot loadFor synchronous synthesis, a missing selection returns HTTP 404; load failures can return 503 or 504. Confirm the saved resource still exists using the same workspace and region. A timeout or dependency error is not fixed by changing its phones.
A streaming response or background job was accepted but no usable audio arrivesInspect errors in the stream, WebSocket context, completed async operation, or individual batch result. An initial HTTP 200 or accepted job does not prove synthesis completed; error codes can differ by transport.

For a persistent problem, record the request ID if present, time, region, model, explicit language, dictionary resource name, and whether a fresh Generate request or only Preview reproduced it. Include the error response when available, but never include your API key.