# Realtime speech

> Low-latency speech-to-speech over a WebSocket at /v1/realtime: the handshake, authentication, the OpenAI Realtime event protocol, and model selection.

Realtime runs a speech-to-speech conversation over a single WebSocket — audio in, audio out, with the model answering as you speak. MirAPI exposes it at `/v1/realtime` in the OpenAI Realtime format, so clients and SDKs written for the OpenAI Realtime API work after you point them at the MirAPI endpoint.

:::note
The examples use `deepseek-chat` as a placeholder. Realtime needs a model that can produce audio output, so replace it with the exact name of a realtime-capable model from your catalogue — see "Pick a model" below.
:::

## Connect

`/v1/realtime` opens a **WebSocket** connection — it is not a plain HTTP call. The client sends an HTTP upgrade request, and the server answers `101 Switching Protocols` before any events flow.

| Element | Value |
|---|---|
| URL | `https://api.mirapi.ai/v1/realtime?model=<realtime-model>` — use the `wss://` scheme |
| Auth | `Authorization: Bearer <key>` header on the WebSocket handshake |
| Model | A realtime-capable model from the catalogue (see "Pick a model") |

MirAPI follows the OpenAI-compatible base URL convention, so the endpoint is `https://api.mirapi.ai/v1/realtime`. If your client cannot set custom headers on the handshake, use one that can — the key must be sent in the standard bearer header.

## Verify the handshake

A WebSocket handshake is an HTTP upgrade request. You can verify connectivity without a full client by sending the upgrade headers yourself:

```bash
curl -i -N "https://api.mirapi.ai/v1/realtime?model=deepseek-chat" \
  -H "Authorization: Bearer $MIRAPI_API_KEY" \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ=="
```

A `101 Switching Protocols` response means the socket is open. Any other status is a rejection — see "Errors and retries" for the codes.

## Session events

Once the socket is open, both sides exchange JSON events from the OpenAI Realtime protocol. MirAPI relays the same events unchanged:

| Event | What it does |
|---|---|
| `session.update` | Configure the session, for example its `modalities` |
| `conversation.item.create` | Add an item to the conversation |
| `input_audio_buffer.append` | Stream audio chunks into the input buffer |

For the full message set, follow the OpenAI Realtime API documentation.

## Python example

Any WebSocket client that speaks the protocol works. This example uses `websockets` to connect, configure the session, and read the first reply:

```python
import asyncio
import os
import websockets

async def main():
    url = "https://api.mirapi.ai/v1/realtime?model=deepseek-chat".replace("https://", "wss://")
    headers = {"Authorization": f"Bearer {os.environ['MIRAPI_API_KEY']}"}

    async with websockets.connect(url, additional_headers=headers) as ws:
        # Configure the session, then stream audio in and out.
        await ws.send('{"type": "session.update", "session": {"modalities": ["audio", "text"]}}')
        reply = await ws.recv()
        print(reply)

asyncio.run(main())
```

Replace `deepseek-chat` with a realtime-capable model before running.

## Pick a model

`/v1/realtime` requires a model that can output audio in real time. Find one with `GET /v1/models` — look for `audio` in the entry's `output_modalities` — and send its exact `id` as the `model` query parameter:

```bash
curl https://api.mirapi.ai/v1/models \
  -H "Authorization: Bearer $MIRAPI_API_KEY"
```

A text-only model cannot open a realtime session: it is rejected because it cannot produce the audio output the session expects. Catalogue entries marked **Coming soon** cannot be called yet.

## Billing and logs

Realtime sessions are billed like any other request, and charges accrue as the session runs. As with other endpoints, the API does not return a monetary amount — reconcile costs in three places:

- the console billing log,
- `GET /api/usage/token` for the account-level summary,
- `GET /api/log/token` for per-request detail.

If the prepaid balance runs out, the request is rejected with `403`; top up and the same key works again immediately.

## Errors and retries

Failures before the socket opens arrive as HTTP status codes on the handshake:

| Status | Meaning | Action |
|---|---|---|
| 401 | Key missing or invalid | Fix the credentials |
| 403 | Valid key but rejected — balance, whitelist, quota, or IP | Resolve the cause and retry |
| 413 | Request body too large | Reduce the payload |
| 429 | Rate limited | Exponential backoff with jitter; no `Retry-After` header |
| 500 | Gateway or upstream failure | Retry idempotent requests |

MirAPI does **not** retry on your behalf. Every error message ends with a request ID — include it when you open a ticket.

## Related links

- [Speech & audio](/docs/guides/audio) — transcription, translation, and text-to-speech endpoints
- [Configuration](/docs/configuration) — authentication and base URLs
- [Models & pricing](/docs/models) — find a realtime-capable model in the catalogue
- [Errors](/docs/api-reference/errors) — status codes, envelopes, and request IDs