> ## Documentation Index
> Fetch the complete documentation index at: https://www.bolna.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom TTS for Bolna voice agents

> Point a voice agent at your own self-hosted or third-party text-to-speech server that speaks the OpenAI speech API, with your endpoint and your key.

Bring your own voice by pointing Bolna at any text-to-speech server that speaks the OpenAI speech API. Your server can run anywhere: vLLM-Omni, a self-hosted open-weight TTS model, or a hosted gateway. Bolna calls it at your URL, with your key. Use this when you need a fine-tuned or open-weight voice model, when audio has to stay on your own infrastructure, or when you already pay a TTS provider directly.

***

## Quick config

```json theme={"system"}
"synthesizer": {
  "provider": "custom",
  "provider_config": {
    "voice": "vivian",
    "model": "custom"
  }
}
```

Register the voice once under **Settings → Integrations → TTS → Custom TTS** in the dashboard, then select it in the agent's voice settings. See [Add a custom TTS in the dashboard](#add-a-custom-tts-in-the-dashboard) for the walkthrough and [Add a custom TTS over the API](#add-a-custom-tts-over-the-api) for the API.

***

## What your server must support

<Note>
  Your server has to follow the OpenAI speech API.
</Note>

| Requirement | Detail |
| - | - |
| `POST {base_url}/audio/speech` | Give Bolna the base URL including the version segment, for example `https://your-host/v1`. Bolna appends the rest of the path. |
| The OpenAI request fields | Each request carries `model`, `voice`, `input`, `response_format` set to `mp3`, and `speed` set to `1.0`. |
| MP3 audio in the response | The whole clip for the text in each request. Bolna converts it to the call's audio format. |
| Bearer authentication | Your key is sent in the `Authorization` header. The key is optional if your server runs without authentication. |
| A public endpoint | The URL must be reachable on the public internet and answer directly, without redirects. Private and local network addresses are refused. |
| A reply within 20 seconds | A server that does not answer in time ends the call with a timeout error. |

Your model name is passed through unchanged, so it must match whatever your server expects. Some servers, such as vLLM-Omni, reject a request whose model is not the one they serve.

***

## Add a custom TTS in the dashboard

<Steps>
  <Step title="Open Integrations">
    Go to **Settings → Integrations** in the dashboard and filter by **TTS**. Find the **Custom TTS** card and click **Add voice**.

    <Frame caption="The Custom TTS card on Settings → Integrations, under the TTS filter">
      <img src="https://mintcdn.com/bolna-54a2d4fe/4NXt7GqDh-Prunzd/images/custom-tts/integrations-custom-tts-card.png?fit=max&auto=format&n=4NXt7GqDh-Prunzd&q=85&s=4310cefffd5ec77f1119e9c1549a5ed1" alt="Integrations page filtered to TTS providers, showing the Custom TTS card with an Add voice button alongside Cartesia, ElevenLabs, Rime and Sarvam" width="1680" height="598" data-path="images/custom-tts/integrations-custom-tts-card.png" />
    </Frame>
  </Step>

  <Step title="Fill in the details">
    * **Voice**: the voice name your server accepts, for example `vivian`
    * **Model name**: the model your server serves, for example `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice`
    * **Endpoint URL**: your OpenAI-compatible base URL, including `/v1`
    * **API key**: the bearer token your server expects. Leave it empty if your server has no authentication.
  </Step>

  <Step title="Save">
    Bolna checks the endpoint before saving it. Your key is stored encrypted and is never shown again.
  </Step>
</Steps>

Each voice you add appears as its own card on the Integrations page, showing its model and endpoint, with a delete action. To use several voices from the same server, add each voice separately.

### What is checked when you save

Registration is refused when Bolna can prove the configuration is wrong:

* The endpoint URL must be public. Private and local network addresses are rejected.
* If the endpoint rejects the API key, you get `The endpoint rejected this API key.`
* Each voice name can be added only once. A duplicate is refused with `A custom TTS with this voice already exists.`

If the endpoint cannot be reached at all, for example because a serverless deployment has scaled to zero, the voice is still saved. Verification is best effort and cannot guarantee that the server will be reachable later.

***

## Use it on an agent

Open the agent, go to the [Languages tab](/docs/agent-setup/audio-tab), choose **custom** as the voice provider and model, then pick your voice. Use the play button to hear a preview, then save the agent.

The agent sends the model name you registered with the voice, so the model shown in the agent stays **custom**.

