Drop-in OpenAI compatible
The official openai clients work unchanged. Only the base URL and the key differ, so an application already calling OpenAI moves over in two lines.
Text to speech, file transcription and live realtime sessions behind one HTTP API — with scoped keys, per-second billing and request logs.
xVoice implements the OpenAI audio API, so the official clients work against it unchanged:
from openai import OpenAI
client = OpenAI(api_key="sk_live_...", base_url="https://your-xvoice-host/v1")Throughout these pages the base URL is written $XVOICE_BASE_URL. It is your deployment's host with /v1 on the end.
| Page | What it covers |
|---|---|
| Quickstart | Signup to a working transcript, in about five minutes |
| Authentication | API keys, test and live environments, key hygiene |
| Errors | The error envelope, every code, and what to do about each |
| Models and voices | What is available, and what capabilities means |
| Rate limits and billing | Tiers, headers, credits, spending limits |
| Endpoint | Purpose |
|---|---|
POST /v1/audio/speech | Text to speech, whole file or streamed |
POST /v1/audio/transcriptions | Transcribe an audio file |
WS /v1/realtime | Transcribe a live stream as it is captured |
GET /v1/models | List models, or read one |
GET /v1/voices | List voices |
GET /v1/me | What a key is attached to |
/docs on your deployment serves the generated OpenAPI schema for these endpoints. It cannot describe WS /v1/realtime — OpenAPI has no vocabulary for WebSockets — so that one is documented only here.
Every endpoint has a working script on the example scripts page, built on the stock openai Python client:
export XVOICE_API_KEY=sk_test_...
export XVOICE_BASE_URL=https://your-xvoice-host/v1
python tts.py "Hello from xVoice." # writes output/speech.wav
python stt.py output/speech.wav
python realtime.py output/speech.wavorg_…, proj_…, key_…, req_…. The prefix says what the thing is, which makes a misplaced id obvious in a log.X-Request-Id, and every error body repeats it as request_id. It is the one thing worth keeping when something goes wrong: it ties a report to the exact request.{"object": "list", "data": [...]}. Where a list can grow without bound it is keyset-paginated with an opaque next_cursor.