Outbound (Reach Out)
Have an agent reach out to someone — place a call, or open a conversation on SMS, WhatsApp or email.
| Operation | Endpoint | Scope |
|---|---|---|
| Start an outbound conversation | POST /api/v1/outbound | conversations:write |
bash
curl -X POST "https://<your-workspace>-api.perfox.ai/api/v1/outbound" \
-H "Authorization: Bearer sk_…" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "01a0…",
"channel": "phone",
"to": "+919876543210"
}'| Field | Values |
|---|---|
agent_id | A published agent that has a Trigger for this channel |
channel | phone, sms, whatsapp or email |
to | Phone number or email address to reach |
opening_message | Optional, up to 2,000 characters — on SMS, WhatsApp and email it is sent exactly as written instead of the agent's greeting; on a call it is given to the agent as its opening instruction for this call |
template | WhatsApp only, optional — an approved template to send instead of opening_message (see below) |
customer_id | Optional — skip the lookup when you already know the customer |
On SMS, WhatsApp and email the first message is not generated by the AI: it is your opening_message, or — when you leave it out — the greeting set on the agent's persona. The agent takes over when the customer replies.
The agent must be built for the channel
An agent without a Trigger for the channel you asked for is rejected. The channel a conversation arrives on shapes how the rest of the flow behaves, so starting one the agent was never built for would fail further downstream and far less clearly.
Opening with a WhatsApp template
Outside WhatsApp's 24-hour service window only an approved template is deliverable, so a campaign to people who have not written in today needs one:
json
{
"agent_id": "01a0…",
"channel": "whatsapp",
"to": "+919876543210",
"template": {
"name": "order_update",
"language": "en_US",
"body_parameters": ["Priya", "#4182"]
}
}| Template field | Values |
|---|---|
name | The approved template's name |
language | Its language code; defaults to en_US |
body_parameters | Values for the body's placeholders, in order |
header | For a header variable: { "type": "text", "text": "…" }, or type image / video / document with a public url |
buttons | Values for the template's buttons: { "sub_type": "quick_reply" | "url", "index": 0, "payload" | "text": "…" } — index is the button's position in the approved template |
Sending both template and opening_message, or a template on any channel other than WhatsApp, is refused with 400 rather than guessing which one you meant.
A phone call always opens a new conversation
A call is a session, not a thread — it has its own recording and its own beginning and end. History still follows the person: the same number resolves to the same customer, and the agent still sees their earlier conversations.
Text channels behave the other way round and continue an open thread, which is what replying by SMS or email means.
Check send_authorized
A successful request returns 201:
json
{
"conversation_id": "01a0…",
"execution_id": "01a0…",
"status": "sent",
"channel": "whatsapp",
"send_authorized": true,
"message_id": "01a0…",
"provider_message_id": "…"
}| Field | Meaning |
|---|---|
conversation_id | The conversation that was started or continued |
status | For SMS, WhatsApp and email: sent (the provider accepted it), failed (the provider rejected it), or suppressed (nothing went out — no matching Sender action, or the recipient is an automated mailbox). For a call: the dialler's status |
message_id | Our id for the message sent on SMS, WhatsApp or email. Absent for calls and when nothing was sent |
provider_message_id | The carrier's id for the message or call; empty when it returned none |
The message goes out from the number or address on the agent's Trigger, so the customer's reply comes back to the same agent.
On email, sent only means "handed over"
SMS and WhatsApp messages move on to delivered, read or failed as the carrier reports back. An email is recorded as sent when the provider accepts it and stays sent — a later bounce is not reflected. Do not read sent on email as proof of delivery.
On SMS, WhatsApp and email, an agent can only send if the matching Sender action is on its canvas. When it is not, the conversation still starts and the agent still runs — but nothing goes out.
send_authorized is how you find that out. Treat false as "this agent is not finished yet" rather than as a failure of the request.
Calls are always authorized, because a call delivers over the voice stream itself rather than through a sender action.
Errors
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A field is missing or malformed |
| 400 | bad_request | The combination is not allowed — for example a template plus opening_message |
| 404 | not_found | No agent with that agent_id |
| 422 | agent_not_published | The agent is a draft — publish it first |
| 422 | no_trigger_for_channel | The agent has no Trigger for this channel |
Error bodies carry the agent_id you sent, so a refusal can be traced without re-reading your request.