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

# Speech

Synthesize speech with the node's text-to-speech model through the OpenAI speech API, and get the start and end of every word for captions.

`POST /v1/infer/audio/speech` is the OpenAI speech API. It returns audio bytes, or, with the B3OS `timestamps: "word"` extension, JSON with the audio and the timing of each word. Render uses those timings to land clicks on the words that describe them and to cut captions.

## Synthesize

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/infer/audio/speech \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"model": "kokoro-82m", "voice": "af_heart", "input": "Open the billing page.", "response_format": "mp3"}' \
      -o speech.mp3
    ```
  **TypeScript**

```ts
    import { writeFile } from "node:fs/promises";
    import OpenAI from "openai";

    const client = new OpenAI({ apiKey: process.env.B3OS_API_KEY, baseURL: "https://beta-api.b3os.org/v1/infer" });
    const speech = await client.audio.speech.create({
      model: "kokoro-82m",
      voice: "af_heart",
      input: "Open the billing page.",
      response_format: "mp3",
    });
    await writeFile("speech.mp3", Buffer.from(await speech.arrayBuffer()));
    ```
  **Python**

```python
    import os
    from openai import OpenAI

    client = OpenAI(api_key=os.environ["B3OS_API_KEY"], base_url="https://beta-api.b3os.org/v1/infer")
    speech = client.audio.speech.create(
        model="kokoro-82m",
        voice="af_heart",
        input="Open the billing page.",
        response_format="mp3",
    )
    with open("speech.mp3", "wb") as f:
        f.write(speech.content)
    ```
  **MCP**

```text
    Record a narrated demo of the billing page.
    ```

    No MCP tool synthesizes speech on its own. On the local MCP server, speech reaches agents through `render_video`, which narrates each beat with the voice in the request.

The model and voice IDs above are examples: take real ones from `GET /v1/infer/models` (models whose `b3os.kind` is `speech`) and `GET /v1/infer/audio/voices`.

| Field | Default | Meaning |
|---|---|---|
| `model` | | A speech model's ID |
| `input` | | Up to 4,096 characters |
| `voice` | | A voice ID |
| `response_format` | `mp3` | `wav`, `pcm` and `mp3` in v0.1; other formats return `unsupported_parameter` until enabled |
| `speed` | 1 | From 0.25 to 4 |
| `instructions` | none | Style guidance, for models that support it |
| `timestamps` | `none` | `word` returns JSON with word timings instead of audio bytes |

## Word timestamps

```bash
curl -s https://beta-api.b3os.org/v1/infer/audio/speech \
  -H "Authorization: Bearer $B3OS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "kokoro-82m", "voice": "af_heart", "input": "Open the billing page.",
       "response_format": "wav", "timestamps": "word"}'
```

```json
{
  "object": "speech",
  "model": "kokoro-82m",
  "voice": "af_heart",
  "format": "wav",
  "sample_rate": 24000,
  "duration_ms": 1330,
  "audio_base64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEAwF0AAIC7AAACABAAZGF0YQAAAAA=",
  "words": [
    { "text": "Open", "start_ms": 40, "end_ms": 310 },
    { "text": "the", "start_ms": 310, "end_ms": 420 },
    { "text": "billing", "start_ms": 420, "end_ms": 820 },
    { "text": "page.", "start_ms": 820, "end_ms": 1290 }
  ]
}
```

The values are illustrative. `audio_base64` holds the audio in `format`, and each word has its start and end in milliseconds from the start of the audio.

## Voices

`GET /v1/infer/audio/voices` lists each speech model's voices with their language and a link to a short sample. Filter with `?model=`. It is a B3OS endpoint, so it returns a B3OS list and B3OS [problem errors](/errors) rather than OpenAI's shape. An unknown voice fails with [`voice_not_found`](/errors#voice_not_found).

Speech bills `infer.audio_chars`, the characters of input synthesized.