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 in allternit-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). The small pack (~39 MB: VAD + Moonshine + Smart Turn) downloads on first speech-to-text use, the tts pack (~350 MB: Kokoro v1.0) on first text-to-speech use; the accurate pack (~487 MB, Parakeet, CC-BY-4.0) downloads on first accurate-STT request. Poll GET /v1/models for missing | downloading | ready | error state. Files come from runtime.allternit.com/voice-packs/v1 with an upstream fallback; override the base with ALLTERNIT_VOICE_MODEL_BASE (see Owned downloads).
  • Inference uses ALLTERNIT_VOICE_THREADS threads (default 2), CPU provider, one model at a time, so voice stays light on a 4-core laptop.
Licences and attributions: 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

The voice service binds to 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 with ALLTERNIT_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

Synthesises speech and returns audio bytes (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

Streaming TTS: the text is split into sentences, the first one cut at its first clause (or about halfway) so the first audio is short, and each piece is emitted as soon as it is synthesised, so playback starts before the whole text is done.

Response

200 OK — application/x-ndjson stream:
A failure mid-stream sends {"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

Transcribes uploaded audio passed as multipart form data. Accepts WAV (PCM 8/16/24/32-bit, float32, G.711 μ-law/A-law; any sample rate; mono or stereo) or raw 16 kHz s16le PCM. Audio is resampled to 16 kHz and segmented by Silero VAD; each segment is transcribed with the selected model.

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 returns 503 right away (with a Retry-After: 5 header) instead of hanging:
Retry after a few seconds, or poll GET /v1/models. Desktop dictation shows the message to the user and a later press transcribes normally.

Streaming speech-to-text

Streaming STT: send raw s16le mono PCM in chunks as you capture it (?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 each ticket 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 at outputSampleRate from session.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 after silenceMs of silence (default 1500).
  • vad ends the turn after silenceMs of silence (default 700).

Example

To try it from a terminal, run 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.