Skip to content

Realtime speech

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.

/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.

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

Terminal window
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.

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.

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

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.

/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:

Terminal window
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.

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.

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.