REST API

Glossary Validation API

POST /api/v1/glossary/validate

Description

Checks a glossary (terminology, fuzzy correction, translation dictionary) for format problems and internal conflicts before it is saved.

Most glossary problems produce no error message at all — they simply yield unexpected results, silently, during recording or translation. For example, when an incorrect variant is also the correct term of another rule, saying that word normally gets it rewritten into something else. This endpoint surfaces problems of that kind at the moment the user presses Save, along with the index of each offending entry, so a management UI can flag the exact item.

When to use it: from your glossary management UI, before saving. It is not necessary to call it before every recording or audio import.

Note: This endpoint validates the glossary itself. Passing validation does not guarantee that audio import will accept the same glossary — import applies stricter rules.

Host

Use the same domain you connect to over WebSocket, swapping the wss:// scheme for https://. This endpoint is served by the realtime service domain, which may differ from the domain serving the other REST endpoints; use the connection settings you were issued.

# If your WebSocket connection is wss://<realtime-host>/ws
curl -X POST "https://<realtime-host>/api/v1/glossary/validate"

Authentication

Header: X-API-Key (see Authentication)

Billing

Free. No points are deducted, no task or recording is created, and no glossary settings are written — it is a pure check.

Rate Limit

120 requests per minute per API Key, as a separate quota counted independently of the other REST endpoints.

Responses that got past authentication always carry the headers below (the quota is counted per API Key, and on an authentication failure the key is not yet known, so 401 and 403 responses do not carry them). When the limit is exceeded the endpoint returns HTTP 429 with Retry-After:

HeaderDescription
X-RateLimit-LimitRequests allowed per minute
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetSeconds until the current window ends
Retry-AfterProvided on 429 only; the recommended wait in seconds

Request Size Limit

A single request body is limited to 2 MB (2,097,152 bytes) by default. A larger body returns HTTP 413 config_payload_too_large, with details.max_bytes carrying the limit currently in effect.

This limit may change. Always read details.max_bytes from the response rather than hard-coding a value.

If a complete glossary exceeds the limit, it can be split across two requests, but not arbitrarily:

RequestContentsNotes
Firstterminology + fuzzy_correctionMust stay together. The conflicts that span these two sections (variant_shadows_term, homophone_conflict) can only be found when both are present in the same request
Secondtranslation_dictCan be sent on its own. The translation dictionary takes part in no cross-section conflict detection

Note: When terminology and fuzzy_correction are sent separately, variant_shadows_term and homophone_conflict are not detected, and the response gives no indication of it — has_conflicts still comes back false. If you use this endpoint as a save-time gate, keep those two sections in the same request.

Request Parameters

Every field is optional — whatever you send is what gets validated.

ParameterTypeRequiredDescription
terminologyobjectNoTerminology, in the same format as config (language code → array of terms)
fuzzy_correctionobjectNoFuzzy correction, in the same format as config
translation_dictobjectNoTranslation dictionary, in the same format as config (the language code is the target language)
check_homophonesbooleanNoWhether to check for homophone conflicts. Defaults to true. See below
transcription_languagesarrayNoThe source languages to check. When omitted, they are derived from the glossary's own language codes
translation_languagesarrayNoReserved field; currently does not affect the result (still subject to the language-code count limit)

Fields other than those listed above are ignored. If your glossary management interface holds a complete config object, you can send it as-is without stripping extra fields.

For the detailed format of the three glossary blocks, see the Terminology Guide.

check_homophones: Editing and Saving Are Checked Differently

Of the eight conflict types, only homophone conflicts (homophone_conflict) require an additional pronunciation comparison; the rest are plain string comparisons that complete in milliseconds.

  • Live checks while the user edits → send false; safe to call frequently
  • When the user actually saves → omit it or send true for the full check

Important: The homophone check applies to Chinese only, and may not finish while the service is busy. homophones_checked in the response is what tells these two situations apart:

  • homophones_checked: true → homophone conflicts have been fully checked
  • homophones_checked: false → the check did not complete; this does not mean there are no conflicts

