Appearance
Calls
| Method | Path | Body / query | Response |
|---|---|---|---|
POST | /v1/tenants/{t}/calls | Outbound intent — see below | 201 { 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:
statusis one ofINITIATED,RINGING,IN_PROGRESS,TERMINATED,FAILED;directionINBOUNDorOUTBOUND;from(inclusive) andto(exclusive) are ISO 8601 timestamps compared with the call's creation time (400if one doesn't parse);customeris the customer's address — a phone number with or without+or00, or a SIP URI;channel_ref/queue_refpick one line or queue (400if the ref matches nothing);limit1–200, default 50. Pages are newest first, two ways: by number —page(1, 2, …) returns that page oflimitcalls withpage,pageSizeandtotal(how many calls the filters match), for a list with numbered pages; or by cursor — passnextBeforeIdasbefore_idfor the next page (nextBeforeIdisnullonly when the page is empty), which stays steady while new calls arrive. Without either, the first page.stats=trueaddsstats: what every call the filters match adds up to —{ total, answered, missed, lost, failed, live, inbound, outbound, talkSeconds, avgTalkSeconds, avgWaitSeconds, longestWaitSeconds }.answeredcounts the calls someone answered; of the ones nobody answered,missedare those your side didn't take (NO_ANSWER,IVR_AGENT_NO_ANSWER,TIMEOUT,REJECTED,BUSY),lostthose whose caller hung up first (CANCELLED),failedthose ended by an error (statusFAILED, or a system, network or provider error, an agent dropped); these four never overlap.avgTalkSecondsis per answered call,avgWaitSecondsper call that waited in a queue (nullwhen 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 }].leftAtisnullwhile they're in;leaveReasonisENDED(the call ended),TRANSFERRED(an agent handed it on) orMONITOR_STOPPED.OPERATORis someone running the Calloop platform who listened to the call (only listened — never heard by anyone on it);agentIdandagentRefarenullfor them, and who it was is in Calloop's own audit log, not here.DASHBOARDis one of your people listening from the dashboard, withlistenerName. 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 isparticipants) —[{ 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 }ornull(failureReason: why afailedrecording won't arrive, thereasonofrecording.failed; otherwisenull);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_metadataan object. An omitted field is left unchanged;nullclears it. - Terminate:
409 call_endedif the call already ended. The result arrives ascall.ended:COMPLETEDif it wasIN_PROGRESS,CANCELLEDotherwise, withterminatedBy: CONSUMER(your backend ended it). An outbound intent the agent hasn't started yet ends straight away. - Recording: a short-lived download URL.
404until the recording has completed (recording.completedsays when;recording.failedthat it won't);503 storage_unavailablewhen 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/eventsno longer lists them).409 call_activewhile 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 thecustomerfilter) keeps its row for your history and reports, but without the customer: the address, name,external_ref,consumer_metadataand provider details are cleared, and its lifecycle log, IVR key presses, SDP, recording and events are deleted.activeCallsSkippedcounts the customer's calls still in progress — erase again once they've ended. Safe to repeat.- Either answers
503 storage_unavailablewhen 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.addressis 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_refis400; an unknownagent_refis404. 403 line_not_owned: the channel is another agent's personal line.409codes:invalid_channel/invalid_agent(not this tenant's),channel_disabled(channel notACTIVE),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.