Skip to main content

Realtime

Knox proxies OpenAI’s Realtime API for gpt-realtime-2.1 and gpt-realtime-2.1-mini. Clients connect to Knox with a Knox API key. Knox authenticates, picks an upstream OpenAI channel, and relays the live session.

This is a speech-to-speech voice-agent endpoint: audio and text in, audio and text out, with tool use and configurable reasoning. It is not /v1/chat/completions. For one-shot audio files in chat, see Audio Inputs.

All authenticated requests use the same base URL and API key as the rest of Knox:

https://api.knox.chat/v1
Authorization: Bearer sk-...

The same routes are also mounted under /api/v1. Query ?model= is optional; Knox defaults to gpt-realtime-2.1.

Endpoints

PathTransportPurpose
GET / POST /v1/realtimeWebSocketConversation session (billed)
POST /v1/realtime/client_secretsHTTP JSONMint a Knox-scoped ephemeral key (ek_knox_…)
POST /v1/realtime/callsHTTP SDP / JSONWebRTC SDP exchange with upstream OpenAI

Typical flow:

  1. Connect — GET /v1/realtime over WebSocket (recommended billed path)
  2. Optionally mint a browser key — POST /v1/realtime/client_secrets
  3. Optionally exchange SDP — POST /v1/realtime/calls (WebRTC; billing is incomplete)

You can also use OpenAI: GPT Realtime 2.1 or OpenAI: GPT Realtime 2.1 Mini in the Knox.Chat UI or KnoxStudio: select the model, pick an API key, and click the microphone.

Confirm the model is listed:

curl -s https://api.knox.chat/v1/models \
-H "Authorization: Bearer $KNOXCHAT_API_KEY" \
| jq '.data[] | select(.id|test("realtime"))'

You should see gpt-realtime-2.1 and gpt-realtime-2.1-mini with input modalities text, audio, image and output text, audio.

Authentication

Knox tokens use the same sk- prefix as the rest of /v1.

ClientHow to send the key
Server (Node ws, Python, etc.)Authorization: Bearer sk-…
Anthropic-style HTTPx-api-key: sk-…
Browser WebSocketsubprotocol openai-insecure-api-key.sk-… plus realtime
Knox-issued ephemeralAuthorization: Bearer ek_knox_… (valid ~60s, then connect)

On WebSocket upgrades only, ?api_key= or ?authorization= is accepted if headers/subprotocol are missing.

Browser native WebSocket cannot set HTTP headers:

const ws = new WebSocket(
"wss://api.knox.chat/v1/realtime?model=gpt-realtime-2.1",
["realtime", "openai-insecure-api-key.sk-YOUR_KNOX_KEY"]
);

Knox echoes Sec-WebSocket-Protocol: realtime when the client requested it.

Safety identifiers: Knox hashes the Knox user id and sends OpenAI-Safety-Identifier on the upstream connection. You do not need to set this yourself. Do not send OpenAI-Beta: realtime=v1. This is the GA interface.

Minimum balance to start a session: > $0.01. The token must allow all models (* / __ALL_MODELS__) or list the realtime model you call (gpt-realtime-2.1 and/or gpt-realtime-2.1-mini).

Server-to-server WebSocket

This is the fully billed path. Knox sits between your process and wss://api.openai.com/v1/realtime.

import WebSocket from "ws";

const ws = new WebSocket(
"wss://api.knox.chat/v1/realtime?model=gpt-realtime-2.1",
{
headers: {
Authorization: "Bearer " + process.env.KNOX_API_KEY,
},
}
);

ws.on("open", () => {
ws.send(
JSON.stringify({
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2.1",
instructions: "You are a concise voice assistant.",
output_modalities: ["audio"],
audio: {
input: {
format: { type: "audio/pcm", rate: 24000 },
turn_detection: {
type: "semantic_vad",
eagerness: "high",
create_response: true,
interrupt_response: true,
},
},
output: {
format: { type: "audio/pcm", rate: 24000 },
voice: "marin",
},
},
reasoning: { effort: "low" },
},
})
);
});

ws.on("message", (data) => {
const event = JSON.parse(data.toString());
console.log(event.type, event);
});

Hitting /v1/realtime without Upgrade: websocket returns 400.

Sending audio

Realtime expects base64 PCM16 mono 24 kHz in input_audio_buffer.append. With semantic_vad, you do not need to call input_audio_buffer.commit or response.create for normal turn-taking.

{ "type": "input_audio_buffer.append", "audio": "<base64 pcm16>" }

Useful server events (GA names):

EventMeaning
session.createdSession is live
response.output_audio.deltaBase64 PCM chunk to play
response.output_audio_transcript.deltaAssistant transcript stream
conversation.item.input_audio_transcription.completedUser transcript
response.doneTurn finished; includes usage (Knox bills this)
errorClient or server error

Beta names (response.audio.delta, response.audio_transcript.delta) still work if an older client sends them.

Session config and prompting

Start with reasoning.effort low. Higher effort increases latency and output tokens.

Knox defaults when you do not send your own session:

  • session.type: realtime
  • output_modalities: ["audio"]
  • input audio: PCM 24 kHz, semantic_vad with eagerness: high and barge-in
  • output voice: marin at PCM 24 kHz
  • reasoning.effort: low
{
"type": "session.update",
"session": {
"type": "realtime",
"model": "gpt-realtime-2.1",
"instructions": "Speak briefly. Confirm before taking actions.",
"output_modalities": ["audio"],
"reasoning": { "effort": "low" },
"truncation": {
"type": "retention_ratio",
"retention_ratio": 0.8,
"token_limits": { "post_instructions": 8000 }
}
}
}

OpenAI’s prompting guide covers preambles, unclear audio, exact entity capture, and tool use. Tools are standard Realtime function tools over the same event stream (response.function_call_arguments.delta, then your conversation.item.create with the tool result).

Ephemeral keys and WebRTC

POST /v1/realtime/client_secrets

Authenticated with a Knox API key. Returns a Knox secret, not an OpenAI ek_ key.

curl -s https://api.knox.chat/v1/realtime/client_secrets \
-H "Authorization: Bearer $KNOXCHAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"session": {
"type": "realtime",
"model": "gpt-realtime-2.1",
"audio": { "output": { "voice": "marin" } }
}
}'

Response shape:

{
"value": "ek_knox_…",
"expires_at": 1750000000,
"session": { "type": "realtime", "model": "gpt-realtime-2.1" }
}

TTL is 60 seconds. Use value immediately on Knox. Raw OpenAI ephemeral keys are never returned, so the browser cannot bypass Knox billing on the WebSocket path. Secrets are in-process; they are not shared across replicas.

POST /v1/realtime/calls

Send either Content-Type: application/sdp with the raw offer SDP, or Content-Type: application/json with { "sdp": "…", "session": { … } }. Knox forwards the SDP to OpenAI and returns the answer SDP.

multipart/form-data is not accepted.

After SDP succeeds, WebRTC media goes directly to OpenAI. Prefer WebSocket when you need accurate Knox billing or hosted web search fulfillment.

gpt-realtime-2.1 and gpt-realtime-2.1-mini do not host OpenAI’s built-in Responses web_search tool. On the billed WebSocket path, Knox enables live search by:

  1. Registering a web_search function tool on the session (plus instructions to use it for current events).
  2. Calling OpenAI POST /v1/responses with { "type": "web_search" } when the model invokes that function.
  3. Returning function_call_output and response.create so the voice model can speak a sourced answer.

Direct /v1/realtime clients get the same injection on session.update (and a fallback update after session.created if they never send one). Knox microphone sessions do this automatically.

Pass "web_search": false on session.update to opt out. If you already define your own function named web_search, Knox leaves it alone and does not intercept.

Search uses gpt-4.1-mini (falling back to gpt-4o-mini / gpt-4.1) on the same OpenAI channel, with search_context_size: medium. Tool calls are billed at OpenAI’s web search rate ($10 / 1k calls) plus the search model’s tokens. /activity shows these as a Search line.

WebRTC /v1/realtime/calls still injects the tool into the SDP session, but media goes directly to OpenAI, so Knox cannot fulfill the function call.

This is separate from chat :online variants and Anthropic web_search on /v1/chat/completions.

Billing

Charged on each response.done (and input-transcription usage events when present), using the OpenAI Realtime cost model. Group ratio applies. Rates match OpenAI’s published cards (USD per 1M tokens).

gpt-realtime-2.1:

InputCached inputOutput
Text$4.00$0.40$24.00
Audio$32.00$0.40$64.00
Image$5.00$0.50

gpt-realtime-2.1-mini:

InputCached inputOutput
Text$0.60$0.06$2.40
Audio$10.00$0.30$20.00
Image$0.80$0.08

Knox reads response.usage.input_token_details / output_token_details (including cached_tokens_details) so text, audio, and image are priced separately. Cached tokens are a subset of input tokens and bill at the cached rate. /activity shows those three lines (T / A / I).

Realtime does not follow the chat-completions “no output tokens → no charge” rule. Input-only or transcription-only turns still bill. Any billable realtime event with usage is charged at least $0.0001.

Input transcription, when enabled, is billed from conversation.item.input_audio_transcription.completed on the transcribe rate card, not speech-to-speech. Knox defaults to gpt-4o-mini-transcribe at 1.25/1.25 / 5.00 per 1M audio-in / text-out tokens (gpt-4o-transcribe is 2.50/2.50 / 10.00 if the session selects it).

Audio is ~1 token per 100 ms of user audio and ~1 token per 50 ms of assistant audio. Later turns include prior conversation items, so cost grows unless you truncate or delete old items.

Limits

  • /v1/realtime is conversation / voice-agent only. Translation (/v1/realtime/translations) and dedicated transcription sessions are not implemented.
  • Chat Completions and Responses are not supported for this model on OpenAI’s side either.
  • Max Realtime session length is OpenAI’s 60 minutes.
  • Multi-instance ephemeral secrets are in-process; they are not shared via Redis.
  • WebRTC is supported for SDP, not for Knox-accurate token billing.

Troubleshooting

Handshake never upgrades

  • Hitting /v1/realtime without Upgrade: websocket returns 400.
  • Missing/invalid Knox key → 401.
  • Browser: include both realtime and openai-insecure-api-key.sk-… subprotocols.

Session opens but no audio

  • Client must send PCM16 24 kHz, not WAV/MP3/WebM.
  • Watch for error events (input_audio_buffer.append rejected, invalid event shape).
  • semantic_vad waits for end of speech; Knox defaults to eagerness: high so replies start sooner.

Ephemeral key rejected immediately

ek_knox_… lives in memory on that Knox process for 60 seconds. A different replica, a restart, or waiting too long invalidates it.

Quick connectivity check

curl -i https://api.knox.chat/v1/realtime \
-H "Authorization: Bearer $KNOXCHAT_API_KEY"
# expect 400: requires WebSocket

curl -i https://api.knox.chat/v1/realtime/client_secrets \
-H "Authorization: Bearer $KNOXCHAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"session":{"type":"realtime","model":"gpt-realtime-2.1"}}'

API Reference