Phone, SMS & voice
Have an AI voice agent answer your number or take outbound calls, either a hosted agent over SIP or your own speech pipeline over a WebSocket.
An AI voice agent is a forward destination like any other. Point a number at it and the agent answers every inbound call. Pass it as forwardTo on one outbound call and the agent talks to whoever picks up. Pozy owns the phone side: the number, the carrier leg, routing, recording and the call history. The agent owns the conversation.
#Pick a shape
| Hosted agent over SIP | Your own agent over WebSocket | |
|---|---|---|
| Destination | sip:<user>@<host> |
wss://your-server.example.com/media |
| Where the prompt, voice and tools live | In the agent platform you use | In your own code: speech-to-text, an LLM, text-to-speech |
| What you build | Nothing on the phone side | A WebSocket server that reads and writes call audio |
| Agent can place its own calls | Use a SIP trunk | Start calls with POST /v1/voice/calls |
Either way there is no second phone leg, so you pay only for the call itself.
#Point a number at the agent
curl -X POST https://api.pozy.ai/v1/phone-numbers/6650d2e1b3c4d5e6f7081923/voice \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "forwardTo": "wss://agent.acme.example.com/media", "transcriptionEnabled": true }'const res = await fetch("https://api.pozy.ai/v1/phone-numbers/6650d2e1b3c4d5e6f7081923/voice", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ forwardTo: "wss://agent.acme.example.com/media", transcriptionEnabled: true }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.pstnForwardTo);import os, requests
resp = requests.post(
"https://api.pozy.ai/v1/phone-numbers/6650d2e1b3c4d5e6f7081923/voice",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={"forwardTo": "wss://agent.acme.example.com/media", "transcriptionEnabled": True},
timeout=30,
)
print(resp.json()["pstnForwardTo"])A hosted agent works the same way with a sip: address in forwardTo. Many hosted platforms identify the number from the SIP user part: keep the number in E.164 with the leading +, written the same way on both sides. A + missing on one side is the usual reason a call connects and drops at once.
The agent sits inside the normal inbound flow, so the blocklist, business hours, an IVR option that sends one digit to the agent, and voicemail all still apply. See Voice calls.
#Hand one outbound call to an agent
Pass the agent as the per-call destination. The number's inbound setting stays as it is.
curl -X POST https://api.pozy.ai/v1/voice/calls \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Idempotency-Key: reminder-appt-7781" \
-H "Content-Type: application/json" \
-d '{
"fromNumber": "+14155550142",
"to": "+13125550188",
"forwardTo": "wss://agent.acme.example.com/media",
"amd": true,
"voicemailDropMessage": "Hi, this is Acme confirming your appointment tomorrow at 10. Call us back if you need to change it."
}'With amd: true, the agent is only bridged in when a person answers. An answering machine gets the voicemailDropMessage instead.
#Build your own agent over WebSocket
When the destination is wss://, the call's audio streams to your server as soon as the call is answered, and audio you send back is played to the caller. Pozy does not re-encode the audio.
| Setting | Value |
|---|---|
| Audio you receive | The caller's side of the call |
| Codec, both directions | PCMU (G.711 u-law), 8 kHz |
| Framing | JSON text frames with an event field (start, media, stop, error). Audio travels base64-encoded in media.payload. |
| Speaking back | Write media frames with base64 PCMU on the same socket |
| Ending | Closing the socket ends the call |
These settings are fixed today, so build the pipeline around 8 kHz PCMU.
A typical loop:
- Accept the WebSocket and wait for
start. - Feed
mediapayloads to speech-to-text. - Send each finished user turn to your LLM with the system prompt and tools.
- Turn the reply into speech, encode it as PCMU at 8 kHz, and write it back as
mediaframes. - On
stop, or when the conversation is done, close the socket.
#Give the agent tools
Your agent's backend holds the Pozy API key, so Pozy endpoints make good agent tools. Match the call to its Pozy id with the call.received webhook, which fires as an inbound call starts and carries call.id, from, to and conversationId. For outbound calls, keep the callId from the create response.
| Tool | Endpoint |
|---|---|
| Hand the caller to a person | POST /v1/voice/calls/{id}/transfer with a tel: or sip: destination. Do this before you close the socket. |
| Hang up | POST /v1/voice/calls/{id}/end |
| Text a link or a summary | POST /v1/sms/messages from the number that was called |
| Reply in the inbox thread | The inbox send endpoints with the conversationId from call.received |
| Look up or save the caller | The contacts endpoints |
The same media bridge serves WhatsApp calls. An agent you build once can answer both channels.
#Capture the conversation
Turn on recordingEnabled and transcriptionEnabled on the number when you want Pozy to keep its own copy of the call. The recording and transcript attach to the call record after hangup. See Call recordings.
#When it does not connect
| Symptom | Likely cause | What to check |
|---|---|---|
| Caller hears voicemail right after answering | The WebSocket handshake failed | The wss:// URL, TLS certificate, and that your server accepts the connection. Read callErrors on the call. |
| Call connects then drops at once (SIP) | The number format differs between Pozy and the agent platform | Use the same E.164 with + on both sides |
409 invalid_resource_state on the voice settings |
The number is attached to a SIP trunk | Detach it, or configure the agent on the trunk side |
If the stream cannot be set up, an already-answered caller falls to voicemail when it is on. Otherwise the call ends.
#Endpoints
| Endpoint | What it does |
|---|---|
POST /v1/phone-numbers/{id}/voice |
Enable phone calling on a number |
POST /v1/voice/calls |
Place an outbound phone call |
POST /v1/voice/calls/{id}/transfer |
Blind-transfer a live call |
POST /v1/voice/calls/{id}/end |
Hang up a live call |
GET /v1/calls/{id} |
Get a call (any channel) |
#Webhook events
| Event | When it fires |
|---|---|
call.received |
Call received. |
call.ended |
Call ended. |
call.failed |
Call failed. |