Overview
The Voice API provides text-to-speech (TTS), speech-to-text (STT), voice session management, and service introspection. The Rust voice service (services/voice) listens on port 8001 (override with the PORT environment variable). Both STT and TTS are real and run on sherpa-onnx inside the binary:
- STT: Silero VAD segments the audio, then Moonshine tiny (default) or Parakeet TDT 0.6B v3 (accurate) transcribes each segment. Any sample rate is accepted and resampled to 16 kHz; 8 kHz phone audio (including G.711 μ-law WAV) works.
- TTS: Kokoro-82M v1.0 (fp32), 28 English voices (default
af_heart), 24 kHz output. Kokoro runs inallternit-tts, a separate GPL-3.0 program the service starts as a child process. - Models download on first use into
~/.allternit/models/voice/<pack>/(sha256-verified). Thesmallpack (~39 MB: VAD + Moonshine + Smart Turn) downloads on first speech-to-text use, thettspack (~350 MB: Kokoro v1.0) on first text-to-speech use; theaccuratepack (~487 MB, Parakeet, CC-BY-4.0) downloads on first accurate-STT request. PollGET /v1/modelsformissing | downloading | ready | errorstate. Files come fromruntime.allternit.com/voice-packs/v1with an upstream fallback; override the base withALLTERNIT_VOICE_MODEL_BASE(see Owned downloads). - Inference uses
ALLTERNIT_VOICE_THREADSthreads (default 2), CPU provider, one model at a time, so voice stays light on a 4-core laptop.
services/voice/THIRD_PARTY_NOTICES.md (sherpa-onnx Apache-2.0; Moonshine MIT; Kokoro Apache-2.0; Silero VAD MIT; Smart Turn BSD-2-Clause; Parakeet CC-BY-4.0, attribution required; onnxruntime MIT). The voice service contains no GPL code; allternit-tts is GPL-3.0-or-later because it links espeak-ng (services/voice-tts/LICENSE).
Base URL
127.0.0.1:8001 by default. When deployed behind the Allternit gateway, it may be exposed under a platform path; consult your deployment’s routing table.
Authentication
The standalone voice service does not require authentication in local development. When exposed through the platform API, standard bearer-token auth applies. See Authentication.Desktop sidecar token
Allternit Desktop starts the voice service withALLTERNIT_VOICE_TOKEN set to a random secret generated for each app run. The Voice Session WebSocket (/v1/voice/session?token=…) accepts that token; without it, the service accepts loopback connections only. The app gets the URL and token from the Desktop preload bridge:
token is present only when this Desktop run started the service itself; a service that was already running never received this run’s token, so the app connects over loopback without one. The token never leaves the machine and is not shown in the UI.
Resources
VoiceModel
SttModel
PackStatus
VoiceSession
Endpoints
Health check
Example response
List voices
Example response
Text-to-speech
audio/wav by default; pass
format: "pcm16" for raw s16le mono 24 kHz). The first request downloads
the small pack if it is not on disk yet.
Request body
Example request
Response
200 OK: audio/wav bytes (or raw pcm16 with format: "pcm16") with
X-Duration-Seconds, X-Sample-Rate, and X-Format headers. 400 for
empty text, an unknown voice or an unsupported format; 503 if the model
could not be downloaded or loaded. Errors are JSON: { "error": "..." }.
Streaming text-to-speech
Response
200 OK — application/x-ndjson stream:
{"type":"error","error":"..."} and ends the
stream.
Decode each audio_b64 to raw s16le mono PCM at sample_rate; concatenating
all chunks yields the full utterance.
Speech-to-text
Request body (multipart)
Example request
Example response
segments carry VAD-derived timestamps. confidence is a heuristic —
the offline recognisers do not emit per-word confidence.
First use: voice pack downloading
If the model pack is not on disk yet, the request starts the download and returns503 right away (with a Retry-After: 5 header) instead of hanging:
GET /v1/models. Desktop dictation shows
the message to the user and a later press transcribes normally.
Streaming speech-to-text
?sample_rate=, default 16000; ?model=moonshine|parakeet) and read
application/x-ndjson events as speech is segmented:
partial is an interim transcript of the speech in progress (each replaces
the last); final is a finished segment, sent after 0.3 s of silence, with
times in seconds from the start of the stream. Finish the request body to
flush trailing speech and get done. Several streams can run at once.
Model packs
Example response
POST /v1/models/small or POST /v1/models/accurate starts that download
in the background and returns 202 with the pack’s status.
Create a session
Request body
Example request
Example response
Service stats
Example response
Error codes
Voice Session (realtime)
GET /v1/voice/session is a WebSocket for live voice conversations. It carries mic audio in, live transcripts and end-of-turn events out, and speaks reply text as it streams in. Barge-in stops the speech when the user talks over it. Implementation IMPLEMENTED (engine traits, state machine, WebSocket route, Smart Turn), with the speech engine wired once the sherpa-onnx engine lands; Exposure INTERNAL (Desktop sidecar). The canonical protocol is services/voice/spec/VOICE_SESSION.md.
Voice is input and output only. The app sends each final user turn to the agent as a normal chat message, then streams the agent’s reply text back to be spoken.
Connect
token must match the service’s ALLTERNIT_VOICE_TOKEN. If that variable is unset, only loopback connections are accepted. A failed check returns 401 before the upgrade, with a message that says whether the ticket was invalid or Allternit Cloud could not be reached.
Cloud tickets and usage
On Allternit Cloud the service redeems eachticket against cloud-api before the upgrade. Tickets are single-use. It does this only when both variables are set; otherwise every ticket is refused.
A ticket session reports
engine: "cloud" in session.ready. The ticket carries a maxSeconds limit. When the session reaches it, the service sends error with code: "session_limit" and fatal: true, then closes the socket.
When the session closes for any reason, the service reports the wall-clock duration (rounded up to whole seconds) to cloud-api as Cloud Voice minutes. It retries in the background for about a minute, and cloud-api ignores duplicates for the same session. Token and loopback sessions, such as the Desktop sidecar and the call worker, report nothing.
Frames
- Text frames are JSON with a
type. - Binary frames are audio. Client → server is PCM16 LE mono mic audio at
inputSampleRate(default 16000, 20 ms frames). Server → client is PCM16 LE mono speech atoutputSampleRatefromsession.ready.
Turn detection
smart(default) runs Smart Turn v3.2 at each pause. A score of 0.5 or more ends the turn at once. Below that, the turn ends aftersilenceMsof silence (default 1500).vadends the turn aftersilenceMsof silence (default 700).
Example
cargo run -p voice-service --example voice_session_client -- --wav question.wav --say "It is noon." --out reply.wav.
Custom voices (cloud-api)
Custom voices live in the Allternit Cloud API, not the voice service. All routes need a signed-in user, except the two worker routes, which need the voice worker token. See the Custom voices guide.
Errors carry
{ "code", "message" }: consent-required (422), consent-text-changed (409), bad-clip (422), clip-too-large (413), too-many-voices (409).
The voice service checks consent through the worker routes before it speaks a custom: voice, and refuses with the realtime error code custom_voice_refused if consent is missing or revoked. If it can’t reach the cloud to check, it reports the voice as unavailable rather than speaking. The voice’s engine is Pocket TTS, from the voices model pack.