The API
Two ways in: a WebSocket that returns punctuated text while somebody is still talking, and an HTTP pipeline for files. Every example below was taken from the running server rather than written to describe it.
What can I build on the VibeVoice API?
Two things. The WebSocket at wss://vibevoice.net/api/stream takes 16 kHz mono PCM and returns punctuated text sub-second while the speaker is still going — that is what the desktop client uses. The HTTP endpoints transcribe files you already have. Both authenticate with an API key from your account.
There is no local or on-premise deployment: audio is transcribed on our servers, so an integration needs a network connection. If your constraint is that audio must never leave the machine, run Whisper yourself — we would rather say so here than after you have built against us.
Authentication
HTTP requests carry the key in an X-API-Key header. The WebSocket does not: it takes the key in the first text frame after the connection opens, and there is no query-parameter form of it — connecting with ?api_key= fails the handshake without explanation, which is the single most common way a first integration goes wrong.
X-API-Key: vv_live_your_key_here # HTTP
{"api_key": "vv_live_your_key_here"} # first WebSocket frameA working client, in four languages
# Install: pip install websockets
import asyncio
import json
import websockets
API_KEY = "your_api_key_here"
URL = "wss://vibevoice.net/api/stream"
async def stream_audio(pcm_chunks):
"""pcm_chunks yields raw int16 PCM bytes, 16 kHz mono."""
async with websockets.connect(URL) as ws:
# 1. Authenticate. The key goes in a text frame, not the URL.
await ws.send(json.dumps({"api_key": API_KEY}))
ack = json.loads(await ws.recv())
if ack.get("status") != "authenticated":
raise RuntimeError(ack)
# 2. Read transcript pieces while audio is still going out.
async def reader():
async for message in ws:
frame = json.loads(message)
if frame.get("is_final"):
break
if frame.get("text"):
print(frame["text"], end="", flush=True)
reading = asyncio.create_task(reader())
# 3. Send audio as binary frames, then close the input explicitly.
for chunk in pcm_chunks:
await ws.send(chunk)
await ws.send("END_STREAM")
await reading
asyncio.run(stream_audio(my_pcm_source()))Endpoints
wss://vibevoice.net/api/streamBidirectional streaming for live dictation. Authenticate with a text frame, send int16 PCM at 16 kHz mono as binary frames, close the input with END_STREAM.
Sub-second first text · /stream is the frozen legacy alias and behaves identically
/api/transcribeSynchronous transcription of a single file. The upload is opened as a WAV and nothing transcodes first, so this endpoint takes WAV and only WAV. The response body is the transcript as plain text.
multipart/form-data · optional initial_prompt · up to 10GB on Ultra
/api/jobs/submitThe queued pipeline, and the one to use for anything long or not already a WAV. Returns a job id immediately; poll GET /api/jobs/{id} for status and the finished transcript. Optional word-level timestamps and speaker diarization.
Accepts audio and video · include_timestamps, word_level_timestamps, enable_diarization
/oauth/device/codeDevice Code Grant, for a client with no browser of its own — the desktop app and CLI tools use it. Pair with /oauth/device/token to exchange an approved code for a key.
Same flow the desktop client runs on first launch
The complete parameter and response reference is in the Redoc explorer, generated from openapi.json.
From nothing to a first transcript
Four steps, and the free tier is enough to finish all of them.
Create an account and verify the email
Unverified accounts can sign in but cannot transcribe, so the first API call will return 401 until the link in the email is clicked. Thirty minutes a month on the free tier, with no card.
Generate a key in the dashboard
Keys are per-device rather than per-account, so a key that leaks can be revoked without breaking the other integrations. Treat it as a password: it is not scoped and it is not read-only.
Try the batch endpoint first
The curl example above is the shortest thing that proves the key works. A WAV, an X-API-Key header, and the transcript comes back as the response body. Debugging auth over a WebSocket is much harder.
Then move to the stream
Connect, send the key as a text frame, wait for the authenticated acknowledgement, then push int16 PCM. Getting the sample format wrong is the second most common failure: float32 bytes are accepted and transcribe as noise.
What the API does not do
- It does not run on your infrastructure. There is no self-hosted build, no container and no on-premise licence — audio is transcribed on our servers.
- It does not work offline, and it has no local fallback. A dropped connection ends the stream rather than queueing it.
- POST /api/transcribe takes WAV only. Other formats belong on /api/jobs/submit, which transcodes; sending an mp3 to the synchronous endpoint currently returns a 500 rather than a helpful error.
- There is no custom vocabulary or terminology training. You can bias a single request with initial_prompt, which is a hint and not a dictionary.
- Speaker diarization is available on the job pipeline only, not on the live stream.
- There is no webhook or push callback yet. Job completion is polled.
Common questions
Which audio format does the stream expect?+
Raw int16 PCM, 16 kHz, mono, sent as binary WebSocket frames — no container, no header, no WAV wrapper. Float32 samples are the usual mistake: they are accepted without error and transcribe as noise, because the handler reads the buffer as int16 regardless.
How is the WebSocket authenticated?+
With a JSON text frame, {"api_key": "..."}, sent as the first message after the connection opens. Wait for {"status": "authenticated"} before sending audio. There is no query-parameter or header form of this — a URL with ?api_key= fails.
What is the difference between /stream and /api/stream?+
Nothing functional. /stream is the original path and remains supported because desktop clients going back to version 0.1.0 still use it and cannot be force-updated. New integrations should use /api/stream.
Is there an official SDK?+
No. The protocol is a WebSocket and a multipart POST, which every language handles in its standard library or one common package, so a wrapper would mostly be maintenance. The four examples above are the reference implementation.
What are the rate limits?+
One hundred requests an hour on POST /api/transcribe and the same on job submission, per key. Separately, your plan caps monthly transcription minutes and maximum file size — those are the limits most integrations meet first.
Can I transcribe a file that is hours long?+
Yes, through /api/jobs/submit rather than the synchronous endpoint. It queues the work and returns an id to poll, so the request does not sit open for the duration. File-size caps still apply by plan.
Related
Last reviewed . Competitor pricing and platform support change; check theirs before buying.