Calls
Programmatic voice-call control: originate an outbound call to a contact (`POST /calls/originate`) and hang up an active call (`POST /calls/{call}/hangup`). This is a call-control API, not a browser softphone — it only hands the call to the AISAR telephony gateway; the audio itself travels over SIP/WebRTC outside HTTP. The whole group requires an active subscription and the `telephony` plan feature; on top of that the calling user must be a provisioned telephony operator (have an active SIP endpoint) and hold the `telephony.use` permission in the current company — otherwise `403 { "data": { "message": "Telephony is not available for your account." } }`. `POST /calls/originate` takes `contact_id` and `channel_id` (both required integers). The channel picks the dial path: a WhatsApp/WABA channel calls the contact over WhatsApp, while a `sip_telephony` channel dials the contact's phone number over a SIP trunk. Both the contact and the channel must belong to the current company (otherwise `404 { "data": { "message": "Contact or channel not found." } }`). On success (200) it returns the created call record: `{ "data": { "call": { id, direction: "outbound", status: "ringing", to_number, from_number, asterisk_channel_id, timeline_message_id } } }` — the call starts in `ringing` and a call card is appended to the conversation timeline (`timeline_message_id`). Subsequent status transitions (`ringing → answered → completed`, or `rejected` / `missed` / `failed`) arrive via real-time events and `call.*` webhooks, not in this response body. WhatsApp calls require a valid call permission from the contact: without it `originate` returns `422` with an `eligibility` block where `call.can_call = false`. Check readiness and request permission ahead of time via `GET /v1/contacts/{contact}/outbound-eligibility` and `POST /v1/contacts/{contact}/call-permission/request` (the Contacts group). SIP channels need no permission (`eligibility.call.permission_status = "not_required"`), only a phone number on the contact — otherwise `422 { "data": { "message": "Contact has no phone number to dial." } }`. Other `originate` outcomes: `403` with an `eligibility` block — the channel or contact cannot be called (no WhatsApp account, operator not enabled); `502` — the telephony gateway failed to originate the call; `422` — request-body validation error (missing `contact_id` / `channel_id`). `POST /calls/{call}/hangup` ends an active call. Beyond telephony availability, only a party to the call (the assigned operator or a call leg) or a user with company telephony-management rights may hang it up — otherwise `403`. Success: `{ "message": "Hangup requested." }` (the teardown request is handed to the gateway; actual termination is confirmed by a `call.*` event). Note: the `hangup` response, unlike `originate`, is not wrapped in `data`.
Endpoints
| Method | Path | Summary |
|---|---|---|
| POST | /v1/calls/originateOriginate an outbound call to a contact over a WhatsApp or SIP channel. | Originate an outbound call to a contact over a WhatsApp or SIP channel. |
| POST | /v1/calls/{call}/hangupHang up an active call (allowed for a call party or a telephony manager). | Hang up an active call (allowed for a call party or a telephony manager). |
Examples
Originate a WhatsApp call
Request
curl -X POST https://api.aisar.app/v1/calls/originate \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 42,
"channel_id": 7
}'Response
{
"data": {
"call": {
"id": 5001,
"direction": "outbound",
"status": "ringing",
"to_number": "7700000XXXX",
"from_number": "7708550XXXX",
"asterisk_channel_id": "1720787000.4521",
"timeline_message_id": 88214
}
}
}Originate a call over a SIP trunk
Request
curl -X POST https://api.aisar.app/v1/calls/originate \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 42,
"channel_id": 12
}'Response
{
"data": {
"call": {
"id": 5002,
"direction": "outbound",
"status": "ringing",
"to_number": "7700000XXXX",
"from_number": "7727000XXXX",
"asterisk_channel_id": "1720787100.4530",
"timeline_message_id": 88221
}
}
}WhatsApp call without the contact's permission (422)
Request
curl -X POST https://api.aisar.app/v1/calls/originate \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 42,
"channel_id": 7
}'Response
{
"data": {
"message": "No valid call permission or daily attempts exhausted.",
"eligibility": {
"message": {
"window_open": true,
"window_expires_at": 1720800000,
"requires_template": false
},
"call": {
"channel_kind": "whatsapp",
"can_call": false,
"permission_status": "none",
"permission_expires_at": null,
"can_request_permission": true,
"request_cooldown_until": null,
"attempts_remaining": 5,
"has_whatsapp_account": true
}
}
}
}Hang up an active call
Request
curl -X POST https://api.aisar.app/v1/calls/5001/hangup \
-H "Authorization: Bearer YOUR_API_TOKEN"Response
{
"message": "Hangup requested."
}Telephony not available for the account (403)
Request
curl -X POST https://api.aisar.app/v1/calls/originate \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 42,
"channel_id": 7
}'Response
{
"data": {
"message": "Telephony is not available for your account."
}
}