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

# Realtime voice sessions

> Live speech-to-speech voice agents over WebSocket — mint a signed session credential from the gateway, connect directly to the voice relay, billed per minute of audio.

<Note>
  Realtime voice is **available on request**. Contact your AI Reserve representative to have `realtimeVoiceEnabled`
  turned on for your organization — until then, session mints return `403`. In environments where the voice relay is not
  deployed they return `503`.
</Note>

Realtime voice gives your application a live speech-to-speech session with xAI Grok Voice over a
WebSocket. Billing, budgets, and usage reporting are handled by AI Reserve the same way as every
other surface.

The gateway acts as a **voice-bound relay**, not a generic LiteLLM passthrough. When you mint a
session you select a voice (built-in or your organization's custom voice); that voice is locked
at mint and injected server-side the moment the upstream WebSocket opens. Your AI Reserve API
key never travels to the WebSocket — only the short-lived ephemeral key does.

## How it works

Because a WebSocket cannot ride the normal request/response gateway, the flow has two steps:

1. **Mint** a session credential — one API call that authenticates you, reserves funds, selects
   the voice, and returns a short-lived `ephemeral_key` and `wss_url`.
2. **Connect** your WebSocket directly to `wss_url` using `ephemeral_key`. Drive the session
   with audio I/O events only.

## Step 1: Mint a session

`POST /v1/realtime/sessions` authenticates the caller, checks the org gate, resolves voice
ownership, reserves worst-case session funds from your wallet, and returns the credentials.

The `voice` parameter selects which voice the relay will use for this session. Pass:

* Any **built-in voice ID** from `GET /v1/voices` (e.g. `"ara"`, `"atlas"`, `"luna"`) — no
  extra org gate required.
* One of **your organization's custom voice IDs** from `GET /v1/custom-voices` — requires the
  `customVoicesEnabled` org gate in addition to `realtimeVoiceEnabled`.

`voice` defaults to `"ara"` when omitted.

<CodeGroup>
  ```bash cURL theme={"dark"}
  curl -X POST https://api.aireserve.com/v1/realtime/sessions \
    -H "Authorization: Bearer $AIRESERVE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"model": "grok-voice", "voice": "ara"}'
  ```

  ```typescript TypeScript theme={"dark"}
  const response = await fetch("https://api.aireserve.com/v1/realtime/sessions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.AIRESERVE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ model: "grok-voice", voice: "ara" }),
  });
  const session = await response.json();
  ```

  ```python Python theme={"dark"}
  import os, requests

  session = requests.post(
      "https://api.aireserve.com/v1/realtime/sessions",
      headers={"Authorization": f"Bearer {os.environ['AIRESERVE_API_KEY']}"},
      json={"model": "grok-voice", "voice": "ara"},
  ).json()
  ```
</CodeGroup>

A successful mint returns `201`:

```json theme={"dark"}
{
  "session_id": "9c3f1a2b-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "model": "grok-voice",
  "voice": "ara",
  "wss_url": "wss://voice-relay-uat-abcdefg-ue.a.run.app/v1/realtime?model=grok-voice",
  "ephemeral_key": "v1.1756946700.ABCDEFGHIJKLMNOPQRSTuvwxyz01234.signature_base64url_43chars",
  "expires_at": "2026-09-03T21:05:00.000Z",
  "key_ttl_seconds": 300,
  "max_session_seconds": 3540,
  "browser_subprotocols": [
    "openai-beta.realtime-v1",
    "openai-insecure-api-key.v1.1756946700.ABCDEFGHIJKLMNOPQRSTuvwxyz01234.signature_base64url_43chars"
  ]
}
```

The `ephemeral_key` is an **HMAC-SHA-256 signed, opaque, one-use relay ticket** — not an API
key. The token itself encodes only an expiry timestamp and a random nonce; it does **not**
carry a voice ID. Voice authorization lives in the session record: the relay stores the
authorized voice and ticket state in a DB row keyed by the ticket's SHA-256 hash at mint time.
The ticket is valid for 5 minutes and consumed on first use — a replay is rejected even within
the TTL. Mint a new session for each conversation; do not log or share the key.

## Step 2: Connect the WebSocket

**From a server** — pass the key as a bearer token header:

```text theme={"dark"}
GET <wss_url returned by POST /v1/realtime/sessions>
Authorization: Bearer <ephemeral_key>
```

**From a browser** — browsers cannot set WebSocket headers; pass the `browser_subprotocols`
array from the mint response verbatim:

```javascript theme={"dark"}
const ws = new WebSocket(session.wss_url, session.browser_subprotocols);
```

The relay verifies the HMAC signature and expiry, reads the session record by the ticket's
SHA-256 hash to retrieve the authorized voice (and atomically marks the ticket consumed to
prevent replay), then opens a connection to the xAI Grok Voice engine and sends an initial
`session.update` event with the authorized voice before forwarding any client audio. The voice
is bound in the session record — the client cannot change it mid-session.

## Supported WebSocket events

The relay enforces an **audio-only event allowlist**. Only the following client→relay events
are accepted; anything else closes the connection with code `1008`:

| Event                       | Purpose                                                                        |
| --------------------------- | ------------------------------------------------------------------------------ |
| `input_audio_buffer.append` | Send a chunk of PCM audio (base64-encoded)                                     |
| `input_audio_buffer.commit` | Mark the current audio turn as complete                                        |
| `input_audio_buffer.clear`  | Clear the uncommitted audio buffer                                             |
| `response.create`           | Request a response from the model                                              |
| `response.cancel`           | Cancel an in-progress response                                                 |
| `session.update`            | Update session options (`instructions`, `turn_detection`, `audio` format only) |

`session.update` is additionally sanitized: only `instructions`, `turn_detection`, and `audio`
fields are forwarded. `voice` is always forced to the value selected at mint regardless of what
the client sends.

Relay→client messages are forwarded as-is from the xAI engine.

<Note>
  **Text output, transcription, tool calling, and session resumption are not supported in this release.** Attempting to
  enable transcription or tools via `session.update` will not work — those fields are stripped. See [Deferred
  features](#deferred-features).
</Note>

## Available voices

Pass a supported built-in voice ID or one of your organization's custom voice IDs (requires
`customVoicesEnabled`). The default when `voice` is omitted is `ara`. Use `GET /v1/voices` to
retrieve the current catalog with names, languages, and genders; catalog additions are enabled on
the synthesis surfaces after AI Reserve verifies and deploys them.

To create custom voice clones for your organization, see [Text-to-speech](/capabilities/text-to-speech).

## Billing and limits

| Item                      | Detail                                                                                                                                                                                                                                                                                                 |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Pricing unit**          | Per audio minute at the published `grok-voice` rate                                                                                                                                                                                                                                                    |
| **Wallet reserve**        | Worst-case session cost (`max_session_seconds` = 3,540 s) is reserved at mint; normally the recorded session duration is settled and the difference released. If relay lifecycle telemetry is incomplete, settlement uses a conservative bounded estimate and marks the ledger row for reconciliation. |
| **Unused credentials**    | An unused (never-connected) ephemeral key releases its wallet reservation automatically                                                                                                                                                                                                                |
| **Mint requires balance** | `402` is returned when the wallet cannot cover the worst-case session                                                                                                                                                                                                                                  |
| **Session ceiling**       | One session runs at most 3,540 seconds; mint a new session to continue                                                                                                                                                                                                                                 |
| **Key TTL**               | The ephemeral key accepts new WebSocket upgrades for 5 minutes (`key_ttl_seconds` = 300); connect promptly after minting                                                                                                                                                                               |

Usage appears on your ledger and dashboards like any other model call (unit: audio seconds) and
counts toward the same wallet and budget caps.

## Audio persistence

AI Reserve does **not** persist audio from realtime sessions. Session metadata (duration, cost,
voice ID, session ID) is stored for billing and usage reporting; no audio content is retained.

## Enable realtime voice

1. Contact your AI Reserve representative (or the Help page in the portal) to enable
   `realtimeVoiceEnabled` for your organization.
2. Optionally enable `customVoicesEnabled` if you want to use custom voice clones in sessions.
3. Mint a session and connect — the two-step flow above is the full integration.
4. Watch your usage dashboard: each session appears as one audio usage row with duration and cost.

## Deferred features

The following are **not available in this release**:

* **Standalone speech-to-text**: transcription calls without voice synthesis. See
  `POST /v1/audio/transcriptions` for the existing OpenAI-compatible transcription endpoint.
* **Streaming TTS**: text-in, audio-out streaming without a microphone. See
  `POST /v1/audio/speech` for synchronous (non-streaming) TTS with `grok-tts`.
* **SIP / telephony integration**: direct PSTN or SIP dial-in/dial-out.
* **Tool calling in realtime sessions**: function calling over the realtime session is not
  available in this release.
* **Session resumption**: each `POST /v1/realtime/sessions` mint starts a fresh session;
  reconnecting to a prior session is not supported.
