> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flowapt.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Calling

> Run WhatsApp voice calls on your FlowIQ number from your own server: answer, decline, end and start calls, ask for call permission, read call history and transcripts, and change the number's calling settings.

FlowIQ answers WhatsApp calls in the inbox, or with its own voice AI. The Calling API lets you do it yourself instead: your server hears about each call the moment it rings, answers it with its own audio (your voice agent, a call centre, an IVR), and FlowIQ keeps the call in the customer's chat history.

> [!NOTE]
>
> WhatsApp calling must be switched on for your number first: [Update Calling Settings](https://docs.flowapt.com/flowiq-api-reference/endpoint/update-calling-settings.md) with `"status": "ENABLED"`, or Settings, Integrations, WhatsApp in FlowIQ. WhatsApp requires a verified business and a messaging limit of 2,000 or more.

## Endpoints

| Method | Path | What it does |
| - | - | - |
| `GET` | [`/v1/calls`](https://docs.flowapt.com/flowiq-api-reference/endpoint/list-calls) | Call history, newest first |
| `GET` | [`/v1/calls/{call_id}`](https://docs.flowapt.com/flowiq-api-reference/endpoint/get-call) | One call with every event, its transcript and recording |
| `POST` | [`/v1/calls/pre-accept`](https://docs.flowapt.com/flowiq-api-reference/endpoint/pre-accept-call) | Connect the audio before you answer (optional) |
| `POST` | [`/v1/calls/accept`](https://docs.flowapt.com/flowiq-api-reference/endpoint/accept-call) | Answer a ringing call |
| `POST` | [`/v1/calls/reject`](https://docs.flowapt.com/flowiq-api-reference/endpoint/reject-call) | Decline a ringing call |
| `POST` | [`/v1/calls/terminate`](https://docs.flowapt.com/flowiq-api-reference/endpoint/end-call) | Hang up |
| `POST` | [`/v1/calls`](https://docs.flowapt.com/flowiq-api-reference/endpoint/start-call) | Call a customer |
| `GET` | [`/v1/calls/permissions/{whatsapp_id}`](https://docs.flowapt.com/flowiq-api-reference/endpoint/get-call-permission) | May you call this person now? |
| `POST` | [`/v1/calls/permission-requests`](https://docs.flowapt.com/flowiq-api-reference/endpoint/request-call-permission) | Ask a customer for permission to call them |
| `GET` | [`/v1/calling/settings`](https://docs.flowapt.com/flowiq-api-reference/endpoint/get-calling-settings) | The number's calling settings and who answers calls |
| `PATCH` | [`/v1/calling/settings`](https://docs.flowapt.com/flowiq-api-reference/endpoint/update-calling-settings) | Change them |

Events arrive on your webhook as the [`call` event](https://docs.flowapt.com/flowiq-api-reference/webhooks#call).

## Authentication and limits

Use your organisation API key (Settings, API Keys) as a Bearer token. The key decides which number you act on; you never pass an organisation id.

| Scope on the key | Needed for |
| - | - |
| `read` | Listing and reading calls, permissions and settings |
| `send` | Answering, declining, ending and starting calls, permission requests |
| `write` | Changing settings, reading SIP credentials |

Limits per key: 240 reads and 60 actions a minute. Every response carries `organization_id` and an `X-Request-Id`; errors carry a stable `code`; `POST` and `PATCH` accept an `Idempotency-Key`. See [Conventions](https://docs.flowapt.com/flowiq-api-reference/conventions.md).

## Three ways to answer

| `answered_by` | Who picks up | You need |
| - | - | - |
| `flowiq` (default) | A person in the FlowIQ inbox, or FlowIQ's voice AI when Auto-answer is on | Nothing |
| `api` | Your server, by answering each call with an SDP answer | A WebRTC media server |
| `stream` | FlowIQ answers the call and streams the audio to your WebSocket | A WebSocket server (no WebRTC) |

Set it for the number with [Update Calling Settings](https://docs.flowapt.com/flowiq-api-reference/endpoint/update-calling-settings.md). With `api` you can also answer any single call by stream: [Accept Call](https://docs.flowapt.com/flowiq-api-reference/endpoint/accept-call.md) with `stream` instead of `sdp`.

## Answer incoming calls with your own server

1. **Register a webhook** for the `call` event (or `all`): [Create Webhook](https://docs.flowapt.com/flowiq-api-reference/endpoint/create-webhook.md).
2. **Take the calls away from FlowIQ:** `PATCH /v1/calling/settings` with `{"answered_by": "api"}`. The FlowIQ inbox stops ringing and FlowIQ's voice AI stops picking up. Calls still show in the customer's chat. Switch back any time with `"flowiq"`.
3. **When a customer calls,** your webhook receives `kind: "call"`, `call.event: "connect"` and WhatsApp's SDP offer in `call.session.sdp`.
4. **Answer:** create an SDP answer for that offer with your WebRTC stack and send it with [Accept Call](https://docs.flowapt.com/flowiq-api-reference/endpoint/accept-call.md). [Pre-accept](https://docs.flowapt.com/flowiq-api-reference/endpoint/pre-accept-call.md) first if you want the audio to flow the moment you accept.
5. **The call runs between WhatsApp and your media server.** Audio never passes through FlowIQ. End it with [End Call](https://docs.flowapt.com/flowiq-api-reference/endpoint/end-call.md); when either side hangs up you receive `call.event: "terminate"` with the duration.

```javascript theme={null}
// Express + any WebRTC stack (wrtc, mediasoup, LiveKit, Pion via a sidecar...)
app.post("/flowiq/webhook", async (req, res) => {
  res.sendStatus(200); // answer the webhook fast, then work
  const e = req.body;
  if (e.event_type !== "call" || e.kind !== "call") return;
  if (e.call.event === "connect" && e.call.direction === "inbound") {
    const sdpAnswer = await myMediaServer.answer(e.call.session.sdp); // your code
    await fetch("https://api.flowiq.live/v1/calls/accept", {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.FLOWIQ_API_KEY}`, "Content-Type": "application/json" },
      body: JSON.stringify({ call_id: e.call.id, sdp: sdpAnswer }),
    });
  }
  if (e.call.event === "terminate") myMediaServer.hangUp(e.call.id);
});
```

> [!WARNING]
>
> With `answered_by` set to `api`, nobody answers if your server does not. Callers hear it ring out.

## Stream the audio to your own voice agent

The simplest way to put an AI voice agent on WhatsApp: no WebRTC, no SDP. FlowIQ answers the call and opens a WebSocket to you with the caller's audio; you send audio back.

1. **Point the number at your server:** `PATCH /v1/calling/settings` with `{"answered_by": "stream", "stream": {"url": "wss://agent.example.com/flowiq", "format": "pcm16_16k", "headers": {"Authorization": "Bearer …"}}}`. Formats: `pcm16_16k` (default), `pcm16_8k`, `pcm16_24k`, `mulaw_8k`.
2. **When a customer calls,** FlowIQ connects to your URL (with your headers, plus `X-Flowiq-Call-Id` and `X-Flowiq-Organization-Id`), answers the call, and streams.
3. **The socket speaks JSON,** shaped like Twilio Media Streams, so a voice agent written for Twilio works with little change (with `mulaw_8k`, usually unchanged):

| FlowIQ → you | |
| - | - |
| `{"event": "connected", "protocol": "flowiq-media-stream", "version": "1"}` | Socket open |
| `{"event": "start", "start": {"call_id", "stream_sid", "organization_id", "contact_id", "from", "to", "media_format": {"encoding", "sample_rate", "channels": 1}, "custom_parameters"}}` | The call is live (Twilio-style aliases `streamSid`, `callSid`, `mediaFormat`, `customParameters` are included) |
| `{"event": "media", "media": {"track": "inbound", "chunk", "timestamp", "payload": "<base64>"}}` | 20 ms of the caller's audio |
| `{"event": "mark", "mark": {"name"}}` | A mark you set has been played to the caller |
| `{"event": "stop", "stop": {"reason"}}` | The call ended |

| You → FlowIQ | |
| - | - |
| `{"event": "media", "media": {"payload": "<base64>"}}` | Audio to play to the caller, in the same format. Send as fast as you like: FlowIQ paces it to real time |
| `{"event": "clear"}` | Drop everything queued (the caller interrupted) |
| `{"event": "mark", "mark": {"name": "…"}}` | Ask for a `mark` back when the audio sent so far has played |
| `{"event": "hangup"}` | End the call (closing the socket ends it too) |

Calls streamed this way show `answered_by: "stream"` in the call history. The call ends when the caller hangs up (you receive `stop`), when you send `hangup` or close the socket, or after 60 minutes.

> [!NOTE]
>
> Audio over the stream is uncompressed PCM (or µ-law): about 32 KB/s per direction at 16 kHz. One socket per call.

## Call a customer

WhatsApp only lets you call people who have given you permission.

1. **Check:** [Get Call Permission](https://docs.flowapt.com/flowiq-api-reference/endpoint/get-call-permission.md) tells you whether you may call now (`can_start_call`) and how many requests and calls you have left.
2. **Ask:** within 24 hours of the customer writing to you, [Request Call Permission](https://docs.flowapt.com/flowiq-api-reference/endpoint/request-call-permission.md) sends them an Allow calls button. Outside that window, send an approved template that has a call permission request with [Send Template](https://docs.flowapt.com/flowiq-api-reference/endpoint/send-template.md). Their answer arrives on your webhook as `kind: "call_permission"`.
3. **Call:** [Start Call](https://docs.flowapt.com/flowiq-api-reference/endpoint/start-call.md) with your SDP offer. WhatsApp rings the customer; when they answer, your webhook gets `call.event: "connect"` with their SDP answer in `call.session`, and `kind: "call_status"` events track ringing, accepted and rejected.

## Or use SIP

Voice platforms that speak SIP (most voice-agent and contact-centre products do) can take the calls without any WebRTC code: set `calling.sip` with [Update Calling Settings](https://docs.flowapt.com/flowiq-api-reference/endpoint/update-calling-settings.md) and WhatsApp sends every call straight to your SIP server.

> [!WARNING]
>
> With SIP on, WhatsApp sends calls only to your SIP server. FlowIQ then receives no call events at all: no `call` webhooks, nothing in the FlowIQ inbox, no FlowIQ voice AI, and the call endpoints above stop working for that number. Turn SIP off to go back.

## The call object

```json theme={null}
{
  "id": "wacid.HBgLMjc4MjAwMDAwMDAVAgARGBI2RjQ3QkU1RjA5Q0UyMkEzQjQA",
  "channel": "whatsapp",
  "direction": "inbound",
  "status": "completed",
  "answered_by": "api",
  "started_at": "2026-10-26T20:48:04.097Z",
  "answered_at": "2026-10-26T20:48:41.000Z",
  "ended_at": "2026-10-26T20:49:51.000Z",
  "duration_seconds": 70,
  "end_reason": "completed",
  "transcript": null,
  "has_recording": false,
  "recording_url": null,
  "contact": { "id": "9b8a7c6d-…", "whatsapp_id": "27820000000", "name": "Jane Doe" }
}
```

| Field | Meaning |
| - | - |
| `status` | `ringing`, `in_progress`, `completed` (answered, then ended), `missed` (ended unanswered), `rejected` (declined) or `failed` |
| `answered_by` | `api` (your server, with an SDP answer), `stream` (your server, through the media stream), `flowiq_ai` (FlowIQ's voice AI) or `inbox` (a person in FlowIQ); `null` until someone answers |
| `channel` | `whatsapp`, or `phone` for calls to a FlowIQ phone line (listed here, but not controlled by this API) |
| `transcript` | When FlowIQ's voice AI answered, or a recording was transcribed |
| `recording_url` | A signed link valid for 1 hour, when the call was recorded in FlowIQ |

Call ids contain characters such as `=` and `/`: URL-encode them when they go in a path (`encodeURIComponent`).

Calls that never got an end event from WhatsApp (a lost webhook) are closed by FlowIQ: `missed` after 3 minutes ringing, `completed` after 35 minutes in progress, with `end_reason: "no_end_event"`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.