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

# OpenAI compatibility

> Call Inworld TTS from the OpenAI SDKs by pointing them at Inworld and choosing an Inworld model and voice

Inworld TTS serves OpenAI's text-to-speech endpoint, `POST /v1/audio/speech`, in OpenAI's own request and response format. If your app already uses OpenAI's text-to-speech, point the SDK at Inworld with your Inworld API key, then set `model` and `voice` to Inworld IDs. The SDK calls themselves stay the same.

## Endpoint

```
https://api.inworld.ai/v1
```

Change your SDK base URL from `https://api.openai.com/v1` to `https://api.inworld.ai/v1`. In the US region, the same base URL also serves the [LLM Router](https://docs.inworld.ai/router/openai-compatibility.md), so one client configuration covers both LLM and TTS.

For [regional deployments](https://docs.inworld.ai/portal/regions.md), use `https://api.eu.inworld.ai/v1` or `https://api.in.inworld.ai/v1` with an API key created in that region's Portal. Regional endpoints are provisioned per organization on Enterprise plans, and keys from the US region do not authenticate against them. The LLM Router is not available in regional deployments.

## Quickstart

Use your Inworld [API key](https://docs.inworld.ai/portal/api-key-auth.md) as the SDK's API key. The OpenAI SDKs send it as `Authorization: Bearer <key>`; the endpoint also accepts `Authorization: Basic <key>`. Keep the key on your server.

<CodeGroup>
```python Python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.inworld.ai/v1",
    api_key="YOUR_INWORLD_API_KEY",
)

with client.audio.speech.with_streaming_response.create(
    model="inworld-tts-2",
    voice="Dennis",
    input="Welcome back! Your order shipped this morning and should arrive by Thursday.",
) as response:
    response.stream_to_file("speech.mp3")
```

```javascript Node
import fs from 'node:fs';
import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://api.inworld.ai/v1',
  apiKey: 'YOUR_INWORLD_API_KEY',
});

const response = await client.audio.speech.create({
  model: 'inworld-tts-2',
  voice: 'Dennis',
  input: 'Welcome back! Your order shipped this morning and should arrive by Thursday.',
});

fs.writeFileSync('speech.mp3', Buffer.from(await response.arrayBuffer()));
```

```bash curl
curl -X POST https://api.inworld.ai/v1/audio/speech \
  -H "Authorization: Basic $INWORLD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "inworld-tts-2",
    "voice": "Dennis",
    "input": "Welcome back! Your order shipped this morning and should arrive by Thursday."
  }' \
  --output speech.mp3
```
</CodeGroup>

## Request parameters

The [API reference](https://docs.inworld.ai/api-reference/ttsAPI/texttospeech/create-speech.md) has the full request and response schema.

| Parameter | Required | Description |
| --- | --- | --- |
| `model` | Yes | An Inworld model ID, such as `inworld-tts-2` or `inworld-tts-2-flash`. See [Models](https://docs.inworld.ai/tts/tts-models.md). OpenAI model names such as `tts-1` return `400`. |
| `input` | Yes | The text to synthesize, up to **4,000 characters**. Inline markup works as it does in the [Streaming API](https://docs.inworld.ai/tts/synthesize-speech-streaming.md), including [steering tags](https://docs.inworld.ai/tts/capabilities/steering.md), [pause controls](https://docs.inworld.ai/tts/capabilities/pause-controls.md), and [custom pronunciation](https://docs.inworld.ai/tts/capabilities/custom-pronunciation.md). |
| `voice` | Yes | An Inworld voice ID, such as `Dennis`, or the ID of one of your custom voices. Voice IDs are case-sensitive. OpenAI voice names such as `alloy` return `404`. List available voices with [List voices](https://docs.inworld.ai/api-reference/ttsAPI/texttospeech/list-voices.md). |
| `response_format` | No | `mp3` (default), `opus`, `flac`, `wav`, or `pcm`. `aac` is not supported. |
| `speed` | No | Speaking rate from `0.5` to `1.5`. Default `1.0`. Values outside this range return `400`. |
| `instructions` | No | A natural-language [steering](https://docs.inworld.ai/tts/capabilities/steering.md) instruction for the whole request. Supported on `inworld-tts-2` only; other models ignore it. |
| `stream_format` | No | `audio` (default) returns raw audio bytes. `sse` returns server-sent events. |

The language is detected from the input text.

## Audio formats

All formats are mono.

| `response_format` | Encoding | Sample rate |
| --- | --- | --- |
| `mp3` | MP3 at 128 kbps | 48 kHz |
| `opus` | Opus in an Ogg container | 48 kHz |
| `flac` | 16-bit FLAC | 48 kHz |
| `wav` | 16-bit PCM in a WAV container | 48 kHz |
| `pcm` | Raw 16-bit signed little-endian samples, no header | 24 kHz |

<Note>
Audio streams as it is generated, so a `wav` response starts before its length is known. Its RIFF and data chunk size fields are set to `0xFFFFFFFF`, a standard marker for streamed WAV. Most players and decoders read it; if a strict parser rejects the file, rewrite the size fields from the file length.
</Note>

## Streaming

With the default `stream_format`, audio bytes arrive as they are generated, so you can play them before synthesis finishes. Read the response incrementally instead of waiting for the whole body — for example with `with_streaming_response` in Python, or by reading `response.body` in Node.

With `stream_format` set to `sse`, the response is a stream of server-sent events. Each `speech.audio.delta` event carries a base64-encoded audio chunk in `audio`, and the stream ends with a `speech.audio.done` event:

```
data: {"type":"speech.audio.delta","audio":"SUQzBAAAAAAAI1RTU0UAAAAPAAAD..."}

data: {"type":"speech.audio.done"}
```

## Errors

Errors use OpenAI's error format, so the OpenAI SDKs raise their usual exception types:

```json
{
  "error": {
    "message": "speed must be between 0.5 and 1.5",
    "type": "invalid_request_error",
    "param": "speed",
    "code": null
  }
}
```

| Status | `type` | Cause |
| --- | --- | --- |
| `400` | `invalid_request_error` | A missing or invalid parameter. `param` names the parameter when the request failed validation. |
| `401` | `authentication_error` | A missing API key, or an invalid key sent as `Bearer`. |
| `402` | `insufficient_quota` | No credits remain on the account. |
| `403` | `permission_error` | An invalid key sent as `Basic`, or a key without access to the request. |
| `404` | `invalid_request_error` | The voice was not found. |
| `429` | `rate_limit_error` | A [rate limit](https://docs.inworld.ai/resources/rate-limits.md) or [concurrency limit](https://docs.inworld.ai/resources/concurrency-limits.md) was exceeded. |
| `5xx` | `api_error` | A server error. Retry with backoff. |

## Billing and limits

Requests are billed per character and share [rate limits](https://docs.inworld.ai/resources/rate-limits.md) and [concurrency limits](https://docs.inworld.ai/resources/concurrency-limits.md) with the [Streaming API](https://docs.inworld.ai/tts/synthesize-speech-streaming.md).

## Features that need the native API

The OpenAI format has no fields for some Inworld TTS features. Use the [native TTS API](https://docs.inworld.ai/tts/synthesize-speech.md#choose-a-synthesis-method) for:

- [Timestamps](https://docs.inworld.ai/tts/capabilities/timestamps.md) for captions and lipsync
- Sample rate, bit rate, and other audio encoding settings
- [Synthesis context](https://docs.inworld.ai/tts/best-practices/generating-speech.md#give-the-model-conversation-context) from earlier conversation turns
- Explicit language selection, [delivery mode](https://docs.inworld.ai/tts/best-practices/generating-speech.md#tune-the-delivery-mode), temperature, and text normalization settings
- [Async](https://docs.inworld.ai/tts/synthesize-speech-async.md) and [batch](https://docs.inworld.ai/tts/synthesize-speech-batch.md) jobs
