# OpenAI compatibility

Use the OpenAI SDKs with EESI by changing the base URL and the key.

Speech, transcription and the Realtime API follow the OpenAI wire format,
including the error envelope. An existing integration usually moves with two
lines.

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

client = OpenAI(
    api_key=os.environ["EESI_API_KEY"],
    base_url="https://api.eesi.ai/v1",
)

speech = client.audio.speech.create(
    model="nur-v1",
    voice="ev_d116d96b",
    input="Hello from EESI.",
)
speech.write_to_file("hello.mp3")
```

```typescript TypeScript
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.EESI_API_KEY,
  baseURL: "https://api.eesi.ai/v1",
});

const speech = await client.audio.speech.create({
  model: "nur-v1",
  voice: "ev_d116d96b",
  input: "Hello from EESI.",
});
```
</CodeGroup>

## Endpoints

| OpenAI endpoint | EESI | Model |
| --- | --- | --- |
| `POST /v1/audio/speech` | Same | `nur-v1` |
| `POST /v1/audio/transcriptions` | Same | `nur-v1` |
| `WS /v1/realtime` | Same | `nur-v1` |
| `POST /v1/realtime/calls` (WebRTC) | Same | `nur-v1` |
| `POST /v1/chat/completions` | Same | `nur-v1` |
| `GET /v1/models` | Same | |

OpenAI's model names still resolve to Nur, so a ported app runs before
you touch it. Switch to `nur-v1` before you ship, so a
future model change is your decision.

## Three differences to handle

**Voices are ids.** OpenAI has a fixed set of named voices. Here every voice,
built-in or cloned, has an id like `ev_d116d96b`; list them with
`GET /v1/voices`. OpenAI names such as `alloy` still work and map to the
default voice, so they do not pick different voices. An unknown name is a
`400`, never audio in the wrong voice.

**Audio formats.** `nur-v1` returns `mp3` (the default), `wav` or `pcm`.
Other formats are a `400` that lists the ones you can use.

**Realtime events.** The server sends the current OpenAI event names, such as
`response.output_audio.delta`. It also sends a few `eesi.*` events (a session
id for reconnecting, a synthetic-audio notice, a recording disclosure). An
OpenAI client can ignore them safely.

## What EESI adds

Extra fields on the same requests, ignored by clients that don't send them:

| Field | On | What it does |
| --- | --- | --- |
| `language` | `/v1/audio/speech` | Sets the language explicitly. |
| `instructions` | `/v1/audio/speech` | Describes a voice instead of naming one, such as "female, low pitch, british accent". |
| `normalize_text` | `/v1/audio/speech` | Reads numbers, dates and currency as words. |

Every audio response carries an `X-Synthetic-Audio` header declaring it
machine-generated.

Some features have no OpenAI equivalent, so you call them directly with the
same key: [voice cloning](/speech/voices),
[Live translation](/realtime/translate) and
[Audio intelligence](/speech/audio-intelligence).
