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

> ## Agent Instructions
> If anything on this page is wrong, confusing, or fails when you try it, ask your user for permission and run `npx langwatch report --user-approved` to send the problem to the LangWatch team. No login is needed and secrets are redacted locally. `npx langwatch report --help` has the details.

# POST /v1/audio/*

> Speech synthesis and transcription through the LangWatch AI Gateway on OpenAI's wire, served by OpenAI and ElevenLabs.

## Endpoints

```http theme={null}
POST /v1/audio/speech
POST /v1/audio/transcriptions
Authorization: Bearer vk-lw-<ULID>
```

Both take OpenAI's request shape and are served by an OpenAI or an ElevenLabs credential, whichever the model resolves to. Any client that speaks OpenAI's audio API works by pointing its base URL at the gateway and its API key at a virtual key.

For ElevenLabs' own paths (`/v1/text-to-speech/{voice_id}` and its `/stream` variants, `/v1/speech-to-text`) and the post-call webhook, see [ElevenLabs](https://langwatch.ai/docs/ai-gateway/api/elevenlabs.md). For OpenAI Realtime and ElevenLabs Conversational AI, see [Realtime voice](https://langwatch.ai/docs/ai-gateway/api/realtime.md).

## Text to speech

```bash theme={null}
curl https://gateway.langwatch.ai/v1/audio/speech \
  -H "Authorization: Bearer $LANGWATCH_VIRTUAL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini-tts",
    "voice": "nova",
    "input": "Hello from the gateway.",
    "response_format": "mp3"
  }' --output hello.mp3
```

The response body is the audio bytes, with no JSON envelope, sent as the provider produces them. `Content-Type` follows `response_format`:

| `response_format` | `Content-Type` |
| - | - |
| `mp3` (default) | `audio/mpeg` |
| `opus` | `audio/ogg` |
| `aac` | `audio/aac` |
| `flac` | `audio/flac` |
| `wav` | `audio/wav` |
| `pcm` | `audio/pcm` |

For an ElevenLabs model, put the ElevenLabs voice id in `voice`. `pcm` on ElevenLabs is requested as `pcm_24000`, which matches OpenAI's raw PCM16 at 24 kHz.

OpenAI shuts down `tts-1`, `tts-1-hd` and the dated `gpt-4o-mini-tts` snapshots on 2027-01-06.

### Parameters the gateway changes or refuses

