# Beatra API — Text to speech

> Synthesize speech from text with a preset or cloned voice.
> Self-contained integration guide for AI coding assistants.
> Human-readable page: https://docs.beatra.ai/en/docs/capabilities/speech/text-to-speech

- Base URL: `https://api.beatra.ai/v1`
- Auth: `Authorization: Bearer <BEATRA_API_KEY>` — create keys in the console at https://console.beatra.ai (Developer → API keys)
- Headers: `Content-Type: application/json`; send `Idempotency-Key` (stable UUID) on create-style POSTs (24h dedupe window); optional `X-Request-Id` is echoed back.
- Interaction model: async task — create returns `202` with a `task_id`; poll `GET /v1/tasks/{task_id}` until terminal status (`succeeded` / `failed` / `canceled`), or pass `callback_url` to be notified (see appendix).

---

## 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.

## Tool arguments

```json
{
  "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](https://docs.beatra.ai/en/docs/capabilities/speech/voices);
preset and [cloned](https://docs.beatra.ai/en/docs/capabilities/speech/voice-clone) voices behave the
same. Streaming and real-time conversation are not part of this capability.

## Models, controls, and cost

Common controls are `input` (required, ≤50,000 characters), `voice`
(required), `language`, `format` (default `mp3`; also `wav`/`flac`/`opus`/`pcm`),
`speed` (0.5–2.0), `volume` (>0–10), `pitch` (−12–12), `emotion`, and
`sample_rate`. Use each only according to the selected model's metadata.

`beatra.models.list` is the current source for selectable models, supported
languages, formats, sample rates, and customer 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.

## 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 model detect the language.
An unsupported language is rejected instead of being silently replaced.

Current speech models cover the 40 languages below;
`constraints.supported_languages` from `beatra.models.list` is the live,
model-specific source.

| Code | Language | Code | Language |
| --- | --- | --- | --- |
| `zh` | Chinese | `tr` | Turkish |
| `yue` | Cantonese | `nl` | Dutch |
| `en` | English | `uk` | Ukrainian |
| `es` | Spanish | `th` | Thai |
| `fr` | French | `pl` | Polish |
| `ru` | Russian | `ro` | Romanian |
| `de` | German | `el` | Greek |
| `pt` | Portuguese | `cs` | Czech |
| `ar` | Arabic | `fi` | Finnish |
| `it` | Italian | `hi` | Hindi |
| `ja` | Japanese | `bg` | Bulgarian |
| `ko` | Korean | `da` | Danish |
| `id` | Indonesian | `he` | Hebrew |
| `vi` | Vietnamese | `ms` | Malay |
| `fa` | Persian | `sk` | Slovak |
| `sv` | Swedish | `hr` | Croatian |
| `fil` | Filipino | `hu` | Hungarian |
| `no` | Norwegian | `sl` | Slovenian |
| `ca` | Catalan | `nn` | Nynorsk |
| `ta` | Tamil | `af` | Afrikaans |

## 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](https://docs.beatra.ai/en/docs/api-reference/operations/audio/text_to_speech_v1_audio_speech_post).
Skill + MCP is the recommended integration; use REST for custom, non-agent
applications.

---

## Appendix: shared contract for async tasks

### Callbacks (optional)

Pass `callback_url` (HTTPS) when creating the task. After the task reaches a
    terminal state Beatra POSTs to it and expects a 2xx within 10 seconds:

```json
{ "event_id": "evt_...", "event_type": "task.succeeded", "event_time": "...", "task": { "...": "full task envelope" } }
```

Headers: `X-Event-Id` (stable across retries — use for idempotent dedupe),
`X-Delivery-Id`, `X-Delivery-Attempt`, `X-Task-Id`, `X-Event-Type`, `X-Timestamp`,
and, for signed delivery, `X-Signature-Key-Id` plus
`X-Signature: t=<unix_ts>,v1=<hex>` where `v1 = HMAC-SHA256(signing_secret, "<unix_ts>." + raw_body)`.
Callback signing keys are independent of API keys and are managed through the
account signing-key API or Console. Pass `callback_signing_key_id` with
`callback_url` for signed delivery, or omit it for unsigned delivery. The
secret is returned once when a signing key is created or rotated. Reject
timestamps more than 300s off. Retries on failure: 1m / 5m / 30m / 2h / 12h.
Polling `GET /v1/tasks/{task_id}` always remains the source of truth.

### Billing

Credits are an independent billing unit with no fixed exchange rate to a fiat
currency. Media tasks are charged up-front at creation; insufficient balance returns `402 insufficient_balance`
and no task is created. Failed tasks are refunded automatically — see
`billing.charged_credits` / `billing.refunded_credits` in the task envelope.

### Errors

Every non-2xx response uses one envelope:

```json
{ "error": { "code": "...", "type": "...", "message": "...", "retryable": false, "request_id": "req_..." } }
```

Retry only when `retryable` is `true`, with exponential backoff and the SAME
`Idempotency-Key`. `429` responses include `X-RateLimit-*` headers.
