Complete developer guide and best practices to help you get started quickly.
Dictionaries (vocabulary vs forced replacement)
Vocabulary and forced_replacement dictionaries solve different problems. This section breaks down their characteristics, when to use each, and why one cannot replace the other.
Dictionaries come in two kinds because "helping the model understand a term and weave it into context" is a fundamentally different requirement from "this exact substring must always be replaced." The first must happen during model generation; the second must happen after generation completes. Folding them into one mechanism would sacrifice the accuracy of one. This section walks through both kinds and points out where they combine and where they conflict.
vocabulary: soft guidance
A vocabulary term is shaped as { variants: { 'zh-TW': ..., en: ..., 'ja-JP': ... } }, expressing the same concept across languages. When the input matches a term in any language, the corresponding entries in the other languages are passed to the model as preferred wording — but the model still chooses the final wording based on context.
- Use cases: brand terminology, product names, industry-specific terms, terms with an official translation.
- Preserves contextual flexibility: the model picks the most natural form (singular/plural, inflection, honorifics).
- Bidirectional: a hit in any language pulls in the corresponding entries for the others.
- Works with both translation and speech recognition endpoints, and is compatible with SSE mode.
forced_replacement: hard post-processing
A forced_replacement term is shaped as { language, from, to, case_sensitive? }, defining a substitution rule for a single language. After the model finishes generating, the system applies these rules to the output, guaranteeing that from will not appear in the final result.
- Use cases: brand spelling consistency, fixed abbreviation expansion, blocking specific words, compliance/legal-mandated term substitution.
- Absolute output guarantee: every match of from is rewritten to to.
- Single-direction, single-language: each rule belongs to one language (the language field) and only fires when the output language matches; case_sensitive defaults to false.
- **Not supported in SSE mode**: mutually exclusive with stream=true (sending both returns 400 validation_error).
Side-by-side comparison
- Intervention timing: vocabulary acts during generation; forced_replacement acts after generation.
- Output guarantee: vocabulary is preference-only; forced_replacement guarantees a 100% hit rate.
- Language scope: vocabulary is multilingual (one term spans languages); forced_replacement is single-language (one term, one language).
- SSE interaction: vocabulary is fully compatible; forced_replacement is mutually exclusive (triggers 400).
- Coexistence: JSON mode can attach both vocabulary and forced_replacement; SSE mode can only attach vocabulary.
- Update rules: vocabulary overwrites the whole `variants` map (at least 1 variant required); for forced_replacement, provide at least one of `from` / `to` / `case_sensitive`, and `language` is immutable (sending the language field on PATCH returns 400 `validation_error`).
Design trade-offs
vocabulary and forced_replacement live on entirely separate code paths; the only thing they share is the user-facing name "dictionary." The type must be chosen at creation time and cannot be changed afterwards (PATCH does not accept the type field). If a single term set needs both behaviours, create two dictionaries and attach both at once. If situations are mutually exclusive, issue separate calls with the appropriate configuration.
Common limits
- type cannot be changed after creation (PATCH does not accept the type field).
- Dictionary names must be unique within the same API key holder; duplicates return 409 duplicate_name.
- Bulk term creation is capped at 250 per batch; exceeding returns 400 bad_request (details.reason: batch_too_large).
- forced_replacement is mutually exclusive with stream=true; sending both returns 400 validation_error, and meta.forced_replacement_count is always 0 in SSE mode.
- A dictionary can only be used by the account that created it; a missing dictionary returns 404 dictionary_not_found.