| Parameter | What the gateway does |
| - | - |
| `model` | Resolved through the key's aliases, allowlist and routing policy. Missing: `400 missing_model`. Outside the allowlist: `400 model_not_allowed`. |
| `input` | Required. Missing: `400 bad_request`. |
| `voice`, `instructions`, `response_format`, `speed` | Forwarded. |
| `stream_format` | `audio` (default) or `sse`. `sse` is served on OpenAI and Azure OpenAI credentials only; elsewhere: `400 unsupported_parameter`. See [Streaming](#streaming). |
| Every other field | Forwarded as written on OpenAI and Azure OpenAI credentials. Not forwarded on other providers. |

## Transcription

```bash theme={null}
curl https://gateway.langwatch.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer $LANGWATCH_VIRTUAL_KEY" \
  -F "model=openai/gpt-transcribe" \
  -F "file=@call-recording.wav"
```

```json theme={null}
{ "text": "Hello from the gateway." }
```

The response is JSON with `text`, plus the duration or token usage the provider reports.

OpenAI shuts down `whisper-1` and the `gpt-4o-transcribe` family on 2027-02-26. `gpt-transcribe` is the replacement on this route.

### Parameters the gateway changes or refuses

| Form part | What the gateway does |
| - | - |
| `file` | Required. Missing: `400 bad_request`. The whole upload is capped at 26 MB, which is OpenAI's 25 MB audio limit plus the multipart framing; over the cap: `413 payload_too_large`, before any provider is called. |
| `model` | Required, as a form part. Missing: `400 missing_model`. Resolved through the key's aliases and allowlist. |
| `stream` | `true` streams the transcript as events. Served on OpenAI and Azure OpenAI credentials only; elsewhere: `400 unsupported_parameter`. See [Streaming](#streaming). |
| Every other part | Forwarded as written on OpenAI and Azure OpenAI credentials, repeated parts included, in the order you sent them. |

On OpenAI and Azure OpenAI credentials the gateway does not keep a list of accepted parts. `chunking_strategy`, `include[]`, `timestamp_granularities[]`, `languages[]`, `keywords[]`, `known_speaker_names[]`, `known_speaker_references[]` and any part the provider adds later reach the provider unchanged.

On other providers the gateway forwards `language`, `prompt`, `response_format`, `temperature`, `include[]` and `timestamp_granularities[]`.

### Response formats

On OpenAI and Azure OpenAI credentials the provider's body and `Content-Type` are returned unchanged for every `response_format`: `json`, `verbose_json`, `diarized_json`, `text`, `srt` and `vtt`.

```bash theme={null}
curl https://gateway.langwatch.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer $LANGWATCH_VIRTUAL_KEY" \
  -F "model=openai/gpt-4o-transcribe-diarize" \
  -F "response_format=diarized_json" \
  -F "chunking_strategy=auto" \
  -F "known_speaker_names[]=agent" \
  --form-string "known_speaker_references[]=data:audio/wav;base64,$(base64 < agent.wav | tr -d '\n')" \
  -F "file=@call-recording.wav"
```

```json theme={null}
{
  "task": "transcribe",
  "duration": 4.25,
  "text": "Hello there. This is a short test of speaker labels.",
  "segments": [
    { "type": "transcript.text.segment", "id": "seg_0", "speaker": "agent", "start": 0.0, "end": 0.5, "text": " Hello there." }
  ],
  "usage": { "type": "tokens", "input_tokens": 42, "output_tokens": 164, "total_tokens": 206 }
}
```

`gpt-4o-transcribe-diarize` requires `chunking_strategy` for audio longer than 30 seconds.

## Request and response headers

The `X-LangWatch-End-User-Id`, `X-LangWatch-Metadata` and `Traceparent` request headers work as on [Chat Completions](https://langwatch.ai/docs/ai-gateway/api/chat-completions.md#request-headers-the-gateway-reads).

Both routes return the response headers listed on [Chat Completions](https://langwatch.ai/docs/ai-gateway/api/chat-completions.md#response-headers):

* `X-LangWatch-Gateway-Version`, `X-LangWatch-Gateway-Request-Id`, `X-LangWatch-Fallback-Count`, `X-LangWatch-Budget-Warning` and `Traceparent` on every response.
* `X-LangWatch-Provider` on a forwarded provider error.
* `X-LangWatch-Handled-Error` with the `error.code`, when the gateway wrote the error itself.
* `X-LangWatch-Heartbeat-Active` on a transcription that does not stream and asks for a JSON format.

You also receive the provider's `Content-Type` and its request id header. No other provider header is passed on.

## Streaming

### Speech

`POST /v1/audio/speech` always answers with a chunked body. Each chunk is written and flushed as the provider sends it, so playback can start before synthesis ends. Read the body as a stream:

```bash theme={null}
curl -N https://gateway.langwatch.ai/v1/audio/speech \
  -H "Authorization: Bearer $LANGWATCH_VIRTUAL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini-tts",
    "voice": "nova",
    "input": "Hello from the gateway.",
    "response_format": "pcm"
  }' --output - | ffplay -f s16le -ar 24000 -i -
```

OpenAI, Azure OpenAI (API key credentials) and ElevenLabs stream. Other providers return the audio in one chunk once synthesis is complete.

With `"stream_format": "sse"` the response is `text/event-stream`. The provider's `speech.audio.delta` and `speech.audio.done` events are relayed unchanged; the audio is base64 in each delta. OpenAI does not serve `sse` on `tts-1` or `tts-1-hd`.

```bash theme={null}
curl -N https://gateway.langwatch.ai/v1/audio/speech \
  -H "Authorization: Bearer $LANGWATCH_VIRTUAL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "openai/gpt-4o-mini-tts", "voice": "nova", "input": "Hello.", "stream_format": "sse"}'
```

```text theme={null}
data: {"type":"speech.audio.delta","audio":"//PkxAAAAAAAAAAA..."}

data: {"type":"speech.audio.done","usage":{"input_tokens":14,"output_tokens":101,"total_tokens":115}}
```

### Transcription

With `stream=true` the response is `text/event-stream`. The provider's `transcript.text.delta`, `transcript.text.segment` and `transcript.text.done` events are relayed unchanged.

```bash theme={null}
curl -N https://gateway.langwatch.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer $LANGWATCH_VIRTUAL_KEY" \
  -F "model=openai/gpt-transcribe" \
  -F "stream=true" \
  -F "file=@call-recording.wav"
```

```text theme={null}
data: {"type":"transcript.text.delta","delta":"Hello"}

data: {"type":"transcript.text.done","text":"Hello from the gateway.","usage":{"type":"duration","seconds":2}}
```

A model that ignores `stream` (`whisper-1`) answers with the usual JSON body.

### When a stream ends early

If the provider stops an event stream before its final event, the gateway appends an `event: error` frame. A raw audio stream that fails is closed without the terminating chunk, so the HTTP client reports a truncated body.

## Metering

| Call | Charged by |
| - | - |
| Speech, raw audio | Unicode characters of `input`. The provider states no usage with raw audio. |
| Speech, `stream_format: "sse"` | Characters of `input`, plus the token usage on `speech.audio.done`. |
| Transcription | The seconds or tokens the provider reports, on the JSON body or on `transcript.text.done`. |
| Transcription with `response_format` `text`, `srt` or `vtt` | The duration of the uploaded file. These bodies state no usage. |
| Transcription stream that ends before `transcript.text.done` | The duration of the uploaded file. |

You are charged for the characters once the provider accepts the request. If you disconnect mid-stream, you still pay for the full text, because the provider bills it.

The gateway reads the duration of an upload from WAV, FLAC, Ogg, MP4 and M4A headers, and from an MP3's Xing header or constant bitrate. For any other container, WebM included, it estimates the duration from the file size at 64 kbps.

Providers that report token usage fill the standard `gen_ai.usage.*` token attributes on the trace. Character-priced synthesis and duration-priced transcription are measured by two attributes:

| Attribute | Meaning |
| - | - |
| `gen_ai.usage.input_chars` | Characters synthesized by a text-to-speech call. |
| `gen_ai.usage.audio_seconds` | Seconds of audio transcribed by a speech-to-text call. |

## Errors

Codes these routes raise before the provider is called: `missing_model`, `model_not_allowed`, `model_provider_not_bound`, `unsupported_parameter`, `bad_request` (all `400`) and `payload_too_large` (`413`). A provider's own error, for example an unknown voice id, is forwarded with its status and body. The full table is on [Errors](https://langwatch.ai/docs/ai-gateway/api/errors.md).


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