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

# Voice

> Transcribe an utterance, speak a reply, list the available voices. Subject-authed like the text path.

Nothing here holds a session. You send audio and get text; you send text and get audio. What happens in between is the ordinary [message](/api-reference/message) path.

Authentication is identical to the text endpoints — the subject's bearer token, plus `Patchwork-Connection` if you route by connection. Provider keys are ours; the browser only ships and plays audio.

If the workspace's API has no voice provider configured, **every endpoint here returns `503 voice_unavailable`**. That is a normal deployment state, not a fault.

## Transcribe

`POST /v1/voice/transcribe`

`multipart/form-data`, not JSON.

<ParamField body="audio" type="file" required>
  The recorded utterance.
</ParamField>

<ParamField body="language" type="string">
  Optional hint. Detected when omitted.
</ParamField>

```json theme={null}
{
  "status": "success",
  "data": {
    "text": "move the meeting to Thursday",
    "confidence": 0.98
  }
}
```

<ResponseField name="text" type="string" required>
  The transcript. Empty for a silent take.
</ResponseField>

<ResponseField name="confidence" type="number">
  Provider confidence, when it reports one.
</ResponseField>

## Speak

`POST /v1/voice/speak`

<ParamField body="text" type="string" required>
  What to say.
</ParamField>

<ParamField body="format" type="string" default="markdown">
  What the text *is*, not what comes back. `markdown` is rendered into something worth hearing before synthesis; `plain` is spoken as given. Anything else is `422 voice_invalid_format`.
</ParamField>

<ParamField body="voice_id" type="string">
  A voice from `GET /v1/voice/voices`. The platform default applies when omitted.
</ParamField>

<ParamField body="language" type="string">
  Optional language hint.
</ParamField>

**Returns raw audio bytes, not the JSON envelope** — the one endpoint in the platform that does. The `Content-Type` is whatever the provider produced.

`Patchwork-Voice-Cache: hit | miss` says whether the clip was served from cache, so repeated playback of the same reply costs nothing.

Text that renders to nothing — markdown that was all formatting, say — is `422 voice_empty_text`.

## Voices

`GET /v1/voice/voices`

```json theme={null}
{
  "status": "success",
  "data": [
    {
      "id": "voice-id",
      "name": "Amber",
      "category": "conversational",
      "preview_url": "https://…"
    }
  ]
}
```

<ResponseField name="id" type="string" required>
  Pass as `voice_id` to **Speak**.
</ResponseField>

<ResponseField name="name" type="string" required>
  Display name for a picker.
</ResponseField>

<ResponseField name="category" type="string">
  Provider grouping.
</ResponseField>

<ResponseField name="preview_url" type="string">
  A sample clip, for auditioning inside the picker.
</ResponseField>

## Errors

| Code | Status | Means |
| - | - | - |
| `voice_unavailable` | 503 | no provider configured |
| `voice_provider_error` | 502 | the provider failed; retry is reasonable |
| `voice_invalid_format` | 422 | `format` was not `markdown` or `plain` |
| `voice_empty_text` | 422 | nothing left to speak after rendering |

## Endpoints

Each has its own page in the sidebar, generated from the spec:

* `POST /v1/voice/transcribe` — audio in, text out
* `POST /v1/voice/speak` — text in, audio out
* `GET /v1/voice/voices` — available voices

Voice is metered separately from agent runs, so these appear on their own in [Activity](/api-reference/request).

For the React SDK, which wraps all three, see the [Voice guide](/guides/voice).


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