This Vapi tutorial builds an inbound voice agent that answers a free US number and books appointments into Google Calendar, with no code required for the first version. It explains each assistant setting (transcriber, model, voice, tools), shows the Function tool webhook format for your own backend, and prices a three-minute call at about $0.25 to $0.39 from Vapi's published rates.
This Vapi AI tutorial takes you from an empty account to a voice agent that answers a real phone number and books appointments into Google Calendar, in eight steps and without code for the first version. Vapi gives you $5 in credits and one free US number to do it, which is enough for a dozen or more test calls.
Checked October 2026 against Vapi's assistants quickstart, Create Assistant API reference, Google Calendar and Function tools docs, free phone numbers page and pricing page. Steps and code follow those docs; the costs are calculated from published rates, not from logged calls.
At the end you will have an assistant with a prompt you wrote, two calendar tools, a phone number and a call log you can read. For how Vapi compares with building the same thing on other stacks, see our voice AI hub on building AI phone agents.
What is a Vapi voice AI agent made of?
A Vapi assistant is a saved configuration with three engines and a set of tools. Every turn of a call passes through them in the same order.
- 01Caller speaks
Audio arrives over a phone number or a browser call.
- 02Transcriber
Speech becomes text and Vapi decides the caller has finished.
- 03Model
The LLM reads the prompt and history, then replies or calls a tool.
- 04Tool
Calendar, your webhook or an API returns a result as text.
- 05Voice
The reply is turned into speech and played back.
You pick each engine yourself or take a model preset. New assistants start on Balanced; the other presets are High Intelligence, Ultra Fast and Cost Saver. Swapping any single component moves the assistant to Customized.
Before you start: the pre-flight check
- A Vapi account at dashboard.vapi.ai; the free credits cover this build
- A Google account with a test calendar, not the one real customers are booked into
- A phone to call the number from
- The facts the agent needs: opening hours, services, appointment length and timezone
- A private API key only if you will use the API; a public key only for the website widget
- Node.js and a tunnel such as ngrok only if you build the custom Function tool
Vapi AI tutorial: the eight steps
Each step lists what you should see when it has worked. Menu names are the ones in Vapi's docs as of October 2026.
- 1Create the assistant
Assistants, then the arrow next to Create Assistant, then Appointment Scheduler. Expected: a draft with a first message, a system prompt and the Ultra Fast preset.
- 2Rewrite the first message and prompt
Name the business, cap replies at 30 words, one question at a time. Expected: the greeting matches what the agent does.
- 3Publish, then press Talk
Publish, Quick Publish, then Talk for a browser call. Expected: it greets you and holds a short conversation.
- 4Connect Google Calendar
Integrations, Tools Provider, Google Calendar, Connect. Expected: the Google authorization window completes.
- 5Create the two calendar tools
Tools, Create Tool, Google Calendar: one Check Availability tool, one Create Event tool. Expected: both appear in your tools list.
- 6Add the tools and publish again
Assistant, Tools tab, select both, name them in the prompt, Publish. Expected: a Talk call creates an event in the test calendar.
- 7Get a phone number
Phone Numbers, Create Phone Number, Free Vapi Number, a US area code. Under Inbound Settings choose the assistant. Expected: active within a few minutes.
- 8Call it and read the log
Book an appointment by phone, then open Logs, Calls. Expected: transcript, recording, the tool calls and a Call Cost tab.
Two details in those steps save the most time. First, edits are saved as a draft and do not affect calls until you publish, so "it ignored my change" usually means an unpublished draft. Second, the tool description is what the model reads when it decides whether to call a tool, so write it as an instruction: when to use it, and what must be known first.
The assistant config, field by field
The dashboard and the API edit the same object. These are the fields that decide how a booking call feels, with the defaults from the API reference.
| Field | What it controls | Default or documented behaviour | What we would set for a booking line |
|---|---|---|---|
| firstMessage | What the agent says when the call connects | If unset, it waits for the caller to speak first | A short greeting with the business name |
| transcriber | Speech to text: provider, model, language | Billed per minute of audio, for both sides of the call | Deepgram nova-3 in English; Deepgram Flux adds built-in turn detection |
| model | The LLM, plus the system prompt in messages | temperature defaults to 0.5, maxTokens to 250 per turn | A mid-size model; raise maxTokens if tool arguments get cut off |
| model.tools and toolIds | What the agent can do during a call | tools are defined inline; toolIds point to saved tools | The two calendar tools |
| voice | Text to speech: provider and voiceId | Billed per character spoken | One voice, short replies |
| startSpeakingPlan.waitSeconds | How long it waits after the caller stops talking | 0.4 seconds | Raise toward 1.0 if it talks over slow speakers |
| maxDurationSeconds | Hard cap on call length | 600 seconds (10 minutes) | Leave it; the cap also limits a runaway bill |
| endCallMessage | What it says when it ends the call | If unset, it hangs up without saying anything | A one-line goodbye |
| server.url | Where Vapi posts events and Function tool calls | Resolved from the tool, then assistant, phone number, organization | Your webhook, only if you use Function tools |
Here is the same assistant as one API call. It follows the request shape in Vapi's quickstart and Google Calendar docs, and assumes you have already connected Google Calendar in the dashboard. Northside Dental is a made-up example.
export VAPI_API_KEY="YOUR_PRIVATE_API_KEY"
curl --request POST \
--url https://api.vapi.ai/assistant \
--header "Authorization: Bearer $VAPI_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"name": "Front Desk",
"firstMessage": "Thanks for calling Northside Dental. How can I help?",
"transcriber": { "provider": "deepgram", "model": "nova-3", "language": "en" },
"model": {
"provider": "openai",
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "You are the front desk assistant for Northside Dental. Keep replies under 30 words and ask one question at a time. To book, collect the caller name, the reason for the visit and a preferred day and time. Call checkAvailability first. If the slot is free, call scheduleAppointment with the reason as the summary. Read the date and time back before you book. All appointments are 30 minutes. Current date and time: {{now}}"
}
],
"tools": [
{ "type": "google.calendar.availability.check", "name": "checkAvailability", "description": "Use this tool to check calendar availability." },
{ "type": "google.calendar.event.create", "name": "scheduleAppointment", "description": "Use this tool to schedule appointments and create calendar events. All appointments are 30 minutes." }
]
},
"voice": { "provider": "11labs", "voiceId": "cgSgspJ2msm6clMCkdW9" },
"endCallMessage": "Thanks for calling. Goodbye.",
"maxDurationSeconds": 600
}'Copy the id from the response; you need it to attach a phone number or start a web call. The quickstart uses gpt-4o, and the API reference lists newer OpenAI IDs such as gpt-5.4-mini and gpt-6-luna as accepted values, so swap the model once the flow works.
One trap: {{now}} is the current time in UTC, and the calendar tools also default to UTC. If your callers are not in UTC, give the prompt local time with the date filter Vapi documents, and state the timezone in the tool description too:
Current date and time: {{"now" | date: "%A, %B %d, %Y, %I:%M %p", "America/New_York"}}Wiring the booking function: built-in tool or your own
The Google Calendar tools need no server. The Create Event tool takes a summary, start and end times in ISO 8601, attendees, a timezone and a calendar ID that defaults to the primary calendar. Reach for a Function tool when the booking has to land in your own system.
- No code and no server to host
- Check availability, then create the event
- Vapi controls the tool schema
- Right for one calendar and fixed-length slots
- You define the parameters the model must fill
- Your server gets the call context with every request
- Works with any scheduler, CRM or database
- You own uptime, speed and the response format
A Function tool is a JSON schema plus a server URL. Create it once and reuse it across assistants:
curl --request POST \
--url https://api.vapi.ai/tool \
--header "Authorization: Bearer $VAPI_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"type": "function",
"function": {
"name": "book_appointment",
"description": "Books an appointment after the caller has confirmed the service, date and time.",
"parameters": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Caller full name" },
"service": { "type": "string", "description": "Service requested" },
"startDateTime": { "type": "string", "description": "Start time in ISO 8601 with a timezone offset" }
},
"required": ["name", "service", "startDateTime"]
}
},
"server": { "url": "https://YOUR_DOMAIN/vapi/tools" }
}'When the model calls it, Vapi sends a POST with a tool-calls message. Your server replies with one result per call. This Express handler follows the request and response format in the Function tools docs; createBooking stands for your own code.
import express from 'express'
const app = express()
app.use(express.json())
// Vapi posts every Function tool call here as a "tool-calls" message
app.post('/vapi/tools', async (req, res) => {
const message = req.body.message
if (!message || message.type !== 'tool-calls') return res.sendStatus(200)
const results = []
for (const toolCall of message.toolCallList) {
const { name, arguments: args } = toolCall.function
try {
if (name !== 'book_appointment') throw new Error('Unknown tool: ' + name)
const booking = await createBooking(args) // your scheduling API or database
results.push({
toolCallId: toolCall.id,
result: 'Booked for ' + args.startDateTime + '. Reference ' + booking.id + '.',
})
} catch (err) {
results.push({ toolCallId: toolCall.id, error: String(err.message).replace(/\s+/g, ' ') })
}
}
// Always HTTP 200. A failure goes in "error", never in the status code.
res.status(200).json({ results })
})
app.listen(3000)To test locally, Vapi's CLI forwards events to your machine with vapi listen --forward-to localhost:3000/vapi/tools, but it does not create a public URL, so run a tunnel alongside it. If your backend is a workflow tool, Vapi documents an n8n route using an API Request tool and a published webhook; our n8n webhook guide covers the receiving side.
How do you run a Vapi AI demo call?
The fastest demo is the Talk button: a browser call to the published assistant, no phone number needed. To let other people try it on your site, use the Web SDK with a public API key. Restrict that key to your domain under Allowed Origins and to this assistant under Allowed Assistants.
import Vapi from '@vapi-ai/web';
const vapi = new Vapi('YOUR_PUBLIC_API_KEY');
vapi.start('YOUR_ASSISTANT_ID');
vapi.on('call-start', () => console.log('Call started'));
vapi.on('call-end', () => console.log('Call ended'));Install it with npm install @vapi-ai/web. Never ship the private key to a browser; it is for server-side requests only.
What a call costs, from Vapi's own calculator
Vapi bills a hosting fee per minute and passes the transcriber, model and voice through at provider cost. The figures below are the ranges shown on the pricing page calculator, checked October 2026, applied to a three-minute call. The call length is an illustrative input, and we have not logged production calls for this tutorial.
| Component | Per minute | 3-minute call, low | 3-minute call, high |
|---|---|---|---|
| Vapi hosting | $0.05 | $0.150 | $0.150 |
| Transcriber (Deepgram) | $0.0095 to $0.0099 | $0.029 | $0.030 |
| Model (OpenAI) | $0.0077 to $0.0452 | $0.023 | $0.136 |
| Voice (ElevenLabs) | $0.0146 to $0.0238 | $0.044 | $0.071 |
| Telephony (Vapi number) | Free | $0 | $0 |
| Total | $0.0818 to $0.1289 | $0.25 | $0.39 |
Source: vapi.ai/pricing calculator, checked October 2026
Three things to take from it. The hosting fee is the floor: nothing you configure lowers it. The model is the widest range, and Vapi's cost docs say prompt and tool-definition size is the biggest lever you control, because both are resent on every request. And a number on your own Twilio account adds the carrier's rate, listed at $0.008 a minute inbound.
At these rates the $5 credit covers roughly 12 to 20 three-minute test calls. Usage-only accounts get 4 concurrent calls; the Core package at $29 a month raises that to 10. Your real figure is in the Call Cost tab of each call log. For the raw model rates behind the middle row, see our LLM API pricing comparison.
Troubleshooting
- Changes do not show up on calls. You edited the draft. Publish, then Quick Publish.
- The tool never fires. Use the exact tool name in the prompt and make the description specific. Vapi's troubleshooting guide also suggests
strict: trueon the function to surface schema errors in the call log. - "No result returned". Wrong response shape or a non-200 status. Check Logs, Webhooks for what your server sent.
- Tool arguments are cut off. Raise
maxTokenson the model, not on the tool. It accepts 50 to 10,000. - Bookings land at the wrong hour. A UTC default somewhere. Set the timezone in the prompt and in the tool description.
- The number does not ring. New numbers take a few minutes to activate. Free numbers are US-only and inbound-only.
- It interrupts callers. Raise
startSpeakingPlan.waitSecondsfrom its 0.4 second default.
Before real customers call, test against a sandbox calendar and re-run the awkward cases: a taken slot, a caller who changes their mind, a tool error. If you are choosing a platform as well as learning one, our Bland AI vs Vapi vs Retell comparison prices the same call on each, and the LiveKit Agents guide covers the open-source route.
A working agent is the easy half. If you plan to sell it, as a product or as a service to local businesses, our AI SaaS Builder program covers the parts around it: validating demand, pricing from your unit costs and billing with Stripe.
Vapi tutorial: FAQ
Is Vapi free to try?
Yes, within limits. Vapi's pricing page lists $5 in free credits with no success package, and you can create one free US phone number without adding a payment method (checked October 2026). The number is free but calls are not: each minute draws on your credits. Free numbers take inbound calls only, so outbound testing needs a number imported from Twilio, Telnyx or DIDWW.
Do I need to code to build a Vapi voice agent?
Not for a first agent. The dashboard has templates, model presets, a Talk button for browser test calls, free phone numbers and a Google Calendar integration, all without code. You need code when the agent must call your own backend through a Function tool, when you embed calls in a website with the Web SDK, or when you create assistants in bulk through the API.
How much does a Vapi call cost?
Vapi charges $0.05 per minute for hosting and passes model costs through. Its pricing calculator (checked October 2026) shows Deepgram transcription at about $0.01 a minute, OpenAI models from $0.0077 to $0.0452 and ElevenLabs voices from $0.0146 to $0.0238, which totals roughly $0.08 to $0.13 a minute. A three-minute call on a Vapi number is therefore about $0.25 to $0.39.
Can a Vapi agent book appointments into a calendar?
Yes. Vapi has a Google Calendar integration with two tools, one that checks availability and one that creates events. You connect a Google account under Integrations, create the tools, add them to the assistant and publish. For other schedulers, a Function tool or an API Request tool can call your own booking endpoint, and Vapi documents a GoHighLevel integration as well.
Why does my Vapi tool return "no result returned"?
The response does not match the format Vapi expects. Your webhook must reply with HTTP 200 and a JSON body containing a results array, where each item has the toolCallId from the request and a result or error that is a single-line string. Any other status code is ignored, and objects, arrays or line breaks in the result cause it to be dropped.
How do I run a Vapi AI demo on my website?
For a quick demo, press Talk on the assistant in the dashboard, which starts a browser call with no phone number. To put it on a site, install the @vapi-ai/web package, create a public API key restricted to your domain and assistant, and call vapi.start with the assistant ID. Vapi also publishes a copy-paste HTML script tag that adds a call button.
Can I make outbound calls with a free Vapi number?
No. Vapi's docs state that free numbers use US area codes and support inbound calls only, and they cannot be used for outbound campaigns or ported to another carrier. To place outbound or international calls, import a number from Twilio, Telnyx or DIDWW, or connect a SIP trunk. You are also responsible for consent and do-not-call rules on outbound calls.
The agent answers. Now turn it into something you can sell.
AI SaaS Builder, included in All Access, takes you from a working build to a priced product: validation, the Claude API, deployment, pricing from unit costs and Stripe billing. The other three programs, live coaching and the private community come with it.
Stuck on a step? Ask other builders
Join the free Discord to share your assistant config, compare per-minute costs and get a second pair of ears on a test call.