A save gate that looks only at has_conflicts lets an unchecked glossary through whenever homophones_checked is false.

Request Example

curl -X POST "https://<realtime-host>/api/v1/glossary/validate" \
  -H "X-API-Key: vas_aB3dE5fG7hI9jK1lM3nO5pQ7rS9tU1vW" \
  -H "Content-Type: application/json" \
  -d '{
    "fuzzy_correction": {
      "zh-TW": [
        { "correct": "報價", "incorrect": ["抱歉"] },
        { "correct": "抱歉" }
      ]
    }
  }'

Success Response

HTTP 200

HTTP 200 means the request itself was valid, not that the glossary is problem-free. The glossary's results are always inside data; read valid and has_conflicts.

{
  "type": "glossary_validation",
  "data": {
    "valid": false,
    "has_conflicts": true,
    "homophones_checked": false,
    "exceeds_runtime_limits": false,
    "checked": ["fuzzy_correction"],
    "languages_used": ["zh-TW"],
    "limits": {
      "terminology_max": 500,
      "fuzzy_rules_max": 6000,
      "dict_entries_max": 4500
    },
    "conflicts": [
      {
        "code": "variant_shadows_term",
        "severity": "error",
        "variant": "抱歉",
        "occurrences": [
          { "language": "zh-TW", "index": 0, "variant_index": 0, "term": "報價" }
        ],
        "shadows": [
          { "language": "zh-TW", "index": 1, "term": "抱歉", "kind": "fuzzy_correct" }
        ]
      }
    ],
    "errors": [],
    "warnings": { "unknown_languages": [] }
  }
}

Response Fields

FieldTypeDescription
validbooleanThe save gate. false when there is any format error or any conflict with severity: "error"
has_conflictsbooleanWhether there is any conflict at all, warnings included. Note: See "No Conflict Analysis When the Counts Are Over the Limit" below
homophones_checkedbooleanWhether homophone conflicts were fully checked. See above
exceeds_runtime_limitsbooleanThe glossary can be saved, but sending it in full into a recording would exceed the limits. See below
checkedarrayWhich blocks were actually checked on this call
languages_usedarrayThe language codes visited on this call (the union across the three blocks)
limitsobjectThe limits this endpoint applies
conflictsarrayThe list of conflicts. See the next section
errorsarrayFormat and quantity problems, in the same shape as the error response of config
warningsobjectCurrently only unknown_languages: language codes that could not be recognized
truncatedbooleanPresent only when content was truncated for exceeding the reporting cap (either conflicts or format problems can be truncated)
totalsobjectPresent only when conflicts were truncated; conflicts is the total number of conflicts before truncation. No total is provided when format problems were truncated

The top-level array fields of data always return [] when empty, never null. Optional fields inside a conflict object (occurrences / shadows / languages / terms and the like) are omitted entirely when they have no content; they never appear as empty arrays.

No Conflict Analysis When the Counts Are Over the Limit

When the glossary holds more entries than the limits allow, this endpoint reports only the count problem itself. It does not go on to check the entries one by one, and it performs neither conflict detection nor the homophone check. In that case the response reads:

  • valid: false, with the count problem in errors
  • has_conflicts: false, conflicts: [], homophones_checked: false

This does not mean the glossary is free of conflicts — it only means nothing has been checked yet. Follow errors to bring the counts back within the limits, then validate again to get the full conflict list.

A save gate that looks only at has_conflicts will read this as "no problems". Always read valid first.

exceeds_runtime_limits

This endpoint allows a glossary slightly larger than the amount actually usable during recording, so a management UI does not get stuck while the user is still editing.

exceeds_runtime_limits: true means the glossary can be saved, but sending it in full into a recording would be rejected. Prompt the user to trim it, or to split it across several glossaries.

Conflict Types

Every conflict carries code, severity, and the position of the problem (index is the position within that language's array, counted from 0), so a management UI can flag the entry directly.

The response contains no human-readable text. Compose your own wording from code and the fields — the language and phrasing are entirely yours to decide.

codeseverityMeaning
variant_shadows_termerrorAn incorrect variant is also the correct term of another rule, or a term in the terminology block
dict_duplicate_sourceerrorUnder the same target language, the same source is registered more than once
variant_ambiguouswarningThe same incorrect variant maps to different correct terms
case_flag_conflictwarningThe same incorrect variant has inconsistent case_insensitive settings across rules
variant_equals_termwarningAn incorrect variant is identical to the correct term of its own rule
duplicate_termwarningThe same term is registered more than once under the same language family
boost_out_of_rangewarningA term's boost is outside the valid range and will be adjusted automatically
homophone_conflictwarningTwo correct terms or terminology entries share a pronunciation

Common Fields

FieldWhen presentDescription
codeAlwaysThe conflict type
severityAlwayserror / warning
variantVariant conflictsThe incorrect variant at fault
termTerminology conflictsThe term at fault
sourceDictionary conflictsThe source word at fault
atSingle-position conflictsThe one position at fault
occurrencesMulti-position conflictsAll related positions
occurrences_totalWhen positions were truncatedThe total before truncation

The position object (the elements of at and occurrences):

FieldDescription
languageLanguage code
indexThe position within that language's array (counted from 0)
variant_indexThe position within that entry's incorrect array (counted from 0); provided for variant conflicts only
termThe result at that position: the rule's correct term, the term itself, or the dictionary's translation
kindThe origin of that position: fuzzy_correct (a fuzzy correction rule's correct term) / terminology_term (a term in the terminology block) / dict_source (an entry in the translation dictionary)

variant_shadows_term (error)

An incorrect variant is also a correct term elsewhere. When the user says that word normally, it is rewritten into something else.

The shadows array lists every position shadowed by this variant (truncated when there are too many, with shadows_total added).

{
  "code": "variant_shadows_term",
  "severity": "error",
  "variant": "抱歉",
  "occurrences": [
    { "language": "zh-TW", "index": 0, "variant_index": 0, "term": "報價" }
  ],
  "shadows": [
    { "language": "zh-TW", "index": 1, "term": "抱歉", "kind": "fuzzy_correct" }
  ]
}

In the example above, rule 0 treats 抱歉 as a misspelling of 報價, while rule 1 registers 抱歉 as a correct term. The result: when the user says 抱歉, it becomes 報價.

dict_duplicate_source (error)

Under the same target language, the same source word is registered with more than one translation. Only one takes effect; the others are ignored without notice.

effective_index points at which element of occurrences actually takes effect (the last entry in the array wins).

{
  "code": "dict_duplicate_source",
  "severity": "error",
  "source": "報價",
  "occurrences": [
    { "language": "en-US", "index": 0, "term": "quotation", "kind": "dict_source" },
    { "language": "en-US", "index": 1, "term": "quote", "kind": "dict_source" }
  ],
  "effective_index": 1
}

variant_ambiguous (warning)

The same incorrect variant maps to different correct terms; only one rule takes effect.

Which rule wins is not reported — the ordering is not guaranteed, least of all across languages. Treat it as something you must fix by choosing one.

When the ambiguity comes from case_insensitive (for example, ALFA and Alfa are the same variant once casing is ignored), variant is reported in lowercase, which may not exactly match any of the spellings in your glossary. For the original spellings, read occurrences[] — every element points at an exact position.

case_flag_conflict (warning)

The same incorrect variant has inconsistent case_insensitive settings across rules.

effective_case_insensitive is always false: if any one rule leaves it disabled, the variant is matched strictly.

variant_equals_term (warning)

An incorrect variant is identical to the correct term of its own rule (with case_insensitive enabled, differing only in casing counts as identical too).

It produces no incorrect result, but the entry has no effect whatsoever while still consuming quota.

duplicate_term (warning)

The same term is registered more than once under the same language family. Correction behavior is unaffected, but each duplicate still consumes terminology quota.

A language family is the part of the language code before the first hyphen: zh-TW and zh-CN belong to the same family, so a term registered under one of them applies to the other as well, and registering it under both is a duplicate. zh-TW and ja-JP do not. occurrences may therefore span several language codes.

boost_out_of_range (warning)

value is the value you sent; clamped_to is the value that actually takes effect.

{
  "code": "boost_out_of_range",
  "severity": "warning",
  "term": "語者分離",
  "at": { "language": "zh-TW", "index": 0, "term": "語者分離" },
  "value": 99,
  "clamped_to": 5
}

homophone_conflict (warning)

Two correct terms or terminology entries share a pronunciation. The registered words themselves are unaffected, but which of them a third, unregistered homophonic spelling is assigned to is not determined.

languages and terms have the same shape as in the realtime service's config_updated event, so the same display logic can be shared.

This conflict also carries occurrences, so you can trace where those words are registered in the glossary (kind is either terminology_term or fuzzy_correct) and flag the exact entries in a management UI. When there are too many positions, occurrences_total is added.

{
  "code": "homophone_conflict",
  "severity": "warning",
  "languages": ["zh-TW"],
  "terms": ["公事包", "公式包"],
  "occurrences": [
    { "language": "zh-TW", "index": 0, "term": "公事包", "kind": "terminology_term" },
    { "language": "zh-TW", "index": 1, "term": "公式包", "kind": "terminology_term" }
  ]
}

This check applies to Chinese only, and runs only when check_homophones has not been turned off.

Error Responses

HTTPerror_codeDescription
400invalid_jsonThe request body is not valid JSON
400config_too_many_languagesThere are more language codes than allowed (details carries field / count / max). The three glossary blocks and transcription_languages / translation_languages are all covered by this limit
401auth_missing_api_keyNo X-API-Key was provided
401auth_invalid_key_formatThe API Key format is not valid
401auth_invalid_api_keyThe API Key is invalid
401auth_key_expiredThe API Key has expired
401auth_key_disabledThe API Key is disabled
403auth_ip_not_allowedThe source IP is not on the allowlist
403auth_user_disabledThe account is disabled
403auth_account_blockedThe account is blocked
405invalid_actionThe method is not POST. The response carries an Allow header listing the supported methods
413config_payload_too_largeThe request body exceeds the size limit (2 MB by default; details.max_bytes is the limit in bytes currently in effect — see Request Size Limit)
429too_many_requestsThe rate limit was exceeded
500auth_service_errorThe authentication service is temporarily unavailable

Note: On auth_service_error, retry — do not swap the key. It means our authentication service is temporarily unavailable and has nothing to do with the key itself; the 401s are the ones that point at the key.

Problems with the glossary itself never return 4xx — those are always inside conflicts and errors on an HTTP 200.

Cross-Origin Requests

This endpoint can be called directly from a browser: OPTIONS preflight is allowed, and responses carry a full set of cross-origin headers (including Access-Control-Expose-Headers, so the browser can read the rate-limit headers and Retry-After). Error responses carry them as well, so the browser can read the body of a 401, 413, or 429.

Important: Calling from a browser means your API key is present in the browser.

This endpoint uses the same API key as every other endpoint, and that key can also create recordings, import audio, and consume credits. Anyone who can open the page can retrieve it.

Therefore:

  • If your glossary management interface is an internal admin tool (visible only to your own signed-in administrators), the risk is manageable and calling directly from the browser is fine
  • If it is a public page, route the call through your own backend instead — do not put the API key in the browser

Calls made from your own backend are not affected by cross-origin restrictions.


Version: V1.24.1 Last Updated: 2026-09-28

Copyright © 2026