╔══════════════════════════════════════════════════════════════╗ ║ Bolo Voice Service — Vapi Integration Guide ║ ╚══════════════════════════════════════════════════════════════════╝ Production URL: https://omnivoice.apps.minascode.com/ This document describes the Vapi-compatible endpoints available on this server. ────────────────────────────────────────────────────────────── CUSTOM TTS (HTTP POST) ────────────────────────────────────────────────────────────── POST /v1/synthesize Vapi assistant voice config: "voice": { "provider": "custom-voice", "server": { "url": "https://omnivoice.apps.minascode.com/v1/synthesize", "secret": "your-vapi-secret", "timeoutSeconds": 30 } } Request body (Vapi format) — all message fields are optional except `type` and `text`: { "message": { "type": "voice-request", "text": "Text to synthesize", "sampleRate": 24000, "voice": "vp_1 | alloy | shimmer | ...", "instruct": "female, low pitch, british accent", "language": "en", "speed": 1.0, "duration": 5.0, "numStep": 16, "guidanceScale": 2.0, "tShift": 0.1, "layerPenaltyFactor": 5.0, "positionTemperature": 5.0, "classTemperature": 0.0, "denoise": true, "preprocessPrompt": true, "postprocessOutput": true, "audioChunkDuration": 15.0, "audioChunkThreshold": 30.0, "voicePassword": "secret123" } } Query parameters — same keys as message body. Body values override query. ?voice=vp_1&instruct=female,%20low%20pitch&language=en&sampleRate=24000 Response: Content-Type: application/octet-stream Body: raw PCM s16le mono at the requested sample rate Supported sample rates: 8000, 16000, 22050, 24000, 44100 Hz Authentication (in priority order): 1. X-VAPI-SECRET header (recommended) 2. x_vapi_secret query param (when headers are unavailable) 3. Authorization: Bearer (standard API key) 4. api_key query param (when headers are unavailable) If the server is started with --vapi-secret, that secret must be provided via header or query param. If only --api-key is set, the API key must be provided via header or query param. Examples: # Minimal curl -X POST http://localhost:8080/v1/synthesize \ -H "Content-Type: application/json" \ -H "X-VAPI-SECRET: your-secret" \ -d '{"message":{"type":"voice-request","text":"Hello!","sampleRate":24000}}' \ --output speech.pcm # With voice profile (body) curl -X POST http://localhost:8080/v1/synthesize \ -H "Content-Type: application/json" \ -H "X-VAPI-SECRET: your-secret" \ -d '{ "message":{ "type":"voice-request", "text":"Hello!", "voice":"vp_1", "instruct":"female, low pitch", "sampleRate":24000 } }' --output speech.pcm # With voice profile (query params) curl -X POST "http://localhost:8080/v1/synthesize?voice=vp_1&instruct=female,%20low%20pitch" \ -H "Content-Type: application/json" \ -H "X-VAPI-SECRET: your-secret" \ -d '{"message":{"type":"voice-request","text":"Hello!","sampleRate":24000}}' \ --output speech.pcm # Auth via query param — useful when platform can't set custom headers curl -X POST "http://localhost:8080/v1/synthesize?voice=shimmer&x_vapi_secret=your-secret" \ -H "Content-Type: application/json" \ -d '{"message":{"type":"voice-request","text":"Hello!","sampleRate":24000}}' \ --output speech.pcm ────────────────────────────────────────────────────────────── CUSTOM TRANSCRIBER (WebSocket) ────────────────────────────────────────────────────────────── Endpoint: GET /v1/ws/transcribe (WebSocket upgrade) Vapi assistant transcriber config: "transcriber": { "provider": "custom-transcriber", "server": { "url": "wss://omnivoice.apps.minascode.com/v1/ws/transcribe", "secret": "your-vapi-secret" } } Protocol: 1. Vapi connects via WebSocket and sends a start message: { "type": "start", "encoding": "linear16", "container": "raw", "sampleRate": 16000, "channels": 2 } 2. Vapi streams binary PCM s16le audio frames. 3. The server buffers audio, detects utterance boundaries using energy-based VAD (~1 second of silence triggers transcription), and runs the configured Whisper ASR model on each utterance. 4. Server sends transcription results: { "type": "transcriber-response", "transcription": "transcribed text", "channel": "customer", "transcriptType": "final" } 5. On WebSocket disconnect, any remaining buffered audio is transcribed and sent before closing. Authentication (in priority order): 1. X-VAPI-SECRET header → matched against --vapi-secret 2. Authorization: Bearer Notes: - Whisper is utterance-based, not word-by-word streaming. Each detected utterance is transcribed after ~1s of silence. - Audio is trimmed at 30 seconds max during profile creation to prevent model position-embedding overflow. - The server must be started with --vapi-secret or --api-key for authentication.