Skip to content

Calls ​

MethodPathBody / queryResponse
POST/v1/tenants/{t}/callsOutbound intent — see below201 { call }
GET/v1/tenants/{t}/calls?status&live&direction&agent_ref&external_ref&customer&channel_ref&queue_ref&from&to&limit&page or &before_id, &stats{ calls, nextBeforeId }, plus page, pageSize, total with page, stats with stats=true (live=true: the ongoing calls — initiated, ringing or in progress)
GET/v1/calls/{callId}—{ call, participants, legs, events, transfers, ivrSessions, recording }
PATCH/v1/calls/{callId}{ external_ref?, consumer_metadata? }{ call }
POST/v1/calls/{callId}/terminate—202 { accepted: true }
GET/v1/calls/{callId}/recording—{ url, expiresInSeconds, format, channelMap }
DELETE/v1/calls/{callId}—204 — see Deleting data
POST/v1/tenants/{t}/customers/erase{ address }{ callsErased, recordingsDeleted, activeCallsSkipped }

call is the call view described in events.md (data.call).

  • List: status is one of INITIATED, RINGING, IN_PROGRESS, TERMINATED, FAILED; direction INBOUND or OUTBOUND; from (inclusive) and to (exclusive) are ISO 8601 timestamps compared with the call's creation time (400 if one doesn't parse); customer is the customer's address — a phone number with or without + or 00, or a SIP URI; channel_ref / queue_ref pick one line or queue (400 if the ref matches nothing); limit 1–200, default 50. Pages are newest first, two ways: by number — page (1, 2, …) returns that page of limit calls with page, pageSize and total (how many calls the filters match), for a list with numbered pages; or by cursor — pass nextBeforeId as before_id for the next page (nextBeforeId is null only when the page is empty), which stays steady while new calls arrive. Without either, the first page. stats=true adds stats: what every call the filters match adds up to — { total, answered, missed, lost, failed, live, inbound, outbound, talkSeconds, avgTalkSeconds, avgWaitSeconds, longestWaitSeconds }. answered counts the calls someone answered; of the ones nobody answered, missed are those your side didn't take (NO_ANSWER, IVR_AGENT_NO_ANSWER, TIMEOUT, REJECTED, BUSY), lost those whose caller hung up first (CANCELLED), failed those ended by an error (status FAILED, or a system, network or provider error, an agent dropped); these four never overlap. avgTalkSeconds is per answered call, avgWaitSeconds per call that waited in a queue (null when none).
  • Detail: participants — who was in the call and when, in join order: [{ kind: CUSTOMER|AGENT|SUPERVISOR|OPERATOR|DASHBOARD, agentId, agentRef, deviceId, joinedAt, leftAt, leaveReason }]. leftAt is null while they're in; leaveReason is ENDED (the call ended), TRANSFERRED (an agent handed it on) or MONITOR_STOPPED. OPERATOR is someone running the Calloop platform who listened to the call (only listened — never heard by anyone on it); agentId and agentRef are null for them, and who it was is in Calloop's own audit log, not here. DASHBOARD is one of your people listening from the dashboard, with listenerName. The customer joins on arrival (inbound) or when they answer (outbound); an agent when they accept or start the call. legs — at most one per type, the latest (a transfer replaces the agent leg; who was in the call over time is participants) — [{ type: AGENT|CUSTOMER|MONITOR, agentId, deviceId, state, connectedAt, disconnectedAt }]; events — the lifecycle log, [{ type, agentId, occurredAt, durationSeconds, metadata }]; recording — { id, status, durationSeconds, format, channelMap, completedAt, failureReason } or null (failureReason: why a failed recording won't arrive, the reason of recording.failed; otherwise null); transfers — [{ id, fromAgentId, toAgentId, toQueueId, initiatedByAgentId, initiatedByType, transferredAt, acceptedAt, acceptanceDurationSeconds }]; ivrSessions — [{ id, ivrFlowId, completed, outcome, durationSeconds, startedAt, endedAt, inputs: [{ nodeName, input, pressedAt }] }].
  • PATCH: external_ref ≤191 chars; consumer_metadata an object. An omitted field is left unchanged; null clears it.
  • Terminate: 409 call_ended if the call already ended. The result arrives as call.ended: COMPLETED if it was IN_PROGRESS, CANCELLED otherwise, with terminatedBy: CONSUMER (your backend ended it). An outbound intent the agent hasn't started yet ends straight away.
  • Recording: a short-lived download URL. 404 until the recording has completed (recording.completed says when; recording.failed that it won't); 503 storage_unavailable when the store holding it can't be reached. Where recordings are stored (Calloop's servers or the operator's S3 bucket) is the operator's; a URL always works wherever the file is.

Deleting data ​

For a customer's request to delete their data, or your own cleanup. Both touch ended calls only, delete the recording files from storage first, and remove the events Calloop sent about those calls (they carry the customer too). Calloop's own retention also removes old detail and recordings on a schedule — see the tenant settings above.

  • DELETE /v1/calls/{callId} — the call and everything about it: legs, lifecycle log, transfers, IVR sessions and key presses, the recording, and its events (GET /v1/events no longer lists them). 409 call_active while the call hasn't ended.
  • POST /v1/tenants/{t}/customers/erase { address } — every ended call of the tenant with this customer address (same forms as the customer filter) keeps its row for your history and reports, but without the customer: the address, name, external_ref, consumer_metadata and provider details are cleared, and its lifecycle log, IVR key presses, SDP, recording and events are deleted. activeCallsSkipped counts the customer's calls still in progress — erase again once they've ended. Safe to repeat.
  • Either answers 503 storage_unavailable when a recording file can't be deleted; nothing is changed then. A recording still on its way from the media server is deleted too (it is dropped when it arrives).

Outbound calls ​

Outbound starts on your backend, because consent to call a customer is yours to check.

POST /v1/tenants/biz-123/calls

json
{
  "channel_ref": "whatsapp-main",
  "agent_ref": "user-7",
  "customer": { "address": "+447700900123", "name": "Jane Doe" },
  "external_ref": "crm-call-42",
  "consumer_metadata": { "crm_contact_id": 991 }
}

201:

json
{ "call": { "callId": 88, "status": "INITIATED" } }

(abridged — the full call view.)

  • customer.address is required (≤191 chars); customer.address_type (E164, WHATSAPP_USER, SIP_URI) is inferred when omitted; customer.name ≤255 chars; external_ref ≤191 chars.
  • An unknown channel_ref is 400; an unknown agent_ref is 404.
  • 403 line_not_owned: the channel is another agent's personal line.
  • 409 codes: invalid_channel / invalid_agent (not this tenant's), channel_disabled (channel not ACTIVE), unsupported_channel (the channel type can't place outbound calls), agent_busy (the agent already has an active call), invalid_customer (the address isn't valid for the channel).

Give callId to that agent's client, which sends call:start { callId, sdpOffer } over its socket; Calloop then dials the customer. An intent not started within 2 minutes ends as CANCELLED (terminatedBy: SYSTEM). The agent is ON_CALL during the call and back to their shift (unchanged) when it ends.

Calloop — the call engine behind your product.