Audio inputs
Use input_audio to ask a compatible model about an MP3 or WAV recording.
For inline audio, send base64 data and its format in input_audio. URL input depends on the route; the example uses inline MP3. An AI Hub uploaded file reference is not resolved as an audio chat part. For a fixed transcription job, choose a speech capability instead of an open-ended model question.
Run a request
Use Python 3 or Node 22, and export TOKENHUB_API_KEY from your secret store. Download the sample, or replace it with your own file of the same format. Each model request uses your account's paid allowance.
curl --fail "https://aihub.belive.sg/samples/dubbing/en.mp3" -o recording.mp3python3 - <<'PY' import base64, json from pathlib import Path encoded = base64.b64encode(Path("recording.mp3").read_bytes()).decode("ascii") body = { "model": "qwen/qwen3.8-omni-flash", "provider": {"only": ["alibaba-model-studio"]}, "max_tokens": 4000, "messages": [{"role": "user", "content": [ {"type": "text", "text": "Transcribe this recording."}, {"type": "input_audio", "input_audio": {"data": encoded, "format": "mp3"}}, ]}], } Path("request.json").write_text(json.dumps(body)) PY curl --fail-with-body "https://tokenhub-api.belive.sg/api/v1/chat/completions" \ -H "Authorization: Bearer $TOKENHUB_API_KEY" \ -H "Content-Type: application/json" \ --data-binary @request.json
import base64, json from pathlib import Path encoded = base64.b64encode(Path("recording.mp3").read_bytes()).decode("ascii") body = { "model": "qwen/qwen3.8-omni-flash", "provider": {"only": ["alibaba-model-studio"]}, "max_tokens": 4000, "messages": [{"role": "user", "content": [ {"type": "text", "text": "Transcribe this recording."}, {"type": "input_audio", "input_audio": {"data": encoded, "format": "mp3"}}, ]}], } import os, urllib.request request = urllib.request.Request( "https://tokenhub-api.belive.sg/api/v1/chat/completions", data=json.dumps(body).encode(), headers={"Authorization": "Bearer " + os.environ["TOKENHUB_API_KEY"], "Content-Type": "application/json"}, ) with urllib.request.urlopen(request, timeout=120) as response: result = json.load(response) print(result["choices"][0]["message"]["content"]) print(result.get("usage")) print(response.headers.get("X-Generation-Id"))
import { readFile } from "node:fs/promises"; const encoded = (await readFile("recording.mp3")).toString("base64"); const apiKey = process.env.TOKENHUB_API_KEY; if (!apiKey) throw new Error("Set TOKENHUB_API_KEY"); const response = await fetch("https://tokenhub-api.belive.sg/api/v1/chat/completions", { method: "POST", headers: { Authorization: "Bearer " + apiKey, "Content-Type": "application/json" }, body: JSON.stringify({ model: "qwen/qwen3.8-omni-flash", provider: { only: ["alibaba-model-studio"] }, max_tokens: 4000, messages: [{ role: "user", content: [ { type: "text", text: "Transcribe this recording." }, {"type": "input_audio", "input_audio": {"data": encoded, "format": "mp3"}}, ] }], }), }); if (!response.ok) throw new Error("HTTP " + response.status + ": " + await response.text()); const result = await response.json(); console.log(result.choices[0].message.content, result.usage); console.log(response.headers.get("X-Generation-Id"));
The response is a normal Chat Completion: read the assistant content, reported usage and X-Generation-Id.
Output and realtime
Audio input does not imply audio output. The example asks for text. For speech synthesis use the audio output routes. Realtime sessions use a different transport and model selection; they are not ordinary Chat SSE.
Realtime WebSocket boundary
GET /api/v1/realtime?model=… is a server-to-server WebSocket upgrade using the inference bearer header. Browser cookies, query keys, WebRTC and SIP are refused. The selected model must have an enabled, qualified realtime route; a missing runtime returns 503 and an unavailable model can return 404. Use the RealtimeClientEvent and RealtimeServerEvent schemas for the closed event subset.
Audio is PCM16 little-endian mono at 24 kHz. Responses are manual, with one outstanding response per session. A lost connection is not resumed: open a new session. Preserve request and session evidence and reconcile before creating new work. Realtime is not the audio-input form of Chat, Responses or Messages.
Choose a model and route
Read GET /api/v1/models in your environment. architecture.input_modalities describes a model, while each endpoint's capabilities identifies the serving route: vision for images, audio_input, video_input or file_input. Check the Models page for available routes. The examples pin a declared route; use a different eligible route only with its own supported request form.
Limits, cost and failures
Base64 increases the request size. The synchronous chat body limit defaults to 20 MiB, and provider format, duration, page and context limits also apply. Keep inline samples small; an uploaded video uses a different path described below. A model's context limit is not a universal media-size limit.
Media input can contribute input tokens or a modality-specific meter. Read the selected route's price table and the resulting usage; do not estimate solely from text length. Key caps and model restrictions still apply.
Malformed input can return 400, an oversized body 413, and an unsuitable route 400 or a routing refusal. See Errors before retrying; after a timeout, reconcile before resubmitting.
Support and limits
Each protocol has its own media block: URL and data inputs and uploaded file IDs have their own schemas and size limits, and a model's page lists its input modalities. PDFs, audio and video are refused on routes that do not take them.
Gateway validation, defaults and response forms are in the operation index and the OpenAPI schemas.
Error codes and recovery are in Errors and recovery. Keep the request or job ID from each response: it identifies the call in Activity and for support.