Calls
What actually happened on your channels: outcome, duration, transcript, tool calls and post-call extraction.
What actually happened on your channels.
const { calls } = await connect.calls.list({ workspaceId, from, to });
const call = await connect.calls.get(callId);
// call.transcript, call.outcome, call.durationSeconds, call.extractionA call record carries the outcome, the duration, the transcript, any tool calls the agent made, and whatever post-call extraction the agent was configured to perform.
Transcripts are the customer's data. They contain whatever the caller said - names, card numbers, medical details, whatever the conversation was about. Whether a call is recorded at all is the workspace's setting, not yours, so treat an absent transcript as normal rather than as a failure. Whatever you store from here falls under the deletion policy you declared for this application.
Records appear after a call completes, not during it. There is no live stream on this API.
Endpoints
GET /api/v1/application/calls
List your calls
Scope calls:read · Rate limit 30 requests / min per IP, 60 requests / min per API key
Every call that arrived on a channel assigned to your application, newest first, across all of your subscribing workspaces.
The application is resolved from your API key and is never a parameter, so there is no request that reads another partner's calls. channelId only narrows within what you already hold; one belonging to someone else simply matches nothing.
Calls your UI placed are a separate list. A call a member places with <ConnectCallButton> leaves from the workspace's own number, which is usually assigned to no application, so it never appears above. Pass placedByApplication=true (optionally with workspaceId) for exactly those - the calls call.completed / call.missed describe with handler: "member". That is what to reconcile a timeline from when a webhook was delivered somewhere else.
Otherwise channelId is the only filter, and that is a budget decision rather than an oversight - every filter here costs a Firestore composite index against a project already at the 200 ceiling. Filter the rest client-side, or read the aggregate from /analytics.
Page until hasMore is false; do not stop on an empty items.
What this deliberately does not return: the routing snapshot taken at pick-up (unbounded, unvalidated, and available live at GET /channels/{channelId}), the carrier's own call id and hangup text, and the carrier's measure of the recording's length. agentId/agentVersion carry who answered, and durationSeconds/billDurationSeconds are the two durations Connect stands behind.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
channelId | query | no | Narrow to one channel. |
placedByApplication | query | no | true for the member calls your UI placed instead of the calls on your channels. |
workspaceId | query | no | With placedByApplication, one workspace's calls. |
cursor | query | no | From a previous response's nextCursor. |
limit | query | no | 1-100, default 50. |
Responses
| Status | Meaning |
|---|---|
200 | Calls on your channels, newest first |
400 | validation_error - a limit outside 1-100 |
403 | forbidden - key unknown or missing the calls:read scope |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
items | array | yes | |
nextCursor | any | yes | |
hasMore | boolean | yes |
GET /api/v1/application/calls/{callId}
Get one call and its transcript
Scope calls:read · Rate limit 30 requests / min per IP, 60 requests / min per API key
The call record plus every transcript turn, ordered by sequence - which is the sort to trust, since two turns can share a millisecond.
interrupted marks a turn the caller talked over, and is only ever true for an agent turn. extraction is your agent's own post-call structured output, keyed by its extractionSchema; Connect stores it without interpreting it.
A long call's transcript is capped at 2,000 turns, the same ceiling applied when it was written.
Recording and transcript files land in the workspace's Drive: recordingFileId and transcriptFileId are Drive file ids, readable with a drive:read key. Both are null until the carrier posts the audio, which is normal for a call that has only just ended - see recordingStatus.
A call belonging to another application answers 404, not 403, for the same enumeration reason the channel reads give.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
callId | path | yes | From GET /calls. Also the id Connect carried into the recording and your own conversation record. |
Responses
| Status | Meaning |
|---|---|
200 | The call and its transcript |
403 | forbidden - key unknown or missing the calls:read scope |
404 | not_found - no such call, or it belongs to another application |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
call | object | yes | |
turns | array | yes |
GET /api/v1/application/calls/availability
Can the signed-in member call from here
Scope calls:place · Rate limit 30 requests / min per IP, 30 requests / min per API key
Whether the member can place a browser call right now, and if not, why - so a Call button can say what is missing instead of failing when pressed. Member-only, like every calls:place endpoint; a member without "Make calls" is refused 403, which the SDK's bridge reports as status: "no_permission".
status, in precedence order: no_number (the workspace holds no business number - offer names the subscription whose plan sells the "Business number" add-on), number_pending (one was bought but Connect's pool was empty; an admin can retry in Connect), no_balance (not a minute's worth of prepaid minutes), available.
canTopUp and canManageNumber are the member's own (billing:update, subscription:update): offer the next step only to someone who can take it. Buying minutes and adding a number both happen in Connect - link to /calling and to /billing/subscriptions/{offer.subscriptionId} with client_id and return_to so they come back.
Responses
| Status | Meaning |
|---|---|
200 | The member's calling availability |
403 | forbidden - key missing calls:place, no session token, or the member lacks Make calls |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
status | available | no_permission | no_number | number_pending | no_balance | yes | |
number | any | yes | |
available | number | yes | |
availableMinutes | number | yes | |
ratePaisePerMinute | number | yes | |
lowBalance | boolean | yes | |
canTopUp | boolean | yes | |
canManageNumber | boolean | yes | |
offer | any | yes |
GET /api/v1/application/calls/settings
Read whether the workspace records its calls
Scope calls:place · Rate limit 30 requests / min per IP, 30 requests / min per API key
The workspace's call-recording switch - the same setting Connect's Calling screen edits - plus canManage, whether the signed-in member may change it (their own channel:manage). Member-only: requires X-Connect-Session-Token; reading needs the member's workspace:read.
recordingRatePaisePerMinute is what recording adds to every billed minute, pre-tax - show it beside the switch, because turning it on changes what every call costs.
Responses
| Status | Meaning |
|---|---|
200 | The workspace's calling settings for this member |
403 | forbidden - key missing calls:place, or no session token |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
recordingEnabled | boolean | yes | |
recordingRatePaisePerMinute | number | yes | |
canManage | boolean | yes |
PUT /api/v1/application/calls/settings
Switch call recording on or off
Scope calls:place · Rate limit 30 requests / min per IP, 30 requests / min per API key
Turns recording of member calls on or off for the workspace. Member-only, and the member must hold channel:manage - the change is audited against them, not your key.
Applies to calls started after it: a call in progress keeps the setting, and the price, it began with. While it is on, the person called hears that the call may be recorded as they answer, and call.analyzed follows each recorded call. Answers the settings as they now stand.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
recordingEnabled | boolean | yes |
Responses
| Status | Meaning |
|---|---|
200 | The settings after the change |
400 | validation_error - recordingEnabled is not a boolean |
403 | forbidden - key missing calls:place, no session token, or the member lacks channel:manage |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
recordingEnabled | boolean | yes | |
recordingRatePaisePerMinute | number | yes | |
canManage | boolean | yes |
GET /api/v1/application/calling/readiness
Read whether the workspace may legally make each kind of call
Scope calls:place · Rate limit 30 requests / min per IP, 30 requests / min per API key
Where the workspace stands on Connect's calling compliance journey - the steps Indian telecom rules ask of a business before it calls (DLT registration, a 140-series number with an approved voice header for sales calls, the autodialer notice to the operator, consent, calling hours). Two callers: a signed-in member (X-Connect-Session-Token; reading needs their workspace:read), or your own server with ?workspaceId= and no token - a dialer checking before it places a call. The workspace must subscribe to your application either way.
runs answers the question you actually have - may this workspace run service or sales calls, dialled by a rep (preview) or by software (automatic) - evaluated by the same gates the dialer refuses on, with the first reason worded for the member. Do not re-derive it from steps.
Send the member to setupUrl to finish a step; it carries your client_id so Connect offers the way back. canManage is their channel:manage - whether they can take the steps or need an admin - and always false on a server call, which has no member.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
workspaceId | query | no | Required without a session token; ignored with one |
Responses
| Status | Meaning |
|---|---|
200 | The journey's state |
400 | validation_error - no session token and no workspaceId |
403 | forbidden - key missing calls:place, the workspace does not subscribe to your application, or the session token is not valid for it |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
intent | any | yes | |
progress | object | yes | |
steps | array | yes | |
runs | array | yes | |
setupUrl | string | yes | |
canManage | boolean | yes |
POST /api/v1/application/calls/browser-token
Sign a member's browser in to the phone network
Scope calls:place · Rate limit 30 requests / min per IP, 30 requests / min per API key
A short-lived carrier access token for Plivo's Browser SDK, which a member's browser logs in with before placing a call. Member-only: requires X-Connect-Session-Token, and that member must hold "Make calls" (call:place) - from their workspace role, or a role on your application carrying connect:call:place.
The token can place no call on its own. Every call it starts is refused by Connect unless POST /calls/start approved it first, so it carries no authority beyond signing in. It is valid for an hour, and the SDK stays registered between calls - a member making calls all morning signs in a handful of times.
You do not need to call this yourself: the SDK's <ConnectCallButton> does, through your bridge.
Responses
| Status | Meaning |
|---|---|
200 | The token and when it expires |
403 | forbidden - key missing calls:place, no session token, or the member lacks Make calls |
412 | precondition_failed - browser calling is not set up on this deployment |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
token | string | yes | |
expiresAt | string | yes | ISO-8601 |
POST /api/v1/application/calls/start
Approve a call from a member to a number
Scope calls:place · Rate limit 30 requests / min per IP, 30 requests / min per API key
Records a call from the signed-in member to phone and returns what their browser needs to place it - the number, the workspace number the callee will see, and two SIP headers to send with the call. Member-only, exactly as /calls/browser-token.
The call leaves from the workspace's own number, never the member's, and is not recorded. Every rule is applied here, before anything reaches the carrier: the workspace must hold an active number and be allowed to place calls, and the member must not already be on one (412).
The headers carry a one-time key valid for two minutes, and Connect dials the number on the record rather than anything the browser later sends - so a tampered browser can ring nobody it was not approved to. Read the outcome afterwards with GET /calls/{callId} (calls:read): handler is member, talkSeconds is how long the callee was on the line, and unansweredReason says why nobody picked up. Your application can read a call it placed even though the number belongs to the workspace.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
phone | string | yes | |
purpose | service | transactional | promotional | no |
Responses
| Status | Meaning |
|---|---|
201 | The approved call |
400 | validation_error - phone is not E.164 |
403 | forbidden - key missing calls:place, no session token, or the member lacks Make calls |
409 | do_not_call - the number is on the workspace's do-not-call list (see GET /do-not-call); conflict - no active number, a blocked workspace, or the member is already on a call |
412 | precondition_failed - no active number, a blocked workspace, or the member is already on a call |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
callId | string | yes | |
toNumber | string | yes | |
fromNumber | string | yes | |
headers | object | yes |
POST /api/v1/application/calls/cancel
Cancel a call the browser never placed
Scope calls:place · Rate limit 30 requests / min per IP, 30 requests / min per API key
For a call /calls/start approved but the browser could not place - the microphone was refused, sign-in failed, the member pressed Hang up while it was still connecting. The call is recorded as unanswered with unansweredReason: "cancelled".
Only the member who started it may cancel it, and only before the carrier has taken it; after that the carrier's own report closes the record, and this does nothing. Someone else's call answers 404.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
callId | string | yes |
Responses
| Status | Meaning |
|---|---|
204 | Cancelled, or already in the carrier's hands |
403 | forbidden - key missing calls:place, no session token, or the member lacks Make calls |
404 | not_found - no such call, or not this member's |
GET /api/v1/application/do-not-call
Read whether a number asked the workspace not to call it
Scope do-not-call:read · Rate limit 120 requests / min per API key
The workspace's do-not-call list is Connect's, and Connect enforces it: every outbound call - a member's /calls/start, from any application or from Connect itself, and every call an agent places - to a listed number is refused with do_not_call (409) before anything reaches the carrier. Read this to show the entry beside a record, never to decide whether to dial.
A number never listed answers doNotCall: false with every other field null, not 404. ?history=true adds the last twenty set/lifted events, newest first. Server-to-server, name the workspace with workspaceId; with X-Connect-Session-Token the workspace comes from the token and the member needs contact:read.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
workspaceId | query | no | |
phone | query | yes | |
history | query | no |
Responses
| Status | Meaning |
|---|---|
200 | The number's standing on the list |
400 | validation_error - phone missing or not E.164 |
403 | forbidden - key missing do-not-call:read, no subscription in the workspace, or the member lacks contact:read |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
status | object | yes | |
history | any | yes |
PUT /api/v1/application/do-not-call
Put a number on the workspace's do-not-call list
Scope do-not-call:write · Rate limit 120 requests / min per API key
Records that a person asked this workspace not to call them. From then on Connect refuses every outbound call to the number, whichever application or agent places it. Every purpose - a person who said "stop ringing me" meant the service call too.
Idempotent: a number already listed keeps its original entry - when they first asked is the fact worth keeping - and answers changed: false, so a backfill can be re-run. Every change is written to an append-only ledger attributed to your application, and to the member when X-Connect-Session-Token came with it (they need call:place - whoever can ring a person can record that they asked to stop). Server-to-server, actor names who made the change in your own terms; it is kept as your claim, never as a Connect user.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
workspaceId | string | no | |
phone | string | yes | |
reason | asked_on_call | asked_in_writing | complaint | imported | other | yes | |
note | any | no | |
actor | object | no |
Responses
| Status | Meaning |
|---|---|
200 | The entry as it now stands |
400 | validation_error - phone not E.164, or an unknown reason |
403 | forbidden - key missing do-not-call:write, no subscription in the workspace, or the member lacks call:place |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
changed | boolean | yes | |
status | object | yes |
POST /api/v1/application/do-not-call/clear
Lift a number's do-not-call entry
Scope do-not-call:write · Rate limit 120 requests / min per API key
Takes a number off the list, so calls to it are placed again. note is required: lifting the entry is a claim that the person took their request back, and the ledger keeps who made it and why. The entry is kept, lifted, with its history; a number not listed answers changed: false. With a session token the member needs contact:manage.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
workspaceId | string | no | |
phone | string | yes | |
note | string | yes | |
actor | object | no |
Responses
| Status | Meaning |
|---|---|
200 | The entry as it now stands |
400 | validation_error - phone not E.164, or no note |
403 | forbidden - key missing do-not-call:write, no subscription in the workspace, or the member lacks contact:manage |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
changed | boolean | yes | |
status | object | yes |