۱۰۰ هزار تومان هدیهٔ ثبت‌نام، تا ۱ میلیونبا ثبت‌نام گردونهٔ هدیه را بچرخان: دست‌کم ۱۰۰٬۰۰۰ تومان و تا ۱٬۰۰۰٬۰۰۰ تومان اعتبار هدیه، بدون کارت بانکیگردونهٔ هدیه۱ میلیون برای تو، ۱ میلیون برای دوستتدوستانت را دعوت کن: برای هر دوستی که فعال شود ۱٬۰۰۰٬۰۰۰ تومان اعتبار هدیه می‌گیری و خودش هم ۱٬۰۰۰٬۰۰۰ تومانجوایز
1xAi
مکالمهٔ صوتیِ زنده

صدا، زنده و دوطرفه

این سرویس همان Realtime API است، نه زنجیرهٔ STT → LLM → TTS. رلهٔ ما دو سوکت را نگه می‌دارد و فریم‌ها را دست‌نخورده رد می‌کند؛ کلاینتِ تو مستقیماً با پروتکلِ Realtime حرف می‌زند. به همین دلیل تشخیصِ پایانِ صحبت، قطع‌کردنِ حرفِ مدل، رونوشتِ زنده و function calling رفتارِ اصلیِ مدل‌اند و ما چیزی را بازسازی نکرده‌ایم.

صدا از سرورِ لندنِ ما عبور می‌کند چون مسیرِ مستقیمِ مرورگر به api.openai.com از ایران باز نیست — و همین دلیلِ نبودِ WebRTC است.

[01]

کلید روی سرور، سکرت روی کلاینت

سوکت عمداً کلیدِ API را نمی‌پذیرد. سرورِ تو با کلیدش یک کلاینت‌سکرتِ کوتاه‌عمر می‌گیرد و آن را به فرانت‌اندِ خودش می‌دهد؛ آنچه در مرورگر قرار می‌گیرد فقط اجازهٔ بازکردنِ یک تماس است و دو دقیقه بعد می‌میرد. گم‌شدنِ یک سکرت یعنی یک پنجرهٔ کوتاه روی یک حساب؛ گم‌شدنِ کلید یعنی کیفِ پول.

گرفتنِ کلاینت‌سکرت
bash
curl -X POST https://1xai.ir/v1/realtime/client_secret \
  -H "Authorization: Bearer $ONEXAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-realtime-mini"}'   # optional; default gpt-realtime

# {
#   "client_secret": { "value": "ek_...", "expires_at": 1756240000 },
#   "model": "gpt-realtime-mini",
#   "url":   "wss://1xai.ir/rt/voice"
# }
سمتِ سرور
python
# Your backend. The API key lives here and only here.
import httpx
from fastapi import FastAPI

app = FastAPI()

@app.post("/voice-token")
async def voice_token():
    async with httpx.AsyncClient() as http:
        r = await http.post(
            "https://1xai.ir/v1/realtime/client_secret",
            headers={"Authorization": f"Bearer {ONEXAI_API_KEY}"},
        )
    r.raise_for_status()
    return r.json()   # hand straight to your front end
[02]

بازکردنِ تماس

مرورگر نمی‌تواند روی دست‌دادنِ WebSocket هدر بگذارد، پس سکرت در کوئری‌استرینگ می‌رود؛ کلاینتِ سمتِ سرور هدر دارد و باید از هدر استفاده کند.

مرورگر
javascript
// Your page. Never sees the API key.
const { client_secret, url } = await fetch("/voice-token", { method: "POST" })
  .then((r) => r.json());

const ws = new WebSocket(`${url}?client_secret=${client_secret.value}`);

ws.onopen = () => {
  // Configure the session: voice, Persian transcription, turn detection.
  ws.send(JSON.stringify({
    type: "session.update",
    session: {
      instructions: "به فارسی و کوتاه پاسخ بده.",
      audio: {
        input: {
          format: "pcm16",
          transcription: { model: "whisper-1", language: "fa" },
          // Server-side VAD: the model decides when you stopped talking,
          // and interrupting it cancels its reply (barge-in).
          turn_detection: { type: "server_vad", interrupt_response: true },
        },
        output: { format: "pcm16", voice: "alloy" },
      },
      tools: [{
        type: "function",
        name: "lookup_order",
        description: "Look up an order in the ERP",
        parameters: {
          type: "object",
          properties: { id: { type: "string" } },
          required: ["id"],
        },
      }],
    },
  }));
};

ws.onmessage = (e) => {
  const ev = JSON.parse(e.data);
  switch (ev.type) {
    case "conversation.item.input_audio_transcription.completed":
      console.log("user said:", ev.transcript);          // live transcript
      break;
    case "response.output_audio.delta":
      playPCM(atob(ev.delta));                            // model speaking
      break;
    case "response.function_call_arguments.done":
      callYourERP(ev.name, JSON.parse(ev.arguments));     // function calling
      break;
    case "error":
      // insufficient_funds arrives here before the socket closes
      console.error(ev.error);
      break;
  }
};

// Microphone in: base64 PCM16 frames, as they are captured.
function sendMic(pcm16) {
  ws.send(JSON.stringify({ type: "input_audio_buffer.append", audio: pcm16 }));
}
کلاینتِ سمتِ سرور
javascript
// Server-side client (no browser). Header instead of query string —
// query strings reach access logs, headers do not.
import WebSocket from "ws";

const ws = new WebSocket("wss://1xai.ir/rt/voice", {
  headers: { Authorization: `Bearer ${clientSecret}` },
});
[03]

Gemini Live

برای gemini-3.1-flash-live-preview یا gemini-3.8-live همان کلاینت‌سکرت را با model مدلِ Gemini بگیر؛ پاسخ، آدرسِ wss://1xai.ir/rt/gemini را برمی‌گرداند. روی این سوکت پروتکلِ خودِ Gemini Live API را حرف بزن: اولین پیام setup است و مدلِ آن با مدلِ سکرت جایگزین می‌شود. ابزارهای جست‌وجوی گوگل، نقشه و URL context حذف می‌شوند؛ function calling و اجرای کد می‌مانند. هزینهٔ هر نوبت با رسیدنِ usageMetadata همان نوبت تسویه می‌شود.

Gemini Live
javascript
// Gemini Live: mint with {"model": "gemini-3.1-flash-live-preview"}
// (or "gemini-3.8-live"); the answer's url is wss://1xai.ir/rt/gemini.
// Then speak Google's Live API (BidiGenerateContent) as-is.
const ws = new WebSocket(`${url}?client_secret=${client_secret.value}`);

ws.onopen = () => {
  // The first message must be the setup. Its "model" is replaced with the
  // secret's; Google Search, Maps and URL context tools are removed
  // (function calling and code execution are kept).
  ws.send(JSON.stringify({
    setup: {
      model: "models/gemini-3.1-flash-live-preview",
      generationConfig: { responseModalities: ["AUDIO"] },
      systemInstruction: { parts: [{ text: "به فارسی و کوتاه پاسخ بده." }] },
      inputAudioTranscription: {},
      outputAudioTranscription: {},
    },
  }));
};

// Microphone in: base64 PCM16, 16 kHz mono.
function sendMic(pcm16) {
  ws.send(JSON.stringify({
    realtimeInput: { audio: { data: pcm16, mimeType: "audio/pcm;rate=16000" } },
  }));
}

ws.onmessage = async (e) => {
  const text = typeof e.data === "string" ? e.data : await e.data.text();
  const msg = JSON.parse(text);
  for (const part of msg.serverContent?.modelTurn?.parts ?? []) {
    if (part.inlineData) playPCM(atob(part.inlineData.data));   // 24 kHz PCM16
  }
  // usageMetadata arrives with each turnComplete; that turn is billed then.
};
[04]

محدودیت‌ها و هزینه

مدل
هنگامِ گرفتنِ کلاینت‌سکرت با model انتخاب می‌شود: gpt-realtime (پیش‌فرض)، gpt-realtime-mini، gpt-realtime-2.1-mini، gemini-3.1-flash-live-preview و gemini-3.8-live. سکرت فقط همان مدل را باز می‌کند و کلاینت بعداً نمی‌تواند عوضش کند.
ترابری
WebSocket — بدون WebRTC.
سقفِ زمانِ تماس
سی دقیقه؛ پس از آن سوکت بسته می‌شود.
عمرِ کلاینت‌سکرت
دو دقیقه از لحظهٔ صدور — فقط برای بازکردنِ سوکت، نه برای طولِ تماس.
حداقلِ اعتبار
۲٬۰۰۰ تومان برای شروعِ تماس. تماس در حینِ اجرا تسویه می‌شود و با تمام‌شدنِ اعتبار با رویدادِ insufficient_funds بسته می‌شود.
هزینه
توکنِ صوتی جدا از متن و با نرخِ صوتیِ ارائه‌دهنده به‌علاوهٔ ۲۰٪ حساب می‌شود و چند برابرِ متن است. صدای کش‌شده (گفت‌وگوی قبلی که هر نوبت دوباره خوانده می‌شود) با نرخِ کشِ ارائه‌دهنده حساب می‌شود؛ روی gpt-realtime یعنی ۸۰ برابر ارزان‌تر از صدای تازه. رونوشتِ صدای ورودی (اگر روشنش کنی) جدا و با نرخِ مدلِ رونوشت حساب می‌شود؛ مدل‌های مجاز: whisper-1، gpt-4o-transcribe، gpt-4o-mini-transcribe، gpt-realtime-whisper و gpt-live-transcribe. تسویه بعد از هر پاسخِ مدل انجام می‌شود، نه در پایانِ تماس. نرخِ لحظه‌ای در صفحهٔ قیمت‌ها است.
هر کاربر، یک نشست
هر سوکت نشستِ مستقلِ خودش را دارد؛ چیزی بین تماس‌ها مشترک نیست.
FAQ · پرسش‌های پرتکرار
این سرویس Realtime واقعی است یا زنجیرهٔ STT → LLM → TTS؟
واقعی است. رلهٔ ما فریم‌ها را دست‌نخورده بین اپ تو و Realtime API رد می‌کند؛ هیچ تبدیل متن‌به‌گفتاری در میانه نیست. یعنی VAD، قطع‌کردنِ صحبتِ مدل، رونوشتِ زنده و function calling همان‌هایی هستند که خودِ مدل ارائه می‌دهد، نه شبیه‌سازیِ ما.
چرا باید کلاینت‌سکرت بگیرم و کلید API را مستقیم روی سوکت نفرستم؟
چون کلیدِ API از کیفِ پولِ تو خرج می‌کند و اگر در جاوااسکریپتِ سمتِ کاربر قرار بگیرد، در devtools هر بازدیدکننده هم هست. سوکت عمداً کلیدِ خام را نمی‌پذیرد و فقط مقدارِ ek_ را قبول می‌کند؛ عمرِ آن دو دقیقه است و تنها یک کار می‌کند: بازکردنِ یک تماس.
WebRTC هم پشتیبانی می‌شود؟
خیر — فقط WebSocket. WebRTC اتصالِ مستقیمِ مرورگر به سرورِ ارائه‌دهنده است و دقیقاً همان چیزی است که از داخلِ ایران کار نمی‌کند؛ صدا باید از سرورِ ما عبور کند.
حداکثر زمان هر تماس چقدر است؟
سی دقیقه. تماسی که از این بیشتر طول بکشد در عمل یک تبِ فراموش‌شده است، نه گفت‌وگو — و بدون سقف تا خالی‌شدنِ کیفِ پول هزینه می‌تراشد.
کدام مدل‌ها را می‌توانم انتخاب کنم؟
gpt-realtime (پیش‌فرض)، gpt-realtime-mini و gpt-realtime-2.1-mini روی /rt/voice با پروتکلِ OpenAI؛ و gemini-3.1-flash-live-preview و gemini-3.8-live روی /rt/gemini با پروتکلِ Gemini Live. مدل را موقعِ گرفتنِ کلاینت‌سکرت با فیلدِ model انتخاب کن؛ سکرت فقط همان مدل را باز می‌کند. نسخه‌های mini صدا را حدودِ یک‌سومِ gpt-realtime و متن را حدودِ یک‌ششمِ آن حساب می‌کنند.
هزینهٔ صدا چطور حساب می‌شود؟
توکن‌های صوتی جدا از متن و با نرخِ صوتیِ خودِ ارائه‌دهنده به‌علاوهٔ ۲۰٪ حساب می‌شوند؛ صدا چند برابرِ متن است. هزینه بعد از هر پاسخِ مدل تسویه می‌شود، نه در پایانِ تماس، و نرخِ لحظه‌ای در صفحهٔ قیمت‌ها هست.

سؤالی ماند؟ بنویس یا مرجعِ کاملِ رویدادها را در مستندات ببین.