---
title: Realtime
description: Learn how to build realtime voice conversations with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/realtime"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Realtime is an experimental feature.

This guide covers legacy token-based, turn-based realtime conversations over
WebSockets. These sessions run in the browser and connect
directly to the provider using a short-lived token that you create on your
server. You can also route the connection through [AI Gateway](/providers/ai-sdk-providers/ai-gateway#realtime).

For OpenAI Live's continuous JSON/PCM16 WSS relay runtime and application-handled
client delegation, see
[`experimental_useRealtime`](/docs/reference/ai-sdk-ui/use-realtime#continuous-conversations).

For Mistral's managed microphone-to-speaker conversations, use
[`mistral.experimental_realtime()`](/providers/ai-sdk-providers/mistral#realtime-voice-agents)
with `api.provider`. Its provider-owned LiveKit transport handles audio, and
your server creates and closes the sessions.

The typical flow is:

1. The browser calls your setup endpoint.
2. Your server creates a short-lived realtime token with `experimental_realtime.getToken()`.
3. The browser opens a WebSocket connection to the provider or AI Gateway.
4. The model streams audio, text, and tool calls back to the browser.
5. Tool calls are handled by your application with `onToolCall`.

For continuous **OpenAI Live** conversations, use an application-owned WebSocket
relay or optional WebRTC via `api.session`. Live uses client delegation: your
application handles delegated work and submits context rather than using the
turn-based tool loop below. See the
[`experimental_useRealtime` reference](/docs/reference/ai-sdk-ui/use-realtime)
for SDP setup, server-owned permissions, capture ownership, and graceful close.

## Server-owned connections

A trusted server can own the WebSocket instead of minting a browser token. Check
`model.capabilities?.connections` for `server-websocket` and obtain the server-only
settings with `await model.getServerWebSocketConfig?.({ intent: 'conversation' })`.
OpenAI Realtime, Google, xAI and AI Gateway support this connection, alongside the
existing OpenAI Live adapter. Returned headers can contain long-lived credentials;
keep the descriptor on your server. The application owns socket lifetime,
authorization, buffering, and reconnect policy.

```ts
import { openai } from '@ai-sdk/openai';
import { WebSocket } from 'ws';

const model = openai.experimental_realtime('gpt-realtime-2.1', {
  api: 'realtime',
});
const options = { intent: 'conversation' } as const;
const { url, headers, protocols } =
  await model.getServerWebSocketConfig(options);
const socket = new WebSocket(url, protocols, { headers });
```

Feed incoming JSON to a fresh `model.createServerEventParser()` when the model
provides it, using one parser for each connection and discarding it on disconnect.
Stateless models can use `model.parseServerEvent(raw)` directly. Serialize outgoing normalized events with
`await model.serializeClientEvent(event, options)`. Use the same `intent` when
building a session configuration with `model.buildSessionConfig(config, options)`.
A complete Node example is in
[`server.ts`](https://github.com/vercel/ai/blob/main/examples/ai-functions/src/realtime/openai/server.ts).

The default intent is `conversation`. OpenAI Realtime also supports
`transcription`, which selects its dedicated endpoint and session shape. Set
`inputAudioTranscription.model` explicitly when the factory model is a conversation
model; otherwise the factory model ID is used for transcription. Instructions,
voice, tools and output settings are rejected in transcription sessions. Google,
xAI, AI Gateway and OpenAI Live reject a non-conversation intent. Google Live
translation remains a session provider option, rather than a connection intent.

### Usage snapshots

Normalized completion events can carry optional `usage` with gross `inputTokens`
and `outputTokens`, available text/audio buckets, cached input portions, and
`seconds` for duration-billed transcription. Cached counts are subsets of gross
input counts. Modality buckets are subsets of totals, so do not add them to those
totals. Unknown counts remain absent, and totals can include other modalities.
The public types are `Experimental_RealtimeServerConnection`,
`Experimental_RealtimeSessionIntent` and `Experimental_RealtimeUsage` from `ai`.

OpenAI Realtime and xAI expose reported usage on `response-done` and input
transcription completions. AI Gateway passes through normalized usage only when
the Gateway route includes it in its events.
OpenAI Live retains its existing duration snapshots on `session-usage` and
`session-closed`.

Google attaches usage to `response-done` only when the same wire frame contains
`turnComplete` and `usageMetadata`. Otherwise it emits an existing `custom` event
with `rawType: 'usageMetadata'` and typed `usage`. Google gross input totals include
reported tool-use prompt counts, and gross output totals include reported thinking
counts. These are provider snapshots;
they do not repeat completion, imply a response association, or represent increments
to sum. A later snapshot can revise an earlier one. The original provider payload
remains available in `raw`.

## Setup Endpoint

Create a setup endpoint that returns a short-lived token for the realtime
provider. This endpoint can also attach tool definitions to the session.

```ts title='app/api/realtime/setup/route.ts'
import { openai } from '@ai-sdk/openai';
import { experimental_getRealtimeToolDefinitions, tool } from 'ai';
import { z } from 'zod';

const tools = {
  getWeather: tool({
    description: 'Get the current weather for a city',
    inputSchema: z.object({
      city: z.string().describe('The city to get weather for'),
    }),
  }),
};

export async function POST(request: Request) {
  const body = await request.json().catch(() => ({}));
  const toolDefinitions = await experimental_getRealtimeToolDefinitions({
    tools,
  });

  const token = await openai.experimental_realtime.getToken({
    model: 'gpt-realtime',
    sessionConfig: {
      ...body.sessionConfig,
      tools: toolDefinitions,
    },
  });

  return Response.json({
    ...token,
    tools: toolDefinitions,
  });
}
```

In production, authenticate and rate-limit your setup endpoint. It creates
realtime sessions using your server-side API key.

## AI Gateway

Use [AI Gateway](/providers/ai-sdk-providers/ai-gateway) when you want the same
realtime client code to work across supported upstream providers. The Gateway
normalizes realtime events server-side, and the browser still receives only a
short-lived client secret.

Create the short-lived Gateway realtime token from a server-side setup endpoint:

```ts title='app/api/realtime/setup/route.ts'
import { gateway } from 'ai';

export async function POST() {
  const token = await gateway.experimental_realtime.getToken({
    model: 'openai/gpt-realtime-2',
  });

  return Response.json(token);
}
```

Then use the matching Gateway realtime model in the browser:

```tsx title='app/realtime/page.tsx'
'use client';

import { experimental_useRealtime } from '@ai-sdk/react';
import { gateway } from 'ai';

const model = gateway.experimental_realtime('openai/gpt-realtime-2');
const sessionConfig = {
  instructions: 'You are a helpful assistant. Be concise.',
  inputAudioTranscription: {},
  voice: 'alloy',
  turnDetection: { type: 'server-vad' as const },
};

export default function RealtimePage() {
  const realtime = experimental_useRealtime({
    model,
    api: {
      token: '/api/realtime/setup',
    },
    sessionConfig,
  });

  // ...
}
```

`gateway.experimental_realtime.getToken()` must run on your server because it
uses your Gateway credential to mint a `vcst_` client secret. Creating the
realtime model with `gateway.experimental_realtime()` is safe in the browser.

Tool definitions work the same way with AI Gateway: convert AI SDK tools with
`experimental_getRealtimeToolDefinitions()` in your setup endpoint and return
the definitions alongside the token. The hook includes them in the session
update after the WebSocket opens.

## Client Session

Use the `experimental_useRealtime` hook to connect to a realtime model, capture
microphone audio, play model audio, send text messages, and render messages.

```tsx title='app/realtime/page.tsx'
'use client';

import { openai } from '@ai-sdk/openai';
import { experimental_useRealtime } from '@ai-sdk/react';

const model = openai.experimental_realtime('gpt-realtime');
const sessionConfig = {
  instructions: 'You are a helpful assistant. Be concise.',
  inputAudioTranscription: {},
  voice: 'alloy',
  turnDetection: { type: 'server-vad' as const },
};

export default function RealtimePage() {
  const realtime = experimental_useRealtime({
    model,
    api: {
      token: '/api/realtime/setup',
    },
    sessionConfig,
  });

  return (
    <div>
      <button onClick={realtime.connect}>Connect</button>
      <button onClick={realtime.disconnect}>Disconnect</button>

      {realtime.messages.map(message => (
        <div key={message.id}>
          <strong>{message.role}</strong>
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}
    </div>
  );
}
```

Keep model and session configuration objects stable across renders. Use module
scope as above, or `useMemo` when configuration depends on props. Replacing either
object replaces the hook's session.

## Tool Calling

Realtime tool execution is client-driven. The provider sends tool calls over the
WebSocket, and your application handles them with `onToolCall`. If the result is
available immediately, return it from `onToolCall`. The SDK sends it back to the
provider as tool output.

For server-backed tools, call an app-specific API endpoint from `onToolCall`.
Avoid generic "execute tool by name" routes. App-specific endpoints are easier
to secure because they can use your normal authentication, authorization,
validation, and rate limiting rules.

### Server-Backed Tool Endpoint

```ts title='app/api/weather/route.ts'
import { z } from 'zod';

const inputSchema = z.object({
  city: z.string(),
});

export async function POST(request: Request) {
  const input = inputSchema.safeParse(await request.json());

  if (!input.success) {
    return Response.json({ error: 'Invalid input' }, { status: 400 });
  }

  return Response.json({
    city: input.data.city,
    temperature: 72,
    condition: 'sunny',
  });
}
```

### Client Tool Handler

```tsx title='app/realtime/page.tsx' {12-26}
import { openai } from '@ai-sdk/openai';
import { experimental_useRealtime } from '@ai-sdk/react';

const model = openai.experimental_realtime('gpt-realtime');

export default function RealtimePage() {
  const realtime = experimental_useRealtime({
    model,
    api: {
      token: '/api/realtime/setup',
    },
    onToolCall: async ({ toolCall }) => {
      if (toolCall.toolName === 'getWeather') {
        const response = await fetch('/api/weather', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(toolCall.args),
        });

        if (!response.ok) {
          throw new Error('Weather lookup failed');
        }

        return response.json();
      }
    },
  });

  // ...
}
```

You can also submit tool output manually with `addToolOutput` when the tool
requires user interaction or another asynchronous process:

```tsx
realtime.addToolOutput(toolCallId, {
  approved: true,
});
```

## Supported Providers

The following providers support the token-based WebSocket flow above:

```ts
import { openai } from '@ai-sdk/openai';
import { google } from '@ai-sdk/google';
import { xai } from '@ai-sdk/xai';

const openaiModel = openai.experimental_realtime('gpt-realtime');
const googleModel = google.experimental_realtime(
  'gemini-3.1-flash-live-preview',
);
const xaiModel = xai.experimental_realtime('grok-voice-latest');
```

You can also route realtime through the [AI Gateway](/providers/ai-sdk-providers/ai-gateway#realtime), which normalizes the
session so the same client code works across upstream providers:

```ts
import { gateway } from '@ai-sdk/gateway';

const gatewayModel = gateway.experimental_realtime('openai/gpt-realtime-2');
```

`gateway.experimental_realtime.getToken()` mints a short-lived Gateway client
secret on your server. The browser uses that token to open the Gateway
WebSocket; the SDK handles the Gateway-specific WebSocket subprotocols for you.
See the [AI Gateway realtime docs](/providers/ai-sdk-providers/ai-gateway#realtime)
for Gateway-specific token and provider option details.

[Mistral voice agents](/providers/ai-sdk-providers/mistral#realtime-voice-agents)
use `mistral.experimental_realtime('mistral-small-latest')` with a managed session
endpoint (`api.provider`). LiveKit is handled internally by the Mistral provider.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)