Calls, Recordings, and Participants

Every call Sent handles for you is a record under /v3/calls. This guide covers reading that record, ending a live call, recording it, and managing the participants of a call your callback connected to a conference room.

Prerequisites

  • A call id. It arrives as callId on the call.request question your callback receives, and as call_id on every call.* webhook. See Answering Calls.
  • A number with phone calls turned on, with a callback URL that answers. See Managing channels.

All requests authenticate with the x-api-key header. Call ids look like call_9f2ab000-0000-4000-8000-000000000001; anything else is refused with 400.

The Call Record

GET /v3/calls/{id} returns one of your calls, with a timeline of when it entered each status:

curl "https://api.sent.dm/v3/calls/call_9f2ab000-0000-4000-8000-000000000001" \
  -H "x-api-key: $SENT_API_KEY"
{
  "success": true,
  "data": {
    "id": "call_9f2ab000-0000-4000-8000-000000000001",
    "direction": "outbound",
    "from": { "kind": "user", "value": "agent-42" },
    "to": { "kind": "number", "value": "+12025550142" },
    "number": "+12025550123",
    "status": "COMPLETED",
    "failure_reason": null,
    "started_at": "2026-07-22T12:00:00+00:00",
    "answered_at": "2026-07-22T12:00:05+00:00",
    "ended_at": "2026-07-22T12:00:47+00:00",
    "duration_seconds": 42,
    "price": null,
    "recording_available": false,
    "timeline": [
      { "status": "INITIATED", "timestamp": "2026-07-22T12:00:00+00:00" },
      { "status": "ANSWERED", "timestamp": "2026-07-22T12:00:05+00:00" },
      { "status": "COMPLETED", "timestamp": "2026-07-22T12:00:47+00:00" }
    ]
  },
  "error": null
}
FieldDescription
idThe call id. The same one the call.request question and every call.* webhook carry
directionoutbound for a call placed from your app, inbound for a call to one of your numbers
from, toWho placed the call and who it was for. kind is user, number, conference or anonymous; value is the app user's identity, the phone number in E.164 format, or the room name. value is null when the kind is anonymous
numberYour number that owns the call: the dialed number for an inbound call, the caller's bound number for a call placed from your app
statusINITIATED, RINGING, ANSWERED, COMPLETED, FAILED, NO_ANSWER or REJECTED. Upper case, like a message status
failure_reasonWhy the call did not complete. Null while the call is live, when it completed, and when it failed without a recorded reason. See Call Failure Reasons
started_atWhen the call was placed (UTC)
answered_atWhen the call was answered (UTC). Null until then, and always null for a call between two of your app users
ended_atWhen the call ended (UTC). Null while the call is live
duration_secondsBillable duration in seconds. Null while the call is live
priceWhat the call cost. Null until it has been priced. Only completed calls are charged
recording_availableTrue once a recording of the call is available
timelineWhen the call entered each status, oldest first. Only returned when reading one call

GET /v3/calls lists your calls, most recent first, in pages of page_size (default 20, at most 100) starting at page 1. Filter with direction, status, number (one of your voice numbers) and the time the call started: from and to, both inclusive, as ISO 8601.

curl "https://api.sent.dm/v3/calls?direction=inbound&status=completed&from=2026-07-22T00:00:00Z" \
  -H "x-api-key: $SENT_API_KEY"

Filters match any case, so status=completed and status=COMPLETED are the same filter. Use the call.* webhooks for live updates; the list is for looking calls up afterwards.

Hang Up a Call

POST /v3/calls/{id}/hangup ends one of your live calls:

curl -X POST "https://api.sent.dm/v3/calls/call_9f2ab000-0000-4000-8000-000000000001/hangup" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

The response is 204. The call then ends the way any other call does, once the disconnect is reported. An answered call ends COMPLETED and you receive call.completed. A call that was still ringing ends NO_ANSWER, REJECTED or FAILED, as the disconnect reports it, and you receive call.failed.

ResponseMeaning
409 CONFLICT_008The call has already ended
409 CONFLICT_011The call has no phone leg, such as a call between two of your app users. The app ends that call itself

Record a Call

A recording starts in one of three ways:

  • record: true on a connectToNumber or connectToConference answer. Recording starts the moment the legs join.
  • A startRecording instruction in an answer, before the action runs.
  • POST /v3/calls/{id}/recordings with action start, mid-call.
curl -X POST "https://api.sent.dm/v3/calls/call_9f2ab000-0000-4000-8000-000000000001/recordings" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "action": "start" }'

Stop the same way with action stop, or with a stopRecording instruction in a later answer. Either stops a recording started by any of the three. The response is 204.

Each start is a recording of its own. Once the call was recorded and the recording has landed, call.recording_ready is sent once per recording, naming it in recording_id. Until then, and for a call that was never recorded, the recordings list is empty.

GET /v3/calls/{id}/recordings returns every recording of the call, oldest first, each with a link:

curl "https://api.sent.dm/v3/calls/call_9f2ab000-0000-4000-8000-000000000001/recordings" \
  -H "x-api-key: $SENT_API_KEY"
{
  "success": true,
  "data": {
    "recordings": [
      {
        "recording_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
        "download_url": "https://recordings.example.com/9f2ab000-0000-4000-8000-000000000001/7c1d2e3f4a5b4c6d8e9f0a1b2c3d4e5f.mp3?X-Amz-Expires=3600&X-Amz-Signature=...",
        "url_expires_at": "2026-07-22T13:00:00+00:00"
      }
    ]
  },
  "error": null
}
FieldDescription
recording_idThe recording's id, the one its call.recording_ready webhook announced it under
download_urlA pre-signed link that downloads the recording as an MP3 file
url_expires_atWhen the link stops working (UTC). Request the recordings again for a fresh link