<Warning>
  Custom TTS works for an agent's main voice only. Saving a multilingual agent that uses a custom TTS on any language is refused with `Custom TTS isn't supported for multilingual agents yet.`
</Warning>

***

## Key settings

| Setting | Type | Value | Description |
| - | - | - | - |
| `provider` | string | `"custom"` | Provider name |
| `provider_config.voice` | string | e.g. `"vivian"` | The voice name you registered |
| `provider_config.model` | string | `"custom"` | Bolna sends the model name you registered with the voice |

***

## How it behaves on a call

* **Latency:** the caller hears each sentence once your server has finished rendering it, so keep the server fast, warm and close to your callers.
* **Call timeline:** each reply shows when speech started, when the first audio arrived and when it ended, like the other providers.
* **Interruptions:** callers can interrupt the agent, as with any provider.
* **Errors:** a rejected key, an unknown voice, a server error or a timeout ends the call, and the reason appears in the call's logs. If the agent's voice no longer exists for the account, the call is stopped before it starts, with status `error` and `Custom TTS voice '<voice>' isn't configured for this account.`

***

## Manage your voices

Delete a voice from its card on the Integrations page. A voice that is still selected on an agent cannot be deleted: the error names the agents using it, so you can point them at another voice first.

Deleting a voice also removes the stored API key.

## Sub-accounts

A custom TTS belongs to the scope it was created in:

* A voice added on your main account is available to the account and to all of its sub-accounts.
* A voice added inside a sub-account is available only in that sub-account. It is not visible to the main account or to other sub-accounts, and only that sub-account can delete it.

## Add a custom TTS over the API

<CodeGroup>
  ```bash Add theme={"system"}
  curl --request POST \
    --url https://api.bolna.ai/user/tts/custom \
    --header 'Authorization: Bearer <BOLNA_API_KEY>' \
    --header 'Content-Type: application/json' \
    --data '{
      "custom_tts_voice": "vivian",
      "custom_tts_model": "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
      "custom_tts_url": "https://your-host/v1",
      "custom_tts_key": "<YOUR_ENDPOINT_KEY>"
    }'
  ```

  ```bash List theme={"system"}
  curl --request GET \
    --url https://api.bolna.ai/user/tts/custom \
    --header 'Authorization: Bearer <BOLNA_API_KEY>'
  ```

  ```bash Delete theme={"system"}
  curl --request DELETE \
    --url https://api.bolna.ai/user/tts/custom/<CUSTOM_TTS_ID> \
    --header 'Authorization: Bearer <BOLNA_API_KEY>'
  ```
</CodeGroup>

Send `custom_tts_key` as an empty string if your server has no authentication. See [Add a custom TTS](/docs/api-reference/user/add_custom_tts) for the full request and response schema.

***

## When to use a custom TTS vs a hosted provider

Use a **custom TTS** when:

* You run a fine-tuned or open-weight voice model that no hosted provider offers
* The audio or the traffic has to stay on your own infrastructure
* You already have a contract with a TTS provider and want the usage billed to it directly

Use a **hosted provider integration** when:

* You want the lowest time to first audio, since hosted providers stream audio as it is generated
* You need a multilingual agent with a different voice per language
* You would rather not own capacity planning, cold starts and upgrades

***

## FAQ

<AccordionGroup>
  <Accordion title="Does my server need to stream audio?">
    No. Each request expects a complete MP3 clip in the response.
  </Accordion>

  <Accordion title="Can one entry hold several voices?">
    No. Each entry is one voice. Add each voice your server offers as its own entry, all pointing at the same endpoint.
  </Accordion>

  <Accordion title="Can I use a custom TTS in a multilingual agent?">
    Not yet. A custom TTS can be an agent's main voice, and saving a multilingual agent that uses one on any language is refused.
  </Accordion>

  <Accordion title="What happens if my server has scaled to zero?">
    The first reply pays the cold start, which the caller hears as silence. A server that takes longer than 20 seconds to answer ends the call, so keep a warm instance for live traffic. Registration tolerates an unreachable endpoint, so scaling to zero does not block you from adding the voice.
  </Accordion>

  <Accordion title="Who bills me for the speech?">
    Your provider, or nobody if you host the model yourself. Bolna does not charge for speech synthesized by a custom TTS.
  </Accordion>

  <Accordion title="Can I point an agent at a server on a private network?">
    No. The endpoint must be reachable on the public internet. Expose it through a public hostname, with your key as the bearer token.
  </Accordion>
</AccordionGroup>

***

## Related

* [Text-to-speech providers](/docs/text-to-speech): every hosted voice provider
* [Bring your own model](/docs/custom-llm-inference): use your own OpenAI-compatible LLM
* [Connect provider credentials](/docs/providers): use your own account with a hosted provider


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.