tts_language_script_mismatch: Korean TTS needs a Hangul syllable

Sume TTS returns 400 tts_language_script_mismatch when language is ko but the text has no Hangul syllable. Usual cause: UTF-8 decoded as Latin-1. Fix it.

4 min readSume
All posts

A Sume text-to-speech request with language: "ko" fails with HTTP 400 tts_language_script_mismatch when the transcript does not contain at least one Hangul syllable (the range U+AC00 to U+D7A3). The error is raised before a job is created, so nothing is queued and nothing is charged. In practice the text is almost never really non-Korean: it is Korean that was decoded with the wrong character set somewhere upstream, and the fix is to repair the text, not the request.

The check is the same on all three TTS entry points: POST /v1/tts-1.0/generate, POST /v1/models/sume/tts-1.0/runs and POST /v1/tts-router/generate.

What exactly does the 400 look like?

The error body carries code and public_reason set to tts_language_script_mismatch, category: validation, retryable: false, next_action: fix_input and details.field: transcript. The message reads along the lines of "Korean TTS requires at least one Hangul syllable in transcript. Check the original text and its encoding before retrying." Because retryable is false, a blind retry with the same body will fail the same way.

What Sume's own tests send with language ko to all three TTS routes (Sume API test suite, read 2026-10-03)
Transcript sent with language koHangul syllable present?Result
Korean text decoded as Latin-1 (mojibake)No400 tts_language_script_mismatch
"Hello from the live show."No400 tts_language_script_mismatch
"123 !?"No400 tts_language_script_mismatch
Bare jamo such as ㄱㄴㄷNo (jamo are not syllables)400 tts_language_script_mismatch
오늘 라이브에서 소개합니다.YesAccepted, job queued

Why is my Korean text suddenly Latin-looking?

The failure that motivated the check came from a production pipeline in which UTF-8 Korean was decoded as Latin-1: each Hangul syllable turns into three accented Latin characters and control characters, which still look like text to a JSON encoder. The usual places this happens are a CSV or spreadsheet export opened with the wrong encoding, a webhook or queue consumer that reads bytes with a default of Latin-1, and a shell or Windows console code page that rewrites the string before curl sends it.

Sending that text to a TTS provider would have produced gibberish audio and a charge. Rejecting it at the door is the cheaper outcome.

How do I repair the text and resend?

If the damage is exactly a UTF-8 to Latin-1 mix-up, it is reversible: encode the string back to Latin-1 bytes and decode those bytes as UTF-8. Check the result for a Hangul syllable before you submit.

  • Fix the source: set the reader to UTF-8 where the text first enters your system, instead of patching strings at the end.
  • Keep language: "ko". The Sume contract says never to translate a non-English request into English, and a Korean request is not a reason to drop the field.
  • If a line is truly not Korean (an English product name on its own), send it with language: "en", or put it inside a Korean sentence.
import re

original = "오늘 라이브에서 소개합니다."
broken = original.encode("utf-8").decode("latin-1")

fixed = broken.encode("latin-1").decode("utf-8")
print(fixed == original)
print(bool(re.search("[가-힣]", fixed)))

Does the check work the other way?

Partly. When language is omitted, Sume infers ko from a transcript that is mostly Hangul and ja from one that is mostly kana, and otherwise the provider default of English applies. That inference is a fallback for those two scripts only. For every other non-English language, set language yourself; the contract describes it as the language the voice speaks the transcript in, and it is the field to set for every non-English transcript.

Separately, a voice whose saved language differs from the requested one can return a 409 or an MCP warning that needs a confirmation. That is a different gate, covered in the related posts below, and it does not involve the script check.

Checklist before you resubmit

Run these four checks in your own code before the request leaves, because the API will not tell you which upstream step damaged the text.

  • Print len(text) and the first 20 characters from the exact value you send, not from your editor.
  • Assert that re.search("[가-힣]", text) is true whenever language == "ko".
  • Log the HTTP body, not just the status: public_reason and details.field tell you the transcript is the problem.
  • Remember the cost model: TTS is billed per transcript character at $0.0475 per 1,000 characters, and a 400 like this one never reaches that meter.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume