۱۰۰ هزار تومان هدیهٔ ثبت‌نام، تا ۱ میلیونبا ثبت‌نام گردونهٔ هدیه را بچرخان: دست‌کم ۱۰۰٬۰۰۰ تومان و تا ۱٬۰۰۰٬۰۰۰ تومان اعتبار هدیه، بدون کارت بانکیگردونهٔ هدیه۱ میلیون برای تو، ۱ میلیون برای دوستتدوستانت را دعوت کن: برای هر دوستی که فعال شود ۱٬۰۰۰٬۰۰۰ تومان اعتبار هدیه می‌گیری و خودش هم ۱٬۰۰۰٬۰۰۰ تومانجوایز
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"

# {
#   "client_secret": { "value": "ek_...", "expires_at": 1756240000 },
#   "model": "gpt-realtime",
#   "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]

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

مدل
gpt-realtime — سمتِ سرور ثابت است و کلاینت نمی‌تواند عوضش کند. صدا گران‌ترین چیزی است که می‌فروشیم؛ انتخابِ مدلِ گران‌تر دستِ تماس‌گیرنده نیست.
ترابری
WebSocket — بدون WebRTC.
سقفِ زمانِ تماس
سی دقیقه؛ پس از آن سوکت بسته می‌شود.
عمرِ کلاینت‌سکرت
دو دقیقه از لحظهٔ صدور — فقط برای بازکردنِ سوکت، نه برای طولِ تماس.
حداقلِ اعتبار
۲٬۰۰۰ تومان برای شروعِ تماس. تماس در حینِ اجرا تسویه می‌شود و با تمام‌شدنِ اعتبار با رویدادِ insufficient_funds بسته می‌شود.
هزینه
توکنِ صوتی جدا از متن و با نرخِ صوتیِ ارائه‌دهنده به‌علاوهٔ ۲۰٪ حساب می‌شود و چند برابرِ متن است. تسویه بعد از هر پاسخِ مدل انجام می‌شود، نه در پایانِ تماس. نرخِ لحظه‌ای در صفحهٔ قیمت‌ها است.
هر کاربر، یک نشست
هر سوکت نشستِ مستقلِ خودش را دارد؛ چیزی بین تماس‌ها مشترک نیست.
FAQ · پرسش‌های پرتکرار
این سرویس Realtime واقعی است یا زنجیرهٔ STT → LLM → TTS؟
واقعی است. رلهٔ ما فریم‌ها را دست‌نخورده بین اپ تو و Realtime API رد می‌کند؛ هیچ تبدیل متن‌به‌گفتاری در میانه نیست. یعنی VAD، قطع‌کردنِ صحبتِ مدل، رونوشتِ زنده و function calling همان‌هایی هستند که خودِ مدل ارائه می‌دهد، نه شبیه‌سازیِ ما.
چرا باید کلاینت‌سکرت بگیرم و کلید API را مستقیم روی سوکت نفرستم؟
چون کلیدِ API از کیفِ پولِ تو خرج می‌کند و اگر در جاوااسکریپتِ سمتِ کاربر قرار بگیرد، در devtools هر بازدیدکننده هم هست. سوکت عمداً کلیدِ خام را نمی‌پذیرد و فقط مقدارِ ek_ را قبول می‌کند؛ عمرِ آن دو دقیقه است و تنها یک کار می‌کند: بازکردنِ یک تماس.
WebRTC هم پشتیبانی می‌شود؟
خیر — فقط WebSocket. WebRTC اتصالِ مستقیمِ مرورگر به سرورِ ارائه‌دهنده است و دقیقاً همان چیزی است که از داخلِ ایران کار نمی‌کند؛ صدا باید از سرورِ ما عبور کند.
حداکثر زمان هر تماس چقدر است؟
سی دقیقه. تماسی که از این بیشتر طول بکشد در عمل یک تبِ فراموش‌شده است، نه گفت‌وگو — و بدون سقف تا خالی‌شدنِ کیفِ پول هزینه می‌تراشد.
هزینهٔ صدا چطور حساب می‌شود؟
توکن‌های صوتی جدا از متن و با نرخِ صوتیِ خودِ ارائه‌دهنده به‌علاوهٔ ۲۰٪ حساب می‌شوند؛ صدا چند برابرِ متن است. هزینه بعد از هر پاسخِ مدل تسویه می‌شود، نه در پایانِ تماس، و نرخِ لحظه‌ای در صفحهٔ قیمت‌ها هست.

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