Build Your First Voice Agent using Vapi
Overview
This guide demonstrates how to build a voice agent on Vapi powered end-to-end by Sarvam AI: listening with Saaras v3, thinking with Sarvam-30B, and speaking with Bulbul v3. Perfect for bringing natural Indian-language conversations to Vapi’s hosted calling infrastructure (web calls, phone numbers, and SIP).
Vapi doesn’t ship Sarvam as a built-in provider, but it exposes custom provider interfaces for exactly this purpose:
All three run through one small Node.js server you’ll copy-paste below: a single process, a single deploy.
Configure via API, not the dashboard. Vapi’s dashboard dropdowns don’t list the custom-transcriber and custom-voice providers; they can only be set through the Vapi API. This guide creates the assistant with a single curl command in Step 6, and tests it with a small web page in Step 7 (the dashboard’s “Talk to Assistant” button has a known bug with custom-transcriber assistants).
What You’ll Build
A Vapi voice agent that can:
- Listen to users speaking (in multiple Indian languages!)
- Understand and process their requests
- Respond back in natural-sounding voices
Quick Overview
- Get API keys (Vapi, Sarvam)
- Install packages:
npm install express ws sarvamai dotenv - Create
.envfile with your API keys - Copy the bridge server (~150 lines of Node.js)
- Run:
node server.js+ expose with ngrok - Create the assistant with one
curlcommand - Test from a browser test page
Quick Start
1. Prerequisites
- Node.js 18 or higher
- API keys from:
- ngrok (free account) to expose your local server to Vapi’s cloud
Vapi keys are per-organization. If you have multiple Vapi orgs, copy both keys from the same org; a key from another org is rejected with 401 Invalid Key or 403 Key doesn't allow assistantId.
2. Create the Project
3. Create Environment File
Create a file named .env in your project folder:
Placeholders you must replace are written as YOUR_* throughout this guide. Swap in your Sarvam API key here.
4. Write the Bridge Server
The server has three routes, one per pipeline stage:
/llm/chat/completions: forwards chat requests to Sarvam’s OpenAI-compatible API, adding your API key and disabling thinking mode (see the Note below)./api/custom-transcriber: receives Vapi’s live call audio over WebSocket (2-channel PCM: caller + assistant), splits the channels, and streams each to a Saaras v3 session./api/synthesize: receives Vapi’svoice-requestwebhooks, synthesizes with Bulbul v3, and returns the raw PCM audio Vapi requires.
Create sarvamTranscriber.js:
Create server.js:
Why the LLM proxy instead of pointing Vapi straight at https://api.sarvam.ai/v1? The direct connection works (Sarvam’s Chat Completions API is OpenAI-compatible and accepts the Bearer token Vapi sends), but Sarvam models have thinking mode on by default, so reasoning tokens stream before the reply, delaying the agent’s speech; with a small max_tokens the entire budget can be consumed by reasoning, leaving the reply empty. The proxy injects reasoning_effort: null on every request, which Vapi has no other way to pass. It also authenticates server-side, so you don’t need to configure a Custom LLM credential in Vapi at all.
5. Run and Expose the Server
In a second terminal, expose it with ngrok:
Note the public URL (e.g. https://your-id.ngrok-free.app): you’ll use it in the next step.
6. Create Your Assistant
Replace YOUR_VAPI_PRIVATE_KEY and the three your-id.ngrok-free.app URLs (model, transcriber, and voice) with your ngrok hostname, then run:
The response contains your assistant’s id: copy it for the next step.
Vapi treats the model url as an OpenAI baseURL and appends /chat/completions itself, which is why the value ends at /llm. The transcriber URL uses wss:// (WebSocket), the other two use https://.
7. Test Your Agent
Create test-call.html, a minimal page that starts a web call using Vapi’s Web SDK:
Fill in your public key and the assistant id, serve it locally, and open it in your browser:
Click Start Call, allow microphone access, and start speaking. You’ll see live transcripts on the page and in your server logs, and hear the agent reply in Bulbul’s voice.
That’s it! You’ve built a Vapi voice agent running entirely on Sarvam models!
Why a test page instead of the dashboard? Vapi’s dashboard has a known bug with custom-transcriber assistants: the “Talk to Assistant” button can fail with a 404 or a Cannot read properties of undefined (reading 'cost') error. The assistant itself is fine; calls made through the Web SDK, API, or a phone number work normally.
How a Call Flows
- Transcriber: Vapi opens one WebSocket per call and streams raw 16-bit PCM with the caller on channel 0 and the assistant on channel 1. The bridge splits the channels and runs a separate Saaras v3 streaming session for each, so both sides of the conversation are transcribed.
- LLM: Vapi sends standard OpenAI-style chat completion requests. The proxy adds your Sarvam key, disables thinking mode, and streams the response back token by token.
- Voice: for every reply (including the
firstMessage), Vapi POSTs avoice-requestwith the text and a target sample rate. The bridge synthesizes with Bulbul v3 at that exact sample rate and returns headerless mono PCM.
Going to Production
- Deploy the bridge server to any Node.js host and replace the ngrok URLs in your assistant (PATCH
https://api.vapi.ai/assistant/:id). Host in an Indian region since Sarvam’s APIs are served from India, so proximity cuts round-trip latency on every STT chunk and TTS request. - Secure the endpoints: set
server.secretin the transcriber/voice config and validate thex-vapi-secretheader on every request, or use Vapi’s Custom Credentials with acredentialId. - Add a fallback voice so calls continue on a built-in Vapi voice if your TTS endpoint is ever unreachable:
- Attach a phone number: in the Vapi dashboard, create or import a phone number and assign your assistant to it, and the same bridge serves phone calls with no changes.
Customization Examples
Example 1: Hindi Voice Agent
Set the bridge’s .env to Hindi:
And make the LLM reply in Hindi via the assistant’s system message:
Example 2: Multilingual Agent (Auto-detect)
Keep STT_LANGUAGE=unknown: Saaras v3 auto-detects the spoken language. Then tell the LLM:
Bulbul v3 handles code-mixed text (e.g. Hinglish) out of the box; set TTS_LANGUAGE to your primary audience’s language.
Example 3: Speech-to-English Agent (Saaras)
Difference: Saaras v3 handles both transcription (same-language output) and translation (English output) via the mode parameter. Use mode="translate" when users speak Indian languages but you want to process and respond in English.
In sarvamTranscriber.js, change the connection:
Note: Saaras v3 with mode="translate" automatically detects the source language (Hindi, Tamil, etc.) and translates spoken content directly to English text, so the rest of your pipeline stays English-only.
Available Options
Language Codes
Speaker Voices (Bulbul v3)
Male (23): Shubh (default), Aditya, Rahul, Rohan, Amit, Dev, Ratan, Varun, Manan, Sumit, Kabir, Aayan, Ashutosh, Advait, Anand, Tarun, Sunny, Mani, Gokul, Vijay, Mohit, Rehan, Soham
Female (14): Ritu, Priya, Neha, Pooja, Simran, Kavya, Ishita, Shreya, Roopa, Tanya, Shruti, Suhani, Kavitha, Rupali
Pro Tips
- Use
STT_LANGUAGE=unknownto automatically detect the language. Great for multilingual scenarios! - Sarvam’s models understand code-mixing: your agent can naturally handle Hinglish, Tanglish, and other mixed languages.
- Use
sarvam-30bfor voice agents: it has lower time-to-first-token thansarvam-105b, which matters on a live call. - Keep LLM replies short: spoken responses feel best under a couple of sentences, so say so in the system prompt.
- Vapi’s
sampleRatein eachvoice-requestmaps directly to Bulbul’sspeech_sample_rate: the bridge passes it through, so never hard-code a sample rate.
Troubleshooting
“Request failed with status code 404” or a cost error in the Vapi dashboard: Known dashboard bug with custom-transcriber assistants: the assistant works; test via the web page from Step 7 instead of the dashboard’s “Talk to Assistant” button.
401 Invalid Key / 403 Key doesn't allow assistantId: You’re mixing keys. Assistant creation needs the private key, browser calls need the public key, and both must come from the same Vapi organization. Verify with: curl -s -o /dev/null -w "%{http_code}" https://api.vapi.ai/assistant -H "Authorization: Bearer YOUR_VAPI_PRIVATE_KEY", which should print 200.
Agent connects but never speaks (empty LLM replies): You’re likely bypassing the LLM proxy. With thinking mode on (Sarvam’s default), reasoning can consume the entire max_tokens budget and return an empty reply: route the model URL through /llm as shown, which disables reasoning.
Garbled or distorted agent speech: The TTS response isn’t matching Vapi’s PCM spec: most commonly a WAV header left in the response or a sample-rate mismatch. Use the wavToRawPcm helper as-is and always pass message.sampleRate through.
No transcripts arriving: Confirm the transcriber URL starts with wss:// (not https://) and check the server logs for “Vapi connected to custom transcriber” and Sarvam auth errors (SARVAM_API_KEY missing or invalid).
ngrok URL stopped working: Free ngrok URLs change on every restart. Get the current URL from http://localhost:4040 and PATCH your assistant with the new endpoints.
Additional Resources
- Sarvam AI Documentation
- Sarvam Streaming Speech-to-Text API
- Sarvam Chat Completions API
- Sarvam Text-to-Speech API
- Vapi Custom Transcriber Guide
- Vapi Custom LLM Guide
- Vapi Custom TTS Guide
Need Help?
- Sarvam Support: developer@sarvam.ai
- Community: Join the Discord Community
Happy Building!