Skip to content
docs

OpenAI-compatible API

Chat, images, video, audio and embeddings on one base URL. Model ids are provider/model.

typeendpointexample model
catalogGET /v1/modelsevery model with its list price
textPOST /v1/chat/completionsaisingapore/gemma-sea-lion-v4-27b-it
imagePOST /v1/images/generationsalibaba/qwen-image-3.0-pro
videoPOST /v1/media/generationsalibaba/hh1-t2v
audioPOST /v1/media/generationsdeepgram/aura-1
embeddingsPOST /v1/embeddingsbaai/bge-base-en-v1.5
realtime voiceGET /v1/realtime (WebSocket)xai/grok-voice, elevenlabs/voice-agent
voicesGET /v1/voicesevery voice you can pick, with samples

Chat

Standard chat completions, streaming or not. Streamed responses end with a usage chunk. Tool calls, JSON mode and vision work where the model supports them.

python
from openai import OpenAI

client = OpenAI(
    base_url="https://xavainf.com/v1",
    api_key="xk_live_...",  # your Xava Inference key
)

r = client.chat.completions.create(
    model="openai/gpt-5.4",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(r.choices[0].message.content)
curl
curl https://xavainf.com/v1/chat/completions \
  -H "Authorization: Bearer $XINF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-5.4","messages":[{"role":"user","content":"Hello!"}]}'

Images

curl
curl https://xavainf.com/v1/images/generations \
  -H "Authorization: Bearer $XINF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"alibaba/qwen-image-3.0-pro","prompt":"a pixel-art rocket","size":"1024x1024"}'

Video

curl
curl https://xavainf.com/v1/media/generations \
  -H "Authorization: Bearer $XINF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"alibaba/hh1-t2v","input":{"prompt":"a pixel-art rocket lifting off","duration":4}}'

Video and audio models take their own fields inside input; each model's page lists them. The request waits until the file is ready (a video can take a minute or more) and returns output (the model's own result) and files: each with url, content_type, bytes and expires_at. Files are deleted after 24 hours.

Audio

curl
curl https://xavainf.com/v1/media/generations \
  -H "Authorization: Bearer $XINF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"deepgram/aura-1","input":{"text":"Hello from one key."}}'

Embeddings

curl
curl https://xavainf.com/v1/embeddings \
  -H "Authorization: Bearer $XINF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"baai/bge-base-en-v1.5","input":"The quick brown fox"}'

Realtime voice

Talk to a voice model over one WebSocket: GET /v1/realtime?model=<id> with Upgrade: websocket and Authorization: Bearer <key> (offer the subprotocol realtime). The events are the OpenAI Realtime protocol: send session.update (voice, instructions, turn_detection, audio formats), stream input_audio_buffer.append (base64 PCM16, 24 kHz mono by default; 8-48 kHz PCM and G.711 also accepted), then input_audio_buffer.commit + response.create, or let server VAD decide. You get response.output_audio.delta, live transcripts of both sides, and response.done. Realtime models carry "realtime": true in /v1/models.

  • Billing as it runs. Usage is settled after every response (and at least once a minute) and each time the server sends billing.updated with that segment's cost and the session total. Before connecting and before each segment your balance must cover about two minutes of audio.
  • Limits. By default a session lasts at most 15 minutes (close code 4001), ends after 3 idle minutes (4003), and is cut when the balance runs out (4002, after an error event insufficient_balance). Two sessions per account at once.
  • Tools. Function tools only; hosted tools (web search, remote MCP) are refused with an error event. Unknown client events are refused the same way and never reach the model.
  • Privacy. Audio and transcripts pass through and are never stored.
  • elevenlabs/voice-agent. An ElevenLabs voice agent behind the same events. Audio is PCM16 at 16 kHz both ways. session.update takes voice (any voice of GET /v1/voices?model=elevenlabs/voice-agent), instructions, language and first_message, before or as the session starts; they are fixed after that. Turn-taking and interruptions are the agent's own (input_audio_buffer.commit ends a push-to-talk turn; response.create is not needed). Billed per minute the session is connected, silence included. Sessions are short (a few minutes) while capacity is limited; when the voice is at capacity the session ends with error voice_unavailable and close code 4503, without charge for the unsettled part.
node (ws)
import WebSocket from "ws";

const ws = new WebSocket("wss://xavainf.com/v1/realtime?model=xai/grok-voice", "realtime", {
  headers: { Authorization: `Bearer ${process.env.XINF_API_KEY}` },
});
ws.on("open", () => {
  ws.send(JSON.stringify({ type: "session.update", session: {
    voice: "ara", instructions: "Be brief.",
    turn_detection: { type: "server_vad" },
    audio: { input: { format: { type: "audio/pcm", rate: 24000 } }, output: { format: { type: "audio/pcm", rate: 24000 } } },
  } }));
  // stream microphone audio: base64 PCM16, 24 kHz mono
  // ws.send(JSON.stringify({ type: "input_audio_buffer.append", audio: chunk.toString("base64") }));
});
ws.on("message", (raw) => {
  const e = JSON.parse(raw.toString());
  if (e.type === "response.output_audio.delta") play(Buffer.from(e.delta, "base64"));
  if (e.type === "response.output_audio_transcript.delta") process.stdout.write(e.delta);
  if (e.type === "billing.updated") console.log("\nsession cost $" + e.session.cost_usd);
});

Voices

GET /v1/voices lists every voice you can pick, across every speech and voice model: id, the value to pass (voice) and the parameter that takes it (param: voice_id, voice or speaker), name, maker, gender, accent, age, style, languages, the models that take it, and a preview_url sample clip where one exists. Filter with ?model=, ?maker=, ?gender=, ?language= or a text search ?q=. GET /v1/voices/{id} returns one voice, GET /v1/voices/{id}/preview its sample (audio/mpeg). No key needed.

curl
curl https://xavainf.com/v1/voices?model=elevenlabs/eleven-v3

# then pass a voice's "voice" value in its "param":
curl https://xavainf.com/v1/media/generations -H "Authorization: Bearer $XINF_API_KEY" -H "Content-Type: application/json" \
  -d '{"model": "elevenlabs/eleven-v3", "input": {"text": "Hello!", "voice_id": "JBFqnCBsd6RMkjVDRZzb"}}'

Native formats

The same key also speaks three providers' own APIs, for SDKs and agents built on them:

formatendpointsmodels
Anthropic MessagesPOST /v1/messages, POST /v1/messages/count_tokens, GET /v1/models with anthropic-versionAnthropic models; native ids such as claude-haiku-4-5-20251001 or claude-sonnet-4-6 map to ours. Auth: x-api-key or Authorization: Bearer
OpenAI ResponsesPOST /v1/responsesOpenAI models (bare ids such as gpt-5.4-mini work)
Gemini APIPOST /v1beta/models/{model}:generateContent, :streamGenerateContent?alt=sse, :countTokens, GET /v1beta/modelsGemini ids such as gemini-2.5-flash, or any chat model by its full id. Auth: x-goog-api-key

Errors use each format's own shape. Token counts are free estimates. Setup for Claude Code, Codex and Gemini CLI: agent docs.

Media endpoints are in preview: request shapes may change before launch. Media is billed per output unit (image, second of video, character or minute of audio), shown on each model's page.

Generated files are deleted after 24 hours. Every media URL (/media/<id> on this origin) comes with expires_at (unix seconds, 24 hours after creation); download or copy the file before then. After that the URL answers 410 Gone.