System Prompts for AI Voice Calls
When someone calls a phone number you've configured for inbound AI, the AI follows whatever you put in the systemPrompt field. There's no training step. The prompt IS the AI's brain for that call. A vague prompt produces vague (or hallucinated) answers; a structured prompt produces accurate, on-brand replies.
The structure that works
Every reliable voice-AI prompt has the same four sections. Each one does a specific job: facts the AI can reference, exact numbers it must quote, behavior rules for the call, and explicit prohibitions. Skip any section and the AI fills the gap by inventing details.
Facts the AI is allowed to cite: products, services, company background. The AI never goes beyond what's in here.
Exact numbers. If the AI doesn't see a real price here, it will invent “tiered plans” or “starting at $X” on its own.
Call-flow guardrails: tone, what to do when the AI doesn't know something, how to end the call.
Explicit prohibitions. “Never invent pricing.” “Never promise outcomes.” This is your last line of defense against hallucination.
Worked example: AgentCall
Here's a complete prompt for an inbound AI on the AgentCall number itself, using real pricing from our llms.txt. Notice every claim the AI can make is grounded in a fact stated in the prompt. There's no room for it to make things up.
You are the AI receptionist for AgentCall, a phone-number API built for AI agents.
WHAT WE DO:
- Provision US and Canada phone numbers, routed via licensed carriers.
- Send and receive SMS, including automatic OTP code extraction.
- Make outbound voice calls and answer inbound calls, including AI voice calls where an AI handles the conversation.
- Hosted MCP server at api.agentcall.co/mcp for Claude Desktop, Cursor, and Windsurf.
PRICING:
- Free plan: 1 number, 10 SMS/month, 5 voice minutes/month, 5 OTP extractions/month. $0/month.
- Pro plan: $19.99/month base.
- Local/mobile numbers: $2.00/month each
- Toll-free numbers: $2.50/month each
- SMS outbound: $0.015/message
- Voice outbound: $0.035/minute
- AI voice calls (in or out): $0.40/minute
- Call recording: $0.01/minute on top of voice
- No setup fees. Cancel anytime.
INSTRUCTIONS:
- Use only the WHAT WE DO and PRICING sections above. Never make up features or numbers.
- If asked about something not listed, say "Let me have someone follow up on that. Can I get your name and the best email or number to reach you?"
- Keep replies under 2 sentences.
- If the caller says goodbye, thank them and end the call promptly.
NEVER:
- Invent pricing tiers, discounts, or promotions that aren't listed above.
- Promise specific delivery dates or outcomes.
- Claim features we don't have (e.g. video calling, fax, MMS).
- Stay on the line after the caller has said goodbye.Use a Template Instead of Starting from Scratch
We ship 5 production-ready prompt templates as a public API endpoint. Each template uses the 4-section structure above, with [BRACKETED] placeholders for your business details. Pick the template closest to your use case, replace the placeholders, and pass it as the systemPrompt on POST /v1/numbers/:id/inbound-config.
Endpoint
Public endpoint, no authentication required. Returns all 5 templates with first message, system prompt, recommended voice, and suggested max duration.
curl https://api.agentcall.co/v1/calls/prompt-templatesAvailable templates
receptionistReceptionist (Front Desk)General-purpose front-desk answering. Start here if you're not sure which template fits.
lead-qualifierLead Qualifier (Sales)Inbound B2B sales calls. Gathers BANT data and routes serious prospects to a human.
appointment-bookerAppointment BookerCaptures name, contact, and preferred slot, then promises a confirmation email. The AI doesn't touch a calendar; a human or backend finalizes.
customer-supportCustomer Support (FAQ Deflection)Answers a defined FAQ. Escalates anything outside the FAQ to a human. Reduces support load without breaking trust.
call-screenerCall Screener (Anti-Spam)Filters unsolicited calls before they reach you. Useful for solo founders and anyone whose number gets scraped.
Fetch a template and configure inbound AI
Full Node.js flow: pull the template, fill in your placeholders, and attach it to a supported number:
import AgentCall from 'agentcall'
const client = new AgentCall(process.env.AGENTCALL_API_KEY)
// 1. Fetch templates (public, no auth)
const res = await fetch('https://api.agentcall.co/v1/calls/prompt-templates')
const { templates } = await res.json()
// 2. Pick the receptionist template
const template = templates.find((t) => t.id === 'receptionist')
// 3. Fill in your business details
const systemPrompt = template.systemPrompt
.replaceAll('[BUSINESS_NAME]', 'Acme Plumbing')
.replaceAll('[BUSINESS_TYPE]', 'a residential plumbing company')
.replace(
'[2-3 sentences describing your products or services and target customers]',
'We do emergency plumbing, drain cleaning, and water-heater installs in the Austin TX area. Most jobs are scheduled within 24 hours.'
)
const firstMessage = template.firstMessage.replaceAll(
'[BUSINESS_NAME]',
'Acme Plumbing'
)
// 4. Attach to your number. Incoming calls now answered by AI
await client.numbers.configureInboundAi('num_abc123', {
systemPrompt,
firstMessage,
voice: template.recommendedVoice,
maxDurationSecs: template.maxDurationSecs,
})Common Mistakes
Open-ended instructions are an invitation to hallucinate. The AI will fill the silence with plausible-sounding details that don't match your business. Ground every claim in a fact stated in the prompt.
“Be brief” combined with “explain everything in detail” produces unpredictable results. Pick one tone and stick with it. If you need both, decide which wins by situation and say so.
If you don't tell the AI what to say when it's stumped, it makes something up. Always include an explicit instruction like: “If asked something you don't know, say ‘Let me have someone follow up. Can I get your name and number?’”
Without a clear instruction to end the call when the caller is done, the AI keeps talking past goodbye. Include: “If the caller says goodbye, thank them and end the call promptly.”
The hard cap is 10,000 characters per prompt. More importantly, a prompt that tries to handle every possible call (sales + support + booking + billing) handles all of them poorly. Pick a focused use case per number. You can give different numbers different prompts.
In-Call Voice Tool Calling for Live Data
System prompts are great for stable facts (services, hours, pricing tiers). They're the wrong tool for data that changes per call: current inventory, account balances, today's availability, order status. For those, the voice AI calls a function during the conversation. That's the Action Bridge, and it now runs live on voice calls, not just SMS.
The setup is the same one that powers two-way AI SMS. On the number's inbound config, declare your tools (function definitions) and an actionWebhook. When the voice AI needs a real answer mid-call, it calls one of your tools. AgentCall HMAC-signs and POSTs the call to your webhook, your own calendar, CRM, or pricing system returns the real value, and the AI speaks it back. So a receptionist checks today's real availability and books on the spot instead of promising a callback, and an on-air host pulls a caller's history while they're still on the line.
The request body is identical to the SMS Action Bridge, with two voice-specific context fields: context.channel is "voice", context.callId holds the live call id, and context.threadId is null.
POST {actionWebhook.url}
X-AgentCall-Event: action.invoke
X-AgentCall-Signature: sha256=<hmac of the raw body>
{
"tool": "check_availability",
"arguments": { "date": "2026-06-14" },
"context": {
"channel": "voice",
"numberId": "num_...",
"agentId": "...",
"contact": { "phone": "+1314..." },
"threadId": null,
"callId": "call_..."
}
}The bridge is fail-soft on voice the same way it is on SMS: if your webhook times out or returns a non-2xx, the model is told the action is temporarily unavailable and keeps the conversation moving instead of inventing a confirmation. The per-call tool-call count shows up on the call record as toolCallCount and in the Call Report as report.toolCallCount. Voice tool calling runs alongside the pre-call context webhook: context is fetched before the call, tools are called during it.
If you have not wired a tool for a given fact yet, the safe fallback still applies: have the AI gather the question and promise a callback (“I'll have someone check on that and get back to you within an hour”) rather than putting a dynamic number in the prompt.
TCPA Disclosure When Recording Is Enabled
Two-party-consent states (CA, FL, IL, MD, MA, PA, WA, NV, NH, MT, CT, DE) require all parties to be informed when a phone call is being recorded. If you turn on the “Record calls” toggle on a number, your firstMessage needs to disclose the recording before the AI starts collecting any information from the caller.
AgentCall does this for you automatically. When recording is enabled and your firstMessage doesn't already mention recording, AgentCall prepends “This call may be recorded for quality.” to the spoken first message at call time. Your saved firstMessage in the dashboard stays exactly as you wrote it. Only the live spoken version gets the disclosure.
If you'd rather control the wording yourself, just include any of these words in your firstMessage and AgentCall will trust your version:
recordedrecordingrecord(any form)
Detection is case-insensitive, so “Hi, calls are RECORDED for training. How can I help?” counts. The auto-prepend never runs twice. It's idempotent.
/v1/numbers/:id/inbound-config and /v1/calls/ai), including SDK and MCP callers. It does not run for plain /v1/calls/initiate recorded calls. Those are programmatic dev calls where you control the audio script directly, so disclosure is your responsibility.Saving an Agent Per Number (Outbound)
Once a prompt is working, you don't want to paste it into the Place AI call dialog every time you dial. Each phone number can hold a saved outbound agent: the system prompt, voice, language, first message, max duration, and recording flag, all stored on the number. The next time you click Place AI call from that row, the dialog hydrates from the saved blob.
Mental model: each number is a phone identity for an AI employee. Configure one number as your appointment-booker and every call from it loads that persona. Want a second persona? Use a second number.
From the dashboard: open Place AI call from a number's row, fill in the dialog as you would for a one-off call, then click Save as saved agent for this number. A small badge appears under the call icon on the numbers page so you can scan the list and tell which numbers have which persona. Click Clear saved agent in the dialog to remove it.
Programmatically:
curl -X POST https://api.agentcall.co/v1/numbers/num_abc123/outbound-defaults \
-H "Authorization: Bearer ac_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"systemPrompt": "You are calling on behalf of Acme Dental to book an appointment...",
"voice": "marin",
"language": "en",
"firstMessage": "Hi, this is Acme Dental confirming your appointment time.",
"maxDurationSecs": 600,
"record": true,
"templateId": "appointment-booker"
}'Read or clear:
curl https://api.agentcall.co/v1/numbers/num_abc123/outbound-defaults \
-H "Authorization: Bearer ac_live_xxxxxxxxxxxx"
curl -X DELETE https://api.agentcall.co/v1/numbers/num_abc123/outbound-defaults \
-H "Authorization: Bearer ac_live_xxxxxxxxxxxx"The same surface is exposed via the SDK (setOutboundDefaults, getOutboundDefaults, clearOutboundDefaults) and the MCP server (set_outbound_defaults, get_outbound_defaults, clear_outbound_defaults). Pro plan only. Independent of the inbound AI receptionist on the same number; saving here never touches inbound config.
CSV-Driven Outbound Calling With a Saved Agent (Hermes Pattern)
Once a saved outbound agent is on a number, you can loop through a CSV and place one AI call per row with a single HTTP request each. Two flags make this safe and ergonomic:
useSavedAgent: truetells AgentCall to hydrate any field you omit (systemPrompt,voice,language,firstMessage,maxDurationSecs,record) from the saved outbound agent on the from-number. Anything you do pass wins, so you can personalizefirstMessageper recipient while inheriting the saved prompt, voice, and language. If the from-number has no saved agent, the call returns HTTP 400 with codeno_saved_agent.idempotencyKey(string, max 200 chars) makes the call safe to retry. If your script crashes mid-loop and reruns the same row, AgentCall replays the original 201 response and sets the headerX-AgentCall-Idempotency-Replayed: true. The recipient is not dialed twice and you are not charged twice. Dedup is scoped per phone number, so the sameidempotencyKeyused on a different from-number is treated as a separate call.
Canonical row call. Save the agent once on the from-number, then the per-row request is this small:
curl -X POST https://api.agentcall.co/v1/calls/ai \
-H "Authorization: Bearer ac_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"from": "num_abc123",
"to": "+15551234567",
"useSavedAgent": true,
"idempotencyKey": "batch-2026-05-25:row-42",
"firstMessage": "Hi Dr. Smith, this is Acme Dental confirming your appointment."
}'Loop over a CSV in Python. The batch_id:row_id idempotency-key shape means rerunning the script after a partial failure resumes safely. The time.sleep keeps the dial rate polite to recipients (one call every 3 seconds in this example).
import csv, time, requests
BATCH_ID = "batch-2026-05-25"
FROM_NUM = "num_abc123"
API_KEY = "ac_live_xxxxxxxxxxxx"
with open("recipients.csv") as f:
for row in csv.DictReader(f):
r = requests.post(
"https://api.agentcall.co/v1/calls/ai",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"from": FROM_NUM,
"to": row["phone"],
"useSavedAgent": True,
"idempotencyKey": f"{BATCH_ID}:{row['id']}",
"firstMessage": f"Hi {row['name']}, this is Acme Dental.",
},
timeout=15,
)
replayed = r.headers.get("X-AgentCall-Idempotency-Replayed") == "true"
print(row["id"], r.status_code, "replayed" if replayed else "new")
time.sleep(3)What this pattern does NOT do: schedule calls for later, track campaign-level completion, or retry failed carriers for you. It is a thin, safe primitive. If your script needs scheduled queueing, run it from a worker (cron, a queue, or your agent platform) and handle the loop there.
FAQ for Voice Prompts
10,000 characters max. The endpoint rejects anything longer. In practice, well-structured prompts for a single use case fit in 1,500–4,000 characters.
Yes. Call POST /v1/numbers/:id/inbound-config again with the new content. Changes take effect on the next incoming call. No redeploy, no restart.
No. We don't train or fine-tune any model on your data. Your prompt is the only source of business knowledge per call. Once the call ends, the AI has no memory of it for future calls.
Yes. Each number's inboundConfig is independent. Run a sales line with one prompt, a support line with another, and a screener on a third. Same account, different numbers, different AIs.
Yes. Pass language on configure_inbound_ai or initiate_ai_call. Supported codes: auto (default, matches the caller), en, es, fr, de, it, pt, nl, ja, ko, zh, hi, ar. A specific code makes the AI respond ONLY in that language, even if the caller speaks another. The firstMessage stays verbatim, so if you want the greeting in Spanish, write it in Spanish. The language setting governs everything the AI says after. Use update_number_language to change just the language on an existing config.
Related
Ready to put a voice on your number?
Provision a number, paste a template, and your AI receptionist is live in minutes.
Get API Key, Free