beatraDocs
Speech & voices

Text to speech

Synthesize speech from text with a preset or cloned voice.

Synthesize speech from text with a preset or cloned voice.

Primary agent path

Use the Universal Skill with the MCP tool beatra.speech.synthesize. The tool is billable and returns an asynchronous task.

  1. Pick a voice first: beatra.voices.list returns preset and cloned voices with voice_id and preview URLs.
  2. If a model, language, format, or price matters, call beatra.models.list with capability: "text_to_speech". Otherwise use model: "auto" or omit model.
  3. Create one opaque client_request_id and submit the final arguments once.
  4. Poll the returned task with beatra.tasks.get until it is terminal.

AI guide

MCP integration guide for AI

For custom API integrations, copy this guide into Claude, Codex, or another coding assistant.

Tool arguments

{
  "voice": "voice_01JX...",
  "input": "Welcome to Beatra — make your first call in three minutes.",
  "language": "en-US",
  "format": "mp3",
  "speed": 1.0,
  "client_request_id": "tts-opaque-1"
}

voice accepts any voice_id from voices, preset or cloned. A voice belongs to one engine (its compatible_models), and on some models what a voice follows differs from voice to voice; its persona says so. Streaming and real-time conversation are not part of this capability.

Speech markup

input may contain a closed set of tokens. Tokens count toward the 50,000 character limit and toward weighted-character billing. The live vocabulary and ranges are constraints.script_markup on the model card from beatra.models.list. The same markup works on every speech model; what a model cannot render is degraded (dropped or ignored) with a warning, never approximated. The model-specific options further down this page are model_options fields, not markup.

  • {pause <seconds>} — silence, at most two decimal places, within the model card's script_markup.pause.range (up to 99.99 seconds on some models, 10 seconds on others; a longer pause is rejected before billing). The request is rejected when a pause sits at the start or end of the text, or when two pauses have no speakable content between them.
  • Sound tags: {laugh} a laugh, {chuckle} a chuckle, {sigh} a sigh, {breath} an audible breath, {cough} a cough, {clear-throat} a throat clear, {gasp} a gasp, {groan} a groan, {inhale} an inhale, {exhale} an exhale, {pant} panting, {sniff} a sniff, {hmm} a hesitant "hmm". script_markup.tags lists the tags a model renders; on a model without them the tag is removed (never read aloud) and a markup_degraded warning says so.
  • {pron <word> <reading>} — pin the reading of this one occurrence of a word; the same word elsewhere keeps its default reading, and one request may give the same character different readings. Readings are pinyin: syllables written together, each ending with a tone digit 1–5, v for ü, one syllable per Chinese character. Other notations are not supported. Example: 孩子{pron 长 zhang3}大了,头发也很{pron 长 chang2}。
  • {sub <word> <spoken text>} — speak this occurrence of a word as the given text, up to 100 characters; subtitles still show the word. Use it for brand names, abbreviations, English words, and digits that must be read one by one. Example: {sub W3C 万维网联盟}.
  • {{ — a literal left brace: a {{ in the text is spoken as one {, and the pair itself is never parsed as a token. Text after it is still parsed normally, so escape only the left brace and write right braces as they are.

constraints.limitations on each model's card states that model's limits in plain sentences; read it for the model you call. Examples:

  • Volume, speed, pitch, and emotion apply to the whole request and cannot target one sentence or word. For local variation, split into multiple calls (each with its own parameters and its own audio file) or rewrite the text (ellipses, short clauses, wording).
  • On some models, long texts (over 3,000 characters, which take the async path) do not get high-quality number normalization, or drop {pause} and {pron} markup (with a warning). Split long texts at paragraph breaks into requests of at most 3,000 characters when that matters.

Performance direction and previous text

Two optional fields describe how a clip should sound, on every model that follows them; the model card lists each one under controls (with constraints.direction_max_characters and constraints.previous_text_tail_characters):

  • direction — natural-language direction for the whole request, for example 像在耳边说悄悄话一样,轻声地说 or read it like breaking news. Keep it to a sentence or two: a direction longer than direction_max_characters is ignored with a warning. How it comes through depends on the model, the voice and the text; a volume request is better made with volume.
  • previous_text — the text that comes right before this clip (the previous line of a dialogue, the previous paragraph), so separately synthesized clips keep a continuous tone. Any length is accepted; the model uses only its last previous_text_tail_characters characters. It is never spoken.

Neither field is billed, and neither ever makes a request fail. A model, a voice, or a path that does not follow a field ignores it and returns an option_ignored warning; the audio is still produced. On some models these controls depend on the voice (for example, character voices may follow neither); constraints.limitations on the card says which. emotion works the same way: a model or voice that cannot render the requested emotion ignores it with a warning. To learn whether a voice follows these controls before a billable call, send the same request to validate (below): it returns the same warnings, free.

Before a billable submit, call beatra.speech.validate or POST /v1/audio/speech/validate. It runs the same checks and warnings and returns the weighted character count and the credit price; it does not create a task, charge credits, or call the speech engine. It does not take an Idempotency-Key.

Warnings, when present, are on the 202 response (including an idempotent replay), GET /v1/tasks/{task_id}, beatra.tasks.get, and the customer callback field task.warnings. The task list, expand, account detail, and admin responses do not promise them. text_begin and text_end in a subtitle file are offsets into the text the engine actually read — the text with markup compiled into the engine's own form or removed — and are not mapped back to the submitted text. Where a subtitle segment lines up with that text, its text shows a {pron} or {sub} word as written and leaves sound tags out. Billing counts characters as described under Models, controls, and cost; output.characters reports the characters the engine read, so the two counts can differ.

Models, controls, and cost

Common controls are input (required, ≤50,000 characters), voice (required), language, format (default mp3; the card's constraints.formats lists the others the model renders), speed (0.5–2.0), volume (within the card's constraints.volume range, for example >0–10 or 0.5–2; a value outside it is rejected before billing), pitch (−1–1, normalized: 0 keeps the voice's original pitch, −1 the lowest, 1 the highest the selected model supports; use decimals like 0.25 for subtle tuning), emotion, sample_rate, and subtitle_enable (default false; set true to receive sentence-level subtitles with the audio as output.subtitles — free; a model that cannot time subtitles for the chosen voice or language rejects the request before billing, and constraints.limitations names those cases; if a subtitle file cannot be produced at run time, the audio is still delivered, without output.subtitles). language is a hint some models only partly use: constraints.limitations says how the selected model treats it. Use each only according to the selected model's metadata. Not every format supports every sample rate (for example opus renders at 8/16/24 kHz on some models and only at its fixed 48 kHz on others, where it takes no sample_rate): read constraints.sample_rates_by_format on the model card instead of assuming the flat rate list applies to every format.

beatra.models.list is the current source for selectable models, supported languages, formats, sample rates, and prices. Do not copy a model catalog or price table into an agent prompt.

Speech is prepaid from the request estimate and billed linearly by weighted characters (quoted per 1,000): each Han ideograph counts as 2 and every other character counts as 1. The count is taken on the submitted text, markup included. On a model whose engine bills the control text it receives by length, the text sent to the engine is counted when it is longer: there, pauses and readings become control tags, and a request that carries them (or whose text begins with <speak) is sent whole in that form (&, < and > escaped). Validate returns the exact count.

Model-specific options

model_options carries options that only one model family runs, named by family. The model card lists the keys it runs under controls (for example model_options.doubao.explicit_dialect). Keys for another family have no effect; a model may say so with an option_ignored warning.

MiniMax: pronunciation dictionary

For rare or polyphonic Chinese characters, pin the exact reading instead of rewriting the text with a homophone:

{
  "model_options": {
    "minimax": {
      "pronunciation_dict": {
        "tone": ["湛/(zhan4)", "晞/(xi1)", "厌/(yan1)"]
      }
    }
  }
}

The format is character/(pinyin+tone digit), with tone digits 1–4 for the four tones and 5 for the neutral tone. A dictionary entry applies to every occurrence of the word in the request; to fix one occurrence, use the {pron} markup above. MiniMax models only; other models ignore this field.

Doubao

  • model_options.doubao.explicit_dialect — beijing, dongbei, henan, shaanxi, shanghai, sichuan or tianjin (Cantonese is language: "yue-HK"). Only voices whose languages_supported lists yue speak dialects; other voices are rejected before billing.
  • model_options.doubao.explicit_language — forces the reading language (zh-cn, en, ja, es-mx, ko, de, fr, …). This is a filter, not a hint: sentences in other languages are skipped, and a voice given text it cannot read produces no audio (the task fails and is refunded). nl, no, uk, fi, da, cs, hu, el, ro, hi and es-es work only with Chinese (zh_…) preset voices. Long texts accept only zh-cn, en, es-mx, id and pt-br.
  • model_options.doubao.silence_duration — silence appended after the last word, 0–30,000 milliseconds.
  • model_options.doubao.latex_parser — "v2" reads LaTeX formulas aloud (education scripts).
  • model_options.doubao.pronunciation_dict — {"tone": ["北京/(bei3)(jing1)", "omg/oh my god"]}: up to 5,000 rules applied to every occurrence of the word (a reading, or a replacement text). To fix one occurrence, use {pron}.

When the text uses {pause} or {pron}, the markup wins: latex_parser and pronunciation_dict are ignored with a warning, because the engine cannot combine them with the form that markup compiles to.

Languages

language is an optional provider-neutral BCP-47 tag such as zh-CN, yue-HK, or en-US. Beatra matches the primary language subtag against the selected model; omit the field to let the voice or provider choose. Pass the exact value auto only when you explicitly want automatic language detection (constraints.language_auto on the model card says whether the model supports it). An unsupported language is rejected instead of being silently replaced.

How a model uses language differs. Some use it as a recognition hint for the voice. Others read Chinese and English by default and use language to turn on language recognition for anything else (a Chinese voice may read nothing in, say, Japanese without it) and to pick a dialect (yue); on those, each voice reads only the languages in its languages_supported. A language the voice does not speak comes back with an option_ignored warning, except where the result could only fail: Cantonese on a voice without it, or a long text in a language the voice cannot read, is rejected before billing. constraints.limitations on the card says which applies.

Current speech models cover up to the 40 languages below, each model a part of them; constraints.supported_languages from beatra.models.list is the live, model-specific source.

CodeLanguageCodeLanguage
zhChinesetrTurkish
yueCantonesenlDutch
enEnglishukUkrainian
esSpanishthThai
frFrenchplPolish
ruRussianroRomanian
deGermanelGreek
ptPortuguesecsCzech
arArabicfiFinnish
itItalianhiHindi
jaJapanesebgBulgarian
koKoreandaDanish
idIndonesianheHebrew
viVietnamesemsMalay
faPersianskSlovak
svSwedishhrCroatian
filFilipinohuHungarian
noNorwegianslSlovenian
caCatalannnNynorsk
taTamilafAfrikaans

Task status and recovery

queued and running are not failures. Keep polling the same task_id; never submit a replacement because work is still running. On a lost create response, retry the identical arguments with the same client_request_id. If any input changes, use a new ID. Failed tasks automatically refund the charged credits. Return the audio artifact, resolved model, and charged credits only from the terminal task response; artifact URLs are CDN addresses — copy them to your own storage promptly.

REST API

Direct protocol integrations may use POST /v1/audio/speech and the shared task endpoints. Follow the generated API operation. Skill + MCP is the recommended integration; use REST for custom, non-agent applications.

Last updated

On this page

Markdown