OpenAI-compatible API
Chat, images, video, audio and embeddings on one base URL. Model ids are provider/model.
| type | endpoint | example model |
|---|---|---|
| catalog | GET /v1/models | every model with its list price |
| text | POST /v1/chat/completions | aisingapore/gemma-sea-lion-v4-27b-it |
| image | POST /v1/images/generations | alibaba/qwen-image-3.0-pro |
| video | POST /v1/media/generations | alibaba/hh1-t2v |
| audio | POST /v1/media/generations | deepgram/aura-1 |
| embeddings | POST /v1/embeddings | baai/bge-base-en-v1.5 |
| realtime voice | GET /v1/realtime (WebSocket) | xai/grok-voice, elevenlabs/voice-agent |
| voices | GET /v1/voices | every 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.
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 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 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 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 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 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.updatedwith 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 anerroreventinsufficient_balance). Two sessions per account at once. - Tools. Function tools only; hosted tools (web search, remote MCP) are refused with an
errorevent. 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.updatetakesvoice(any voice ofGET /v1/voices?model=elevenlabs/voice-agent),instructions,languageandfirst_message, before or as the session starts; they are fixed after that. Turn-taking and interruptions are the agent's own (input_audio_buffer.commitends a push-to-talk turn;response.createis 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 witherrorvoice_unavailableand close code4503, without charge for the unsettled part.
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 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:
| format | endpoints | models |
|---|---|---|
| Anthropic Messages | POST /v1/messages, POST /v1/messages/count_tokens, GET /v1/models with anthropic-version | Anthropic 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 Responses | POST /v1/responses | OpenAI models (bare ids such as gpt-5.4-mini work) |
| Gemini API | POST /v1beta/models/{model}:generateContent, :streamGenerateContent?alt=sse, :countTokens, GET /v1beta/models | Gemini 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.