AI phone call API: place and answer real calls with an AI agent
Wixzel Voice is a voice AI API for building AI agents that place and answer real phone calls over your own SIP trunk, or talk to people in your web and mobile apps. One API key and one prepaid balance cover every voice engine, billed per second of actual usage.
Last updated
What is the Wixzel Voice API for AI phone calls?
With the Wixzel Voice API, an AI agent you define places and answers real phone calls over your own SIP trunk: POST /v1/calls with a number and an agent id, billed per second of actual usage.
In one line, Wixzel Voice is the voice AI API for building AI phone agents: an AI calling API where you define the agent and Wixzel Voice runs the call. Outbound, POST /v1/calls dials a number and connects your agent. Inbound, a phone number with an inbound_agent_id answers every call to it. The same agent can also talk to people in a web page or an app through the realtime API, with no phone line, or be driven by an AI assistant through the MCP server.
Wixzel Voice is one API key, one balance, every voice engine: the speech, language and voice provider accounts are the platform's, so there are no Deepgram, ElevenLabs or OpenRouter keys to bring. The phone line is the one thing you bring: bring your own SIP trunk, from Twilio, Telnyx, Plivo or any SIP provider.
How do you place an AI phone call with Wixzel Voice?
Four requests, after you have an API key and some credit. Each response carries the id the next request needs; the snippets read them from shell variables.
- 01
Connect your SIP trunk
Your carrier's host and credentials. The password is encrypted at rest and never returned again. The response carries
platform_ip(95.216.218.102today), which your carrier must allowlist, andorigination_uri(sip:95.216.218.102:5090today), where your carrier sends inbound calls.cURLcurl https://api.voice.wixzel.com/v1/sip-trunks \ -H "Authorization: Bearer $WIXZEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My carrier", "host": "sip.carrier.example", "username": "acct123", "password": "..." }' - 02
Register a phone number on it
A number you already own at that carrier, in E.164. Wixzel Voice does not sell or port numbers.
cURLcurl https://api.voice.wixzel.com/v1/phone-numbers \ -H "Authorization: Bearer $WIXZEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number": "+14155550100", "sip_trunk_id": "'"$TRUNK_ID"'" }' - 03
Create an agent
A short spoken-style prompt, the first words it says, and a voice: three models, or one realtime model.
opening_messageis required, because an agent without one answers in silence.outbound_phone_number_idmakes the number from step 2 its caller ID.cURLcurl https://api.voice.wixzel.com/v1/agents \ -H "Authorization: Bearer $WIXZEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Support", "system_prompt": "You are a concise support agent.", "opening_message": "Hi, how can I help?", "voice": { "stt": { "model": "deepgram/nova-3" }, "llm": { "model": "openrouter/gpt-4o-mini" }, "tts": { "model": "elevenlabs/eleven_turbo_v2_5" } }, "outbound_phone_number_id": "'"$NUMBER_ID"'" }' - 04
Place the call
A destination and an agent.
Idempotency-Keyis required, because a network timeout says nothing about whether the call was placed: retrying with the same key returns the original call instead of dialling twice. The response is201with the call,status: "queued".cURLcurl https://api.voice.wixzel.com/v1/calls \ -H "Authorization: Bearer $WIXZEL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "to": "+14155551234", "agent_id": "'"$AGENT_ID"'" }'
Before the first call connects, your carrier has to allowlist platform_ip. The carrier guides show where that setting lives on Twilio, Telnyx, Plivo, Vonage, Bandwidth, Exotel, Vobiz.
How do you place an AI phone call from TypeScript or Dart?
The official SDKs are wixzel-voice on npm, for TypeScript and JavaScript on Node, browsers and edge runtimes, and wixzel_voice on pub.dev, for Dart and Flutter. Both cover every endpoint, retry rate limits, and generate the Idempotency-Key on the paths that spend money. Other languages use the plain HTTP API and the OpenAPI document.
import { WixzelVoice } from 'wixzel-voice';
const client = new WixzelVoice({ apiKey: process.env.WIXZEL_API_KEY! });
// 1. Connect your SIP trunk.
const trunk = await client.sipTrunks.create({
name: 'My carrier',
host: 'sip.carrier.example',
username: 'acct123',
password: process.env.CARRIER_PASSWORD!,
});
console.log('Allowlist', trunk.platform_ip, 'and send inbound calls to', trunk.origination_uri);
// 2. Register a number you own on it.
const number = await client.phoneNumbers.create({
phone_number: '+14155550100',
sip_trunk_id: trunk.id,
});
// 3. Create the agent, with that number as its caller ID.
const agent = await client.agents.create({
name: 'Support',
system_prompt: 'You are a concise support agent.',
opening_message: 'Hi, how can I help?',
voice: {
stt: { model: 'deepgram/nova-3' },
llm: { model: 'openrouter/gpt-4o-mini' },
tts: { model: 'elevenlabs/eleven_turbo_v2_5' },
},
outbound_phone_number_id: number.id,
});
// 4. Place the call. The SDK sends the Idempotency-Key for you.
const call = await client.calls.create({ to: '+14155551234', agent_id: agent.id });
// Inbound: calls to the number are answered by the same agent.
await client.phoneNumbers.update(number.id, { inbound_agent_id: agent.id });
// Later: what happened, what it cost, what was said.
const detail = await client.calls.retrieve(call.id);
console.log(detail.status, detail.cost_micros, detail.failure_code, detail.failure_reason);
for (const line of detail.transcript) console.log(line.role, line.content);import 'dart:io';
import 'package:wixzel_voice/wixzel_voice.dart';
Future<void> main() async {
final client = WixzelVoice(apiKey: Platform.environment['WIXZEL_API_KEY']!);
// 1. Connect your SIP trunk.
final trunk = await client.sipTrunks.create(CreateSipTrunk(
name: 'My carrier',
host: 'sip.carrier.example',
username: 'acct123',
password: Platform.environment['CARRIER_PASSWORD']!,
));
print('Allowlist ${trunk.platformIp}; send inbound calls to ${trunk.originationUri}');
// 2. Register a number you own on it.
final number = await client.phoneNumbers.create(CreatePhoneNumber(
phoneNumber: '+14155550100',
sipTrunkId: trunk.id,
));
// 3. Create the agent, with that number as its caller ID.
final agent = await client.agents.create(CreateAgent(
name: 'Support',
systemPrompt: 'You are a concise support agent.',
openingMessage: 'Hi, how can I help?',
voice: VoiceConfig.composed(
stt: const SttConfig(model: 'deepgram/nova-3'),
llm: const LlmConfig(model: 'openrouter/gpt-4o-mini'),
tts: const TtsConfig(model: 'elevenlabs/eleven_turbo_v2_5'),
),
outboundPhoneNumberId: number.id,
));
// 4. Place the call. The SDK sends the Idempotency-Key for you.
final call = await client.calls.create(CreateCall(
to: '+14155551234',
agentId: agent.id,
));
// Inbound: calls to the number are answered by the same agent.
await client.phoneNumbers.update(
number.id, UpdatePhoneNumber(inboundAgentId: agent.id));
// Later: what happened, what it cost, what was said.
final detail = await client.calls.retrieve(call.id);
print('${detail.status} ${detail.costMicros} ${detail.failureCode}');
for (final line in detail.transcript) {
print('${line.role}: ${line.content}');
}
client.close();
}How does an AI agent answer inbound calls on Wixzel Voice?
Give the phone number an inbound_agent_id, and every call to it is answered by that agent. Without one, inbound calls to the number are rejected.
curl -X PATCH https://api.voice.wixzel.com/v1/phone-numbers/$NUMBER_ID \
-H "Authorization: Bearer $WIXZEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "inbound_agent_id": "'"$AGENT_ID"'" }'The other half is at your carrier: inbound calls have to be delivered to the trunk's origination_uri (sip:95.216.218.102:5090 today). It is separate configuration in a different part of the carrier's console from the allowlist, and if it is missing the number simply never rings: the carrier has nowhere to send the call, so nothing appears in any log. Every inbound call is logged with direction: "inbound" and both numbers, and fires an inboundCall webhook.
What does a Wixzel Voice call record return?
POST /v1/calls answers at once with the call, status: "queued". GET /v1/calls/{id} returns the full record as the call runs and after it ends, and a callCompleted webhook, signed with X-Wixzel-Signature, tells your server when it is over so you do not have to poll.
| Field | What it holds |
|---|---|
| status | queued, ringing, in-progress, completed, failed, busy, no-answer or canceled. |
| direction / channel | outbound or inbound; phone for a call over your trunk, web for a realtime session from a browser or app. |
| duration_seconds | How long the call lasted, once it has ended. |
| engine | The voice engine the agent ran on. |
| cost_micros | What the call cost, in micro-USD (1,000,000 = $1.00), written when the call settles. null on a call that never connected, which costs nothing. |
| transcript | Every turn, with role (user, assistant or system), content and a UTC timestamp. Also at GET /v1/calls/{id}/transcript. |
| summary / recording_url | The recording, where the call was recorded. summary is empty for calls placed through the API; read the transcript. |
| failure_code / failure_reason | On a call that did not connect: the Q.850 cause from the carrier, the stable thing to branch on, and what to do about it in plain language. |
| transfers | One entry per attempt to hand the caller to a person, with its status, ring time and SIP cause. |
| metadata | The flat string map you attached to POST /v1/calls, returned verbatim. Never sent to the model. |
Every micro-dollar on cost_micros is explained by a usage row in GET /v1/usage/events: seconds of speech recognised, tokens in and out of the model, characters synthesised, and the orchestration fee per minute.
What happens when an AI phone call does not connect?
A call the carrier refused has status: "failed" (or busy, or no-answer), a failure_code and a failure_reason, and cost_micros: null: it cost nothing, and the credit reserved for it was released. failure_code is the Q.850 cause from the carrier, the stable thing to branch on; failure_reason says what to do about it in plain language.
Some requests are refused before anything is dialled, and cost nothing: a balance that cannot fund the first 15 seconds is 402 insufficient_credits, a raw request without an Idempotency-Key is 400 missing_idempotency_key, and a call over the concurrent-call limit is refused with 503. Every failure code and every refusal, with what to do about each, is on the integrations page.
What does an AI phone call cost on Wixzel Voice?
Engine cost for one call, at typical speech rates, computed from the price book the meter bills against. Each price includes the $0.012 per minute orchestration fee. Calls are billed per second of actual usage, so a 90-second call costs about half of a three-minute one. Telephony is your own trunk at your carrier's rate and is not included.
| Engine | Pipeline | 1-minute call | 3-minute call | 5-minute call |
|---|---|---|---|---|
| classic | Deepgram + GPT-4o-mini + ElevenLabs | $0.0867 | $0.2601 | $0.4335 |
| sarvam | Sarvam, Indian languages end to end | $0.0623 | $0.1869 | $0.3115 |
| gemini-live | Gemini Live, native audio | $0.0466 | $0.1398 | $0.2330 |
| deepgram-agent | Deepgram Voice Agent | $0.1851 | $0.5553 | $0.9255 |
Prepaid: add credit from $5, no subscription and no free tier. The cheapest engine today is gemini-live at $0.0466 per minute. Estimate your own mix.
What are the limits of the Wixzel Voice API for AI phone calls?
- Wixzel Voice is in alpha, deployed and taking real calls. Behavioural changes ship behind a dated
Wixzel-Versionheader, and an existing key keeps the behaviour it was created with. - Five concurrent calls per account by default, raised on request. Web and app sessions count toward the same limit.
- Human transfer is blind only: the caller is handed straight to a number you listed. Attended transfer is not built, and transfer is unavailable on test calls and in web or app sessions.
- Wixzel Voice does not sell or port phone numbers, and phone calls need your own SIP trunk.
- Web and app sessions carry G.711 µ-law audio at 8 kHz, which is phone quality, not wideband.
- 10 API requests per second per key, with a burst of 20. A rate-limited request is never charged.
- The API and stored data are in Helsinki, Finland. Speech and language providers in the United States and India process audio and text for the duration of a call; the sarvam engine processes speech in India.
- No free tier and no trial credit. The minimum top-up is $5.
Frequently asked questions
- What is an AI phone call API?
- An API that connects an AI agent to a real phone call: your code names a number and an agent, and the API dials, runs the conversation with speech recognition, a language model and speech synthesis, and returns the call's status, its transcript and its cost. Wixzel Voice is one:
POST /v1/callsplaces a call over your own SIP trunk, and a phone number with aninbound_agent_idanswers calls the same way. - Is Wixzel Voice an AI calling API or a finished calling agent?
- An API. You build the agent: its prompt, its opening line, its voice engine and what your system does with the result. Wixzel Voice sells the APIs, the SDKs and the MCP server, and never a finished agent.
- How much does an AI phone call cost?
- The voice engine costs $0.0867 per connected minute on classic, $0.0623 per connected minute on sarvam, $0.0466 per connected minute on gemini-live, $0.1851 per connected minute on deepgram-agent, each including the $0.012 orchestration fee, billed per second of actual usage. A three-minute call on classic is about $0.2601. Your carrier bills the phone line separately, at your rates, with no markup.
- Can the AI agent answer inbound calls as well as place outbound ones?
- Yes. Set
inbound_agent_idon a phone number withPATCH /v1/phone-numbers/{id}and calls to that number are answered by that agent. Your carrier has to send inbound calls to theorigination_urithe trunk returns. Without aninbound_agent_id, inbound calls to the number are rejected. - Can the AI agent transfer a caller to a person?
- Yes, as a blind transfer: when a caller asks for a person, the agent hands the live call to one of the destinations you listed on the agent in
human_transfer, dialled over your SIP trunk. Attended transfer, where the agent speaks to the person first, is not built, and transfer is unavailable on test calls and in web or app sessions. - How many AI calls can run at once?
- Five concurrent calls per account by default, raised on request. A call over the limit is refused before anything is dialled and costs nothing. Web and app sessions count toward the same limit.
- Is there a free trial?
- No. The API is prepaid, with a $5 minimum top-up and no subscription. A call that cannot fund its first 15 seconds is refused with
402 insufficient_creditsbefore anything is allocated.
Where to go next
How the trunk connects, and a setup guide for every common carrier.
Every engine's price per minute, and a builder for your own composition.
Appointment booking, lead qualification, support lines and more, with what a month costs.
How carrier passwords, API keys and webhooks are protected, and where data lives.
An honest comparison, with where each is the better choice.
Operator, prices, limits, refunds and data location, on one page.
