{"provider":"zyraxon","keyRequired":false,"keyNote":"No API key is needed. Send any non-empty string if a client insists on one.","groups":["Talk to a model","Hear an answer","Speak instead of typing"],"count":7,"endpoints":[{"id":"chat","group":"Talk to a model","method":"POST","url":"https://zyraxonai.lovable.app/api/public/zyraxon/v1/chat/completions","path":"/api/public/zyraxon/v1/chat/completions","name":"Chat completions","blurb":"The call your app makes. OpenAI chat-completions semantics, with server-sent-event streaming when you ask for it.","returns":"`choices[].message.content`, or a stream of `chat.completion.chunk` objects when `stream` is true.","keyRequired":false,"featured":true,"fields":[{"name":"model","required":true,"note":"Any id from the model catalogue, e.g. `zyraxon-apex`."},{"name":"messages","required":true,"note":"The usual `[{role, content}]` array."},{"name":"stream","required":false,"note":"`true` for server-sent events."},{"name":"tools","required":false,"note":"OpenAI function-tool definitions; the model calls them."}],"notes":["The `model` value is a ZYRAXON name, not an upstream one. That name does not change when the pool behind it does."],"snippets":[{"label":"Streaming","lang":"shell","code":"curl -sN https://zyraxonai.lovable.app/api/public/zyraxon/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"zyraxon-apex\",\n    \"stream\": true,\n    \"messages\": [{ \"role\": \"user\", \"content\": \"Hello\" }]\n  }'"},{"label":"Non-streaming","lang":"shell","code":"curl -s https://zyraxonai.lovable.app/api/public/zyraxon/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"zyraxon-apex\",\n    \"messages\": [{ \"role\": \"user\", \"content\": \"Hello\" }]\n  }' | jq -r '.choices[0].message.content'"}]},{"id":"models","group":"Talk to a model","method":"GET","url":"https://zyraxonai.lovable.app/api/public/zyraxon/v1/models","path":"/api/public/zyraxon/v1/models","name":"Model catalogue","blurb":"What a client reads first, to discover which models exist. Standard OpenAI list shape, so no special handling is needed.","returns":"`{ object: \"list\", data: [{ id, owned_by, … }] }` — ten ZYRAXON models.","keyRequired":false,"featured":true,"snippets":[{"label":"List the line","lang":"shell","code":"curl -s https://zyraxonai.lovable.app/api/public/zyraxon/v1/models | jq -r '.data[] | .id'"}]},{"id":"provider","group":"Talk to a model","method":"GET","url":"https://zyraxonai.lovable.app/api/public/zyraxon/v1/provider","path":"/api/public/zyraxon/v1/provider","name":"Provider details","blurb":"What the serving pool looks like right now — modalities, concurrency, and each model's live status and context window.","returns":"`{ models: [{ id, status, context, … }], modalities, concurrency, fleet }`.","keyRequired":false,"featured":false,"notes":["The provider page reads this endpoint live, so a model shown as rotated there is rotated in the pool at that moment."],"snippets":[{"label":"Read the pool","lang":"shell","code":"curl -s https://zyraxonai.lovable.app/api/public/zyraxon/v1/provider | jq '{modalities, concurrency, fleet}'"}]},{"id":"integrations","group":"Talk to a model","method":"GET","url":"https://zyraxonai.lovable.app/api/public/zyraxon/v1/integrations","path":"/api/public/zyraxon/v1/integrations","name":"Integration catalogue","blurb":"The same integration list this page shows, as JSON — the exact values to paste into a host app, with steps and snippets.","returns":"`{ integrations: [{ id, name, category, fields, steps, snippets }] }`.","keyRequired":false,"featured":false,"snippets":[{"label":"Find one by id","lang":"shell","code":"curl -s https://zyraxonai.lovable.app/api/public/zyraxon/v1/integrations | jq -r '.integrations[] | \"\\(.id)\\t\\(.name)\"'"}]},{"id":"tts","group":"Hear an answer","method":"POST","url":"https://zyraxonai.lovable.app/api/public/tts","path":"/api/public/tts","name":"Text to speech","blurb":"Turn an answer into audio. The voice follows the language of the text, so a Bengali answer is read by a Bengali voice rather than an English one sounding out Bengali letters.","returns":"`audio/mpeg`, streamed, with `X-ZYRAXON-Voice` naming the voice that spoke.","keyRequired":false,"featured":true,"fields":[{"name":"text","required":true,"note":"What to say, up to 5000 characters. Markdown is stripped first."},{"name":"voice","required":false,"note":"An exact name from the voices list, e.g. `bn-BD-NabanitaNeural`. Wins over `locale`."},{"name":"locale","required":false,"note":"A language, e.g. `bn`. Picks a voice in that language."}],"notes":["Code blocks, links and Markdown are removed before speaking, so an answer is not read out character by character.","Naming `voice` exactly also skips the voice-catalogue lookup, so the first byte of audio arrives sooner."],"snippets":[{"label":"cURL","lang":"shell","code":"# Speak text. The voice is chosen from the text's own script\n# unless you name one, so a Bengali answer is read by a Bengali voice.\ncurl -s https://zyraxonai.lovable.app/api/public/tts \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"text\":\"Hello from ZYRAXON.\",\"locale\":\"en\"}' \\\n  -o answer.mp3\n\n# Name the voice exactly when you want a specific one\ncurl -s https://zyraxonai.lovable.app/api/public/tts \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"text\":\"আজ আকাশ পরিষ্কার।\",\"voice\":\"bn-BD-NabanitaNeural\"}' \\\n  -o bengali.mp3\n\n# What voices exist: 322 of them, across 75 languages\ncurl -s https://zyraxonai.lovable.app/api/public/tts/voices | jq -r '.data[] | select(.locale|startswith(\"bn\")) | .name'"},{"label":"Browser: speak as it streams","lang":"typescript","code":"// Speak an answer, sentence by sentence, while it is still being written.\n// Speaking the whole reply at the end means seconds of silence; speaking each\n// finished sentence as it appears is what makes it feel like conversation.\nconst queue: string[] = [];\nlet buffer = \"\";\nlet speaking = false;\n\nasync function speak(text: string): Promise<void> {\n  const res = await fetch(\"https://zyraxonai.lovable.app/api/public/tts\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\" },\n    body: JSON.stringify({ text }),      // the language is read from the text\n  });\n  const audio = new Audio(URL.createObjectURL(await res.blob()));\n  await new Promise<void>((done) => {\n    audio.onended = () => done();\n    audio.onerror = () => done();\n    void audio.play();\n  });\n}\n\nasync function pump(): Promise<void> {\n  if (speaking) return;\n  speaking = true;\n  while (queue.length) await speak(queue.shift()!);\n  speaking = false;\n}\n\n/** Call on every streamed token of the model's answer. */\nexport function feed(chunk: string): void {\n  buffer += chunk;\n  // Cut after a sentence terminator, including the Bengali danda.\n  let at = -1;\n  for (let i = 0; i < buffer.length; i++) if (/[.!?।॥]/.test(buffer[i]!)) at = i;\n  if (at >= 0) {\n    queue.push(buffer.slice(0, at + 1).trim());\n    buffer = buffer.slice(at + 1).trimStart();\n    void pump();\n  }\n}\n\n/** Call when the answer ends, to speak whatever is left. */\nexport function finish(): void {\n  if (buffer.trim()) queue.push(buffer.trim());\n  buffer = \"\";\n  void pump();\n}"}]},{"id":"voices","group":"Hear an answer","method":"GET","url":"https://zyraxonai.lovable.app/api/public/tts/voices","path":"/api/public/tts/voices","name":"Voice catalogue","blurb":"Every voice available, with its locale and gender. 322 voices across 75 languages.","returns":"`{ count, languages, locales, data: [{ name, locale, gender, friendly }] }`.","keyRequired":false,"featured":false,"snippets":[{"label":"Every Bengali voice","lang":"shell","code":"curl -s https://zyraxonai.lovable.app/api/public/tts/voices \\\n  | jq -r '.data[] | select(.locale | startswith(\"bn\")) | .name'\n# bn-BD-NabanitaNeural\n# bn-BD-PradeepNeural\n# bn-IN-BashkarNeural\n# bn-IN-TanishaaNeural"}]},{"id":"stt","group":"Speak instead of typing","method":"POST","url":"https://zyraxonai.lovable.app/api/public/stt","path":"/api/public/stt","name":"Speech to text — one endpoint, two modes","blurb":"Send a recording and get the transcript, or stream audio while the person is still speaking and get text as it is recognised. Same URL; the request itself picks the mode.","returns":"Batch: `{ success, text, language, confidence, engine, container, duration, elapsed }`. Live: server-sent events — `ready`, `language`, `partial`, `segment`, `done`.","keyRequired":false,"featured":true,"fields":[{"name":"language","required":true,"note":"`bn` or `bn-BD`. Required in both modes — the recogniser does not detect language, so a guess would be a wrong transcript."},{"name":"file","required":false,"note":"Batch, multipart: the audio. Format is read from the bytes, so a mislabelled file still works."},{"name":"audio","required":false,"note":"Batch, JSON: base64 audio, with an optional `format`."},{"name":"segment","required":false,"note":"Live: seconds per committed chunk, default 4."},{"name":"partial","required":false,"note":"Live: seconds between partial updates, default 1.2."}],"notes":["Accepted containers: wav, mp3, flac, ogg, webm, and raw PCM. The format is sniffed from the first bytes, not from the Content-Type.","Live mode takes raw 16-bit mono PCM. One second at 16 kHz is 32000 bytes — a client paces its microphone by that.","A compressed container cannot be cut mid-frame, which is why live mode takes PCM: raw samples can be split at any moment without corrupting the audio.","`language` is required, not detected. Asked for English about Bengali audio the recogniser returns English-sounding words at high confidence instead of failing, so a guess produces a confident wrong answer. The caller knows which language is being spoken; the endpoint asks rather than assumes.","`GET` on this URL returns the contract as JSON, so a coding assistant handed the URL can read it without a human explaining it."],"snippets":[{"label":"A recording","lang":"shell","code":"# A recording you already have. The format is read from the bytes,\n# so a mislabelled file still works. language is required — the\n# recogniser does not detect it, and a guess is a wrong transcript.\ncurl -s https://zyraxonai.lovable.app/api/public/stt \\\n  -F \"file=@recording.mp3\" \\\n  -F \"language=bn\" \\\n| jq -r '.text'"},{"label":"Live, from a pipe","lang":"shell","code":"# Live transcription over Server-Sent Events.\n# Body: raw 16-bit mono PCM at 16 kHz — one second is 32000 bytes.\n# --data-binary with @- streams stdin, so this works from a pipe:\nffmpeg -i recording.mp3 -ar 16000 -ac 1 -f s16le - \\\n| curl -sN -X POST \"https://zyraxonai.lovable.app/api/public/stt?language=bn\" \\\n    -H \"Content-Type: audio/l16; rate=16000\" \\\n    -H \"Accept: text/event-stream\" \\\n    --data-binary @-\n\n# event: ready     {\"rate\":16000,\"language\":\"bn\"}\n# event: partial   {\"text\":\"আজ আকাশ\"}\n# event: partial   {\"text\":\"আজ আকাশ পরিষ্কার\"}\n# event: segment   {\"text\":\"আজ আকাশ পরিষ্কার এবং সূর্য উজ্জ্বল।\",\"seconds\":4.2}\n# event: done      {\"text\":\"...\",\"segments\":2,\"seconds\":8.1}"},{"label":"Live, in a browser","lang":"typescript","code":"// Live transcription from a browser microphone.\n//\n// The browser records to WebM/Opus, which cannot be cut mid-frame, so the audio\n// is resampled to raw PCM here with an AudioWorklet-free approach: the recorder\n// is only used as a clock, and each chunk is decoded and resampled to 16 kHz\n// before being posted. Sending PCM is what lets the server split the stream\n// cleanly at any moment.\nconst RATE = 16000;\n\nexport async function startLive(\n  onText: (text: string, final: boolean) => void,\n  language: string,        // required: \"bn\", \"bn-BD\", \"en-US\", …\n): Promise<() => void> {\n  const stream = await navigator.mediaDevices.getUserMedia({\n    audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true },\n  });\n\n  // Decode the microphone through a context at the target rate, so the samples\n  // posted are already 16 kHz mono and no resampling is needed on the server.\n  const ctx = new AudioContext({ sampleRate: RATE });\n  await ctx.audioWorklet.addModule(\"/pcm-worklet.js\");\n  const source = ctx.createMediaStreamSource(stream);\n  const worklet = new AudioWorkletNode(ctx, \"pcm-worklet\");\n  source.connect(worklet);\n\n  const controller = new AbortController();\n  const upload = new ReadableStream<Uint8Array>({\n    start(sink) {\n      worklet.port.onmessage = (event: MessageEvent<Float32Array>) => {\n        const floats = event.data;\n        const pcm = new Int16Array(floats.length);\n        for (let i = 0; i < floats.length; i++) {\n          const v = Math.max(-1, Math.min(1, floats[i]!));\n          pcm[i] = Math.round(v * 32767);\n        }\n        sink.enqueue(new Uint8Array(pcm.buffer));\n      };\n    },\n    cancel() {\n      controller.abort();\n    },\n  });\n\n  const res = await fetch(`https://zyraxonai.lovable.app/api/public/stt?stream=1&language=${language}`, {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"audio/l16; rate=\" + RATE, Accept: \"text/event-stream\" },\n    body: upload,\n    signal: controller.signal,\n    // @ts-expect-error streaming request bodies need this in some browsers\n    duplex: \"half\",\n  });\n\n  // Read the event stream and hand each update to the caller.\n  const reader = res.body!.getReader();\n  const decoder = new TextDecoder();\n  let pending = \"\";\n  void (async () => {\n    for (;;) {\n      const { value, done } = await reader.read();\n      if (done) break;\n      pending += decoder.decode(value, { stream: true });\n      const frames = pending.split(\"\\n\\n\");\n      pending = frames.pop() ?? \"\";\n      for (const frame of frames) {\n        const line = frame.split(\"\\n\").find((l) => l.startsWith(\"data: \"));\n        if (!line) continue;\n        const event = JSON.parse(line.slice(6));\n        if (event.type === \"partial\") onText(event.text, false);\n        if (event.type === \"segment\" || event.type === \"done\") onText(event.text, true);\n      }\n    }\n  })();\n\n  // The caller stops the stream by calling this.\n  return () => {\n    controller.abort();\n    source.disconnect();\n    void ctx.close();\n    stream.getTracks().forEach((t) => t.stop());\n  };\n}"},{"label":"Live, in Node","lang":"typescript","code":"// Live transcription from Node: pipe any audio source into the endpoint and\n// print text as it is recognised. This is the shape an Electron app or a phone\n// recorder uses — it already has a PCM source and wants text as it speaks.\nimport { spawn } from \"node:child_process\";\nimport { Readable } from \"node:stream\";\n\nconst RATE = 16000;\nconst language = process.env.LANGUAGE;      // required, e.g. bn\nif (!language) throw new Error(\"set LANGUAGE, e.g. LANGUAGE=bn node listen.ts audio.mp3\");\nconst url = `https://zyraxonai.lovable.app/api/public/stt?stream=1&language=${language}`;\n\n// Any ffmpeg-readable input, resampled to the format the endpoint wants.\nconst ffmpeg = spawn(\"ffmpeg\", [\n  \"-i\", process.argv[2] ?? \"-\",     // a file, or stdin for a live device\n  \"-ar\", String(RATE),\n  \"-ac\", \"1\",\n  \"-f\", \"s16le\",\n  \"-\",\n]);\n\nconst res = await fetch(url, {\n  method: \"POST\",\n  headers: { \"Content-Type\": `audio/l16; rate=${RATE}`, Accept: \"text/event-stream\" },\n  body: Readable.toWeb(ffmpeg.stdout) as ReadableStream<Uint8Array>,\n  // @ts-expect-error Node needs this to stream a request body\n  duplex: \"half\",\n});\n\nconst reader = res.body!.getReader();\nconst decoder = new TextDecoder();\nlet pending = \"\";\nfor (;;) {\n  const { value, done } = await reader.read();\n  if (done) break;\n  pending += decoder.decode(value, { stream: true });\n  const frames = pending.split(\"\\n\\n\");\n  pending = frames.pop() ?? \"\";\n  for (const frame of frames) {\n    const line = frame.split(\"\\n\").find((l) => l.startsWith(\"data: \"));\n    if (!line) continue;\n    const event = JSON.parse(line.slice(6));\n    if (event.type === \"partial\") process.stdout.write(`\\r${event.text}`);\n    if (event.type === \"segment\") console.log(`\\n[${event.seconds}s] ${event.text}`);\n    if (event.type === \"done\") console.log(`\\ndone: ${event.text}`);\n  }\n}"},{"label":"Python","lang":"python","code":"\"\"\"Speech to text, batch and live, from Python.\"\"\"\nimport json\nimport requests\n\nSTT = \"https://zyraxonai.lovable.app/api/public/stt\"\n\n# ── a recording you already have ──────────────────────────────────────────\nwith open(\"recording.mp3\", \"rb\") as f:\n    res = requests.post(STT, files={\"file\": f}, data={\"language\": \"bn\"}, timeout=120)\nres.raise_for_status()\nanswer = res.json()\nprint(answer[\"text\"], \"|\", answer.get(\"language\"), answer.get(\"confidence\"))\n\n# ── live: stream PCM and read the events ──────────────────────────────────\ndef pcm_chunks(path, rate=16000, chunk_bytes=rate * 2):   # one second per chunk\n    import subprocess\n    proc = subprocess.Popen(\n        [\"ffmpeg\", \"-i\", path, \"-ar\", str(rate), \"-ac\", \"1\", \"-f\", \"s16le\", \"-\"],\n        stdout=subprocess.PIPE,\n    )\n    while True:\n        chunk = proc.stdout.read(chunk_bytes)\n        if not chunk:\n            break\n        yield chunk\n\nwith requests.post(\n    f\"{STT}?language=bn\",   # required, e.g. \"bn\" or \"bn-BD\"\n    data=pcm_chunks(\"recording.mp3\"),\n    headers={\"Content-Type\": \"audio/l16; rate=16000\", \"Accept\": \"text/event-stream\"},\n    stream=True,\n    timeout=300,\n) as res:\n    for line in res.iter_lines(decode_unicode=True):\n        if not line or not line.startswith(\"data: \"):\n            continue\n        event = json.loads(line[6:])\n        if event[\"type\"] == \"partial\":\n            print(\"\\r\" + event[\"text\"], end=\"\", flush=True)\n        elif event[\"type\"] == \"segment\":\n            print(f\"\\n[{event['seconds']}s] {event['text']}\")\n        elif event[\"type\"] == \"done\":\n            print(f\"\\ndone: {event['text']}\")"},{"label":"The whole loop","lang":"shell","code":"#!/usr/bin/env bash\n# talk.sh — speak a question, hear the answer. The whole loop, both halves.\nset -euo pipefail\n\nORIGIN=\"https://zyraxonai.lovable.app\"\nRECORDING=\"${1:-recording.mp3}\"\n\n# 1. Your speech -> text\nQUESTION=$(curl -s \"$ORIGIN/api/public/stt\" -F \"file=@$RECORDING\" -F \"language=bn\" | jq -r '.text')\necho \"you> $QUESTION\"\n\n# 2. Text -> the model's answer\nANSWER=$(curl -s \"$ORIGIN/api/public/zyraxon/v1/chat/completions\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"$(jq -nc --arg q \"$QUESTION\" \\\n        '{model:\"zyraxon-apex\", messages:[{role:\"user\", content:$q}]}')\" \\\n  | jq -r '.choices[0].message.content')\necho \"zyraxon> $ANSWER\"\n\n# 3. The answer -> speech, in the language it was written in\ncurl -s \"$ORIGIN/api/public/tts\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"$(jq -nc --arg t \"$ANSWER\" '{text:$t, locale:\"bn\"}')\" -o reply.mp3\nffplay -nodisp -autoexit reply.mp3    # macOS: afplay   Windows: start"}]}]}