message.comDevelopers

Calls Live

Calls are conversations with channel = "call". Each call carries an event timeline (ringing, answered, ended, voicemail_left) plus the standard conversation fields. AI features (Smart Hold, AI Receptionist) layer on top.

Real-time call control happens over Socket.io call events. The REST endpoints here exist for backend automation, audit, and history.

List calls

GET/api/v1/callsAuth: Bearer

Cursor-paginated list. Filters: status, assigneeId, departmentId, phoneNumberId, visitorId.

Code samples

cURL
curl 'https://app.message.com/api/v1/calls?status=open' \
  -H 'Authorization: Bearer YOUR_WORKSPACE_JWT'
JavaScript
const res = await fetch('https://app.message.com/api/v1/calls?status=open', {
  headers: { Authorization: 'Bearer ' + token }
});
const { calls } = await res.json();
Python
import requests
r = requests.get(
    "https://app.message.com/api/v1/calls",
    headers={"Authorization": f"Bearer {token}"},
    params={"status": "open"},
)
calls = r.json()["calls"]
Ruby
require "net/http"
require "json"
uri = URI("https://app.message.com/api/v1/calls?status=open")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
  req = Net::HTTP::Get.new(uri)
  req["Authorization"] = "Bearer #{token}"
  http.request(req)
end
calls = JSON.parse(res.body)["calls"]
PHP
<?php
$ctx = stream_context_create([
  "http" => ["method" => "GET", "header" => "Authorization: Bearer $token"]
]);
$calls = json_decode(file_get_contents("https://app.message.com/api/v1/calls?status=open", false, $ctx), true)["calls"];

Get one call

GET/api/v1/calls/:conversationIdAuth: Bearer

Returns the call record plus the full event timeline.

200 OK
{
  "id": "uuid",
  "version": 4,
  "channel": "call",
  "status": "open",
  "fromE164": "+14155550100",
  "toE164": "+14155550199",
  "assignedAgentId": "uuid",
  "phoneNumberId": "uuid",
  "voicemailId": null,
  "events": [
    { "eventType": "ringing", "createdAt": "2026-05-13T15:00:00Z", "fromE164": "+14155550100" },
    { "eventType": "answered", "agentId": "uuid", "createdAt": "2026-05-13T15:00:08Z" },
    { "eventType": "ended", "createdAt": "2026-05-13T15:01:32Z", "duration_ms": 84000, "recordingUrl": "https://..." }
  ],
  "createdAt": "2026-05-13T15:00:00Z"
}

Event types

TypeMeaning
ringingCarrier delivered the inbound call; queue is being notified.
answeredAn agent picked up. Carries agentId.
on_holdAgent put the caller on hold.
resumedHold released.
transferredCall moved to another agent or department.
endedCall ended. Carries duration_ms and recordingUrl (if recording enabled).
voicemail_leftCaller left a voicemail; voicemailId links to the voicemail record.
ai_engagedSmart Hold or AI Receptionist took the call. Planned

Answer a call

POST/api/v1/calls/:conversationId/answerAuth: Bearer

Claims the call for the calling agent. The carrier-side answer happens over WebRTC in the dashboard; this endpoint records the claim and returns one of four 409 call_unavailable reasons if it loses the race: already_claimed, call_ended, agent_busy, or stale_version.

Hang up

POST/api/v1/calls/:conversationId/hangupAuth: Bearer

Ends the call cleanly. The conversation status moves to solved. An ended event is appended.

Hold and resume

POST/api/v1/calls/:conversationId/holdAuth: Bearer

One endpoint toggles both directions: there is no separate /resume path. Body { "hold": true } (or an empty body) places the caller on hold; { "hold": false } resumes. Holding triggers the configured hold music or, above the Smart Hold threshold, the AI hold experience.

Transfer

POST/api/v1/calls/:conversationId/transferAuth: Bearer

Mirrors assign-to-agent under the calls path so a phones-focused integration can use one consistent set of paths. Same handshake semantics as conversation transfer.

Body
{
  "agentId": "uuid",
  "expectedVersion": 4
}

Recording

There is no mid-call recording start/stop action. Recording is a per-phone-number setting (recordingEnabled on PATCH /api/v1/phone-numbers/:id): if it's on for the number a call comes in on, the whole call is recorded automatically. Recordings are stored in DigitalOcean Spaces; the URL appears on the ended event.

Call recording is regulated. Two-party consent jurisdictions require notice. We provide a configurable spoken disclosure on the inbound greeting; ensure it is enabled before recording in those regions.

Errors

CodeWhen
400 invalid_bodyPayload validation failed.
403 not_assignedCaller is not the assigned agent and not an admin.
404 not_foundCall does not exist in this workspace.
409 call_unavailableAnswer lost the race; see reason for which of the four cases.

Common pitfalls

  • Polling for incoming calls. Subscribe to call events on the agent namespace. The REST list endpoint is for backfill only.
  • Looking for a /resume path. There isn't one; call hold again with { "hold": false }.
  • Assuming recording is always on. Recording is per-phone-number. Check recordingEnabled on the phone-number record before relying on a recording URL.
  • Confusing call status with event type. Status is the conversation state (open, solved). Event type is a specific transition in the call timeline.