Skip to main content

Quinn Qwen3.5 Omni Plus Realtime

GET 

/v1/realtime

OpenAI Realtime-style WebSocket for qwen3.5-omni-plus-realtime. Video is sent as JPEG frames, not a raw video stream.

Snapshot alias qwen3.5-omni-plus-realtime-2026-03-15 has the same capability.

Use a platform-issued API token. Do not send OpenAI Sec-WebSocket-Protocol (openai-insecure-api-key.*); this gateway and the upstream both use Bearer.

Connect

This is a WebSocket endpoint (wss://), not an HTTP GET. Do not call it with a normal HTTPS curl.

wss://open-api.fancyai.com/v1/realtime?model=qwen3.5-omni-plus-realtime
Authorization: Bearer YOUR_API_KEY

curl (needs curl 8+ with WebSocket support):

curl --no-buffer \
-H "Authorization: Bearer $API_KEY" \
"wss://open-api.fancyai.com/v1/realtime?model=qwen3.5-omni-plus-realtime"

After the handshake, the server sends session.created.

Python:

import os
import json
import websocket

WS_URL = "wss://open-api.fancyai.com/v1/realtime?model=qwen3.5-omni-plus-realtime"
API_KEY = os.environ["API_KEY"]

def on_open(ws):
print("connected")

def on_message(ws, message):
print(json.dumps(json.loads(message), ensure_ascii=False, indent=2))

ws = websocket.WebSocketApp(
WS_URL,
header=["Authorization: Bearer " + API_KEY],
on_open=on_open,
on_message=on_message,
)
ws.run_forever()

Request

Responses

Switching Protocols — WebSocket established

Configure the session

Send session.update after connect:

{
"event_id": "event_001",
"type": "session.update",
"session": {
"modalities": ["text", "audio"],
"voice": "Tina",
"instructions": "You are a voice assistant. Answer briefly in spoken language.",
"audio": {
"input": { "format": { "type": "pcm", "sample_rate": 16000 } },
"output": { "format": { "type": "pcm", "sample_rate": 24000 } }
},
"turn_detection": {
"type": "semantic_vad",
"threshold": 0.5,
"silence_duration_ms": 800
}
}
}
FieldNotes
modalities["text"] text only; ["text","audio"] text + speech
voiceOutput voice, default Tina
audio.input/output.formatQwen 3.5 Realtime only. Input default pcm/16000, output default pcm/24000. Sample rate: 8000 / 16000 / 24000 / 48000
input_audio_format / output_audio_formatLegacy fields, still accepted
turn_detectionsemantic_vad (recommended) or server_vad. null = manual mode
enable_searchWeb search, off by default. Cannot be used together with tools
toolsFunction tools. Cannot be used together with enable_search

Server replies with session.updated.

End the session with session.finish, or close the WebSocket. Leaving it open keeps accumulating context and billing.

Send audio

Base64-encode PCM/WAV and send:

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

About 100ms per packet is recommended.

VAD mode (semantic_vad / server_vad): the server auto-commits and replies after it detects you finished speaking.

Manual mode (turn_detection is null): after audio, submit and trigger a reply:

{ "type": "input_audio_buffer.commit" }
{ "type": "response.create" }

Cancel the current reply:

{ "type": "response.cancel" }

Send images (video frames)

The model does not accept raw video. Extract JPEG frames and send Base64:

{
"type": "input_image_buffer.append",
"image": "<base64 jpeg>"
}

Constraints:

  • Send input_audio_buffer.append at least once first
  • JPG/JPEG; 480P or 720P recommended, max 1080P
  • Base64 payload ≤ 256KB per frame; raw size ≤ 190KB recommended
  • Live camera: 1 fps (max 2 fps)
  • In VAD mode, finish the current frame before input_audio_buffer.speech_stopped

Audio is the timeline; images are inserted at send time. Camera can be turned on or off at any time.

Text-only input

{
"type": "conversation.item.create",
"item": {
"type": "message",
"role": "user",
"content": [{ "type": "input_text", "text": "What's the weather outside?" }]
}
}

In manual mode, follow with response.create.

Receive replies

Text only:

EventMeaning
response.text.deltaIncremental text
response.text.doneFull text

Text + audio:

EventMeaning
response.audio_transcript.deltaIncremental transcript of speech
response.audio_transcript.doneFull transcript
response.audio.deltaBase64 audio chunk; decode and play
response.audio.doneThis audio segment ended
response.doneTurn finished, includes usage

Other events:

EventMeaning
session.createdConnection ready
session.updatedSession config applied
input_audio_buffer.speech_startedUser started speaking (you may cancel the current reply)
input_audio_buffer.speech_stoppedUser finished speaking
conversation.item.input_audio_transcription.completedUser speech transcribed (if input transcription is on)
errorError

Billing

Billed from usage on each response.done, not by connection duration. In multi-turn chats, audio / image / text still in the window is counted again as this turn's input.

When the reply includes speech, output text is not billed separately (matches gateway settlement).

Singapore list price converted at CNY/USD 6.8:

Itemplus-realtime
Input: text / image / video frame$2.315 / M
Input: audio$18.184 / M
Output: text only$13.666 / M
Output: text + audio (output text not billed)$68.329 / M

Audio conversion (round up to 1 second):

  • Input: seconds × 7
  • Output: seconds × 12.5

Images / video frames: about 1 token per 32×32 pixels, min 4 / max 1280 per image.

Web search is billed extra and is off by default. tools and enable_search cannot both be on.

Limits

plus-realtime
Max audio turns100
Max video (frame) turns50
Max audio duration600 s
Max video (frame) duration240 s
Single session120 minutes

The server disconnects on timeout. Older history is dropped.

Choose a model

Prefer plus for stronger understanding and longer context. For lower latency, use qwen3.5-omni-flash-realtime. This API does not provide WebRTC / AOQ.

Official capability notes: Qwen-Omni realtime. Clients must connect only to this platform /v1/realtime.