Skip to main content

Calls

Start a call​

POST /api/customers/{id}/calls · scope calls:write

{
"agent_id": 12,
"kb_document_ids": [3, 7]
}
FieldRequiredNotes
agent_id✓The teammate to ring first — an id, not a number
kb_document_ids—Which documents suggestions may draw on

agent_id is an id rather than a phone number on purpose: agents are org members with a stored handset, so a call can only ever reach someone who already has an account here.

An empty kb_document_ids means no suggestions

It does not mean "search everything". Omitting the field gives the rep a call with no assistance at all — which is a valid choice, but make it deliberately.

What happens​

  1. The agent's phone rings
  2. When they answer, the customer is dialled
  3. Call audio is forked to nextneural_assist, transcribed per leg
  4. Suggestions stream to the rep over the agent-assist socket
StatusCause
400{"detail": "choose which agent should take this call"}
400{"detail": "that agent is inactive and can't take calls"}
400{"detail": "this customer has no phone number and cannot be called"}
400{"detail": "this customer's phone number isn't a valid number"}
400{"detail": "one or more selected documents aren't available for AI — enable them in Knowledgebase first"}
404{"detail": "that agent isn't set up in your organization"}
404{"detail": "customer not found"}

The knowledge base check happens before the call is placed — a rep should not discover mid-conversation that their documents were never vectorized. See Knowledge base.

Open to every role: placing a call is day-to-day sales work.

Live calls​

GET /api/calls/live · scope calls:read

Every call currently in progress in the organization — a supervisor view.

{
"calls": [
{
"uuid": "7f3a1b2c-…",
"customer_id": 87,
"customer_name": "Priya Kumar",
"customer_company": "Pragati Interiors",
"agent_type": "human",
"call_type": "outbound",
"from_number": "+915550010000",
"to_number": "+918888888888",
"status": "in_progress",
"started_at": "2026-09-20T14:32:01Z",
"answered_at": "2026-09-20T14:32:09Z",
"ended_at": null
}
]
}

Note started_at and answered_at are distinct: the gap between them is how long the phone rang.

My live call​

GET /api/calls/live/mine · scope calls:read

{ "call": null }

The caller's own current call under a call key, null when they are not on one. This is what a rep's panel polls to know when to open the assist socket — it avoids showing them a colleague's call, and avoids needing supervisor visibility to see your own.

Get a call​

GET /api/calls/{uuid} · scope calls:read

The full record including transcript and recording, once the call has ended.

Call statuses​

StatusMeaning
initiatedThe agent's leg is ringing
agent_answeredThe rep picked up; the customer is being dialled
in_progressBoth legs connected
completedEnded normally
agent_no_answerThe rep never answered — the customer was not called
customer_no_answerThe customer did not answer
failedThe call could not be placed

agent_no_answer is worth distinguishing in any reporting you build: the customer was never disturbed, so it is not a failed outreach attempt.

Provider callbacks​

Called by the provider, not by you:

EndpointPurpose
POST /api/webhooks/plivo/answer/{uuid}A leg answered
POST /api/webhooks/plivo/hangup/{uuid}A leg ended
POST /api/webhooks/plivo/recording/{uuid}Recording ready
POST /api/webhooks/plivo/stream-status/{uuid}Media stream verdict
GET/POST /api/webhooks/exotel/stream-urlReturns the socket address
GET/POST /api/webhooks/exotel/connect-paramsReturns connection parameters

Both methods are accepted on the Exotel routes because providers differ in which they use.

Exotel's unmatched calls

Exotel answers a stream request it could not match to an agent with an unmatched- address rather than refusing it, because the customer is already on the line. Such a call has no row and nothing watching it — the audio is still accepted, so the failure surfaces as an empty assist panel plus a log line, rather than as a dropped call on a real customer.

Dashboard​

GET /api/dashboard · scope calls:read

{
"window": "today",
"stats": {
"sessions_total": 0,
"sessions_active": 0,
"prompts_served": 0,
"avg_response_ms": null
},
"trend": [ { "date": "2026-09-16", "sessions": 0 } ]
}

Measures assist sessions and the prompts served to reps, not call volume — avg_response_ms is how quickly suggestions reached the screen.