Anyone holding download_url can download the recording until the link expires. Treat it like the recording itself: fetch it from your backend, and never hand it to a browser you do not control.

ResponseMeaning
403 BUSINESS_026Call recording is not available. Applies to stop as well, because nothing could be recording
409 CONFLICT_008The call has already ended
409 CONFLICT_011The call has no phone leg, such as a call between two of your app users, which cannot be recorded

A connectToUser answer accepts record: true but never honours it: a call connected to an app user cannot be recorded. The call connects without the recording. Stops in the same answer still run, because a recording started earlier in the call may still be running.

Conference Participants

Only a live call your answer connected to a conference room takes participants. A live call connected to a user or a number answers 409 CONFLICT_009; a call that has ended answers 409 CONFLICT_008.

POST /v3/calls/{id}/participants dials one of your app users or a phone number into the room:

curl -X POST "https://api.sent.dm/v3/calls/call_9f2ab000-0000-4000-8000-000000000001/participants" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": { "kind": "number", "value": "+14155551234" },
    "caller_id": "+12025550123"
  }'
FieldDescription
to.kinduser for one of your app users, number for a phone number
to.valueThe app user's identity, or the phone number in E.164 format
caller_idThe number shown to a phone participant as the caller, in E.164 format. Must be one of your numbers. The call's owning number when omitted

The response is 202 with the participant's own call record. from.kind is conference, because the room called them:

{
  "success": true,
  "data": {
    "id": "call_9f2ab000-0000-4000-8000-000000000002",
    "direction": "outbound",
    "from": { "kind": "conference", "value": "daily-standup" },
    "to": { "kind": "number", "value": "+14155551234" },
    "number": "+12025550123",
    "status": "INITIATED",
    "failure_reason": null,
    "started_at": "2026-07-22T12:00:00+00:00",
    "answered_at": null,
    "ended_at": null,
    "duration_seconds": null,
    "price": null,
    "recording_available": false,
    "timeline": null
  },
  "error": null
}

A participant is a call of their own. It has its own id, can be read with GET /v3/calls/{id} and hung up, and is billed and reported through call.completed and call.failed like any other call.

Every participant needs a positive balance on the account. A phone participant also needs a destination you may call and a caller id you own:

ResponseMeaning
400 BUSINESS_011Calls to the destination country are not allowed
400 BUSINESS_022caller_id is not one of your numbers
402 BUSINESS_003Insufficient balance. Checked for every participant, app users included
409 CONFLICT_008The call has ended
409 CONFLICT_009The call is live but not in a conference

The room is recorded on the call as Sent reads your answer. A participant added right after the answer can briefly see 409 CONFLICT_009 while that write lands. Retry for a few seconds before treating it as final.

GET /v3/calls/{id}/participants lists who is in the room, the call itself included:

{
  "success": true,
  "data": [
    {
      "id": "call_9f2ab000-0000-4000-8000-000000000001",
      "kind": "user",
      "value": "agent-42",
      "muted": false,
      "duration_seconds": 184
    },
    {
      "id": "call_9f2ab000-0000-4000-8000-000000000002",
      "kind": "number",
      "value": "+14155551234",
      "muted": true,
      "duration_seconds": 96
    }
  ],
  "error": null
}
FieldDescription
idThe participant's own call id. What the mute and remove endpoints take, and what GET /v3/calls/{id} accepts
kinduser for one of your app users, number for a phone number, anonymous for a caller who withheld their number
valueThe app user's identity or the phone number in E.164 format. Null when the kind is anonymous
mutedTrue while the room mutes this participant
duration_secondsHow long the participant has been connected to the room, in seconds

The list is empty once everyone has left.

PATCH /v3/calls/{id}/participants/{participantId} mutes or unmutes one participant. muted is required: true silences them, false lets them be heard again. Muting a participant who is already muted succeeds, as does unmuting one who is not. The response is 204.

curl -X PATCH "https://api.sent.dm/v3/calls/call_9f2ab000-0000-4000-8000-000000000001/participants/call_9f2ab000-0000-4000-8000-000000000002" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "muted": true }'

DELETE /v3/calls/{id}/participants/{participantId} removes one participant. Their leg ends and is reported through call.completed; everyone else stays connected.

DELETE /v3/calls/{id}/participants removes every participant, the call itself included. Every leg ends and is reported through call.completed. A room that is already empty answers 204 as well.

Both answer 404 RESOURCE_017 for a participant who is not in this call's room, 409 CONFLICT_008 for a call that has ended, and 409 CONFLICT_009 for a live call that is not in a conference.

What a Call Costs

price is on the call record and on call.completed. On the webhook it is omitted until billing has recorded the charge, so read the call again later if you need it. Only completed calls are charged.

A leg to a phone number costs money for as long as it runs. It is told up front how long your balance affords at the destination's per-minute rate, and ends as completed when it reaches that cap. Legs to app users and conference rooms are free and have no such limit.

Next Steps

On this page