Calls
An analysed recording. Everything is scoped to the active project — there is no project parameter on these routes.
List calls
GET /api/calls
| Param | Notes |
|---|---|
outcome | resolved, follow_up_scheduled, visit_scheduled, escalated, interested, no_interest, voicemail, failed |
sentiment | positive, neutral, negative |
direction | inbound or outbound |
source_type | How it arrived — e.g. manual_upload, public_api |
customer_id | Restrict to one customer |
segment_uuids | Comma-separated, OR-matched |
search | Free text |
unassigned | 1 returns only calls with no segment |
limit / offset |
An unknown segment_uuid is a 400, not a silently empty page.
Get a call
GET /api/calls/{id}
{
"id": 1042,
"uuid": "sc1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
"customer_id": 87,
"customer_name": "Priya Kumar",
"direction": "outbound",
"source_type": "manual_upload",
"segment": "winback",
"segment_name": "winback",
"agent_name": "Arjun Rao",
"agent_type": "human",
"duration_seconds": 113,
"status": "completed",
"error_message": null,
"outcome": "follow_up_scheduled",
"sentiment": "positive",
"summary": "Customer interested in the Standing Desk Pro; asked about delivery.",
"key_details": [
{ "label": "Promised date", "value": "2026-09-27" }
],
"custom_field_values": { "budget_confirmed": true, "decision_maker": "Priya Kumar" },
"scores": { "opening": 3.0, "probing": 2.0, "objection_handling": 3.0 },
"goal_status": "achieved",
"goal_reasoning": "Follow-up call booked for the 27th.",
"recording_url": "/api/calls/1042/recording",
"transcript": [
{ "speaker": "agent", "text": "Hi, is that Priya?", "start": 0.0 },
{ "speaker": "customer", "text": "Yes, speaking.", "start": 2.4 }
],
"polished_transcript": [],
"polish_status": null,
"segments": [{ "uuid": "sg1a…", "name": "winback" }],
"is_flagged": false,
"processing_skip_reason": null,
"coaching_analysis": null,
"speech_duration_seconds": 96.2,
"silence_duration_seconds": 16.8,
"waveform_peaks": [0.02, 0.31, 0.44],
"started_at": "2026-09-20T14:32:01Z",
"ended_at": "2026-09-20T14:33:54Z"
}
| Field group | What it is |
|---|---|
outcome, sentiment, summary | Model-extracted conclusions |
key_details | Labelled facts pulled from the conversation |
custom_field_values | Your configured extraction fields |
scores | Per-criterion rubric scores |
goal_status, goal_reasoning | Whether the call achieved its purpose, and why |
speech_*, silence_*, waveform_peaks | Measured before transcription; null on older rows |
is_flagged | Flagged for review |
processing_skip_reason | Why the pipeline skipped this recording, when it did |
coaching_analysis | Coaching notes, where that analysis is configured |
segment / segment_name | The single segment string carried on the call row, distinct from the many-to-many segments array |
Statuses
| Status | Meaning |
|---|---|
pending | Queued |
awaiting_recording | Row exists, audio has not arrived |
transcribing | Speech recognition running |
completed | Analysed |
failed | Pipeline failure — see error_message |
Update a call
PATCH /api/calls/{id}
{
"customer_name": "Priya Kumar",
"customer_phone": "+918888888888",
"customer_segment": "winback",
"customer_id": 87,
"new_customer": false,
"is_flagged": true
}
| Field | Type | Notes |
|---|---|---|
customer_name | string | |
customer_phone | string | |
customer_segment | string | |
customer_id | integer | Reattach the call to an existing customer |
new_customer | bool | Create a new customer from the name and phone above instead of matching |
is_flagged | bool | Flag or unflag for review |
Every field is optional; send only what changes. Model output such as scores,
outcome, and summary is produced by analysis and cannot be set here, and
neither can direction — that is fixed at upload.
Delete a call
DELETE /api/calls/{id} → 204
Retry a call
POST /api/calls/{id}/retry
Re-queues a failed call. The stored audio is reused, so there is nothing to
re-upload. The call returns to pending.
Use this after fixing whatever caused the failure — a missing model route, an
expired provider key, ffmpeg not on PATH.
Re-analyze a call
POST /api/calls/{id}/reanalyze — superadmin only
Runs the analysis again on a call that already succeeded, picking up changed extraction fields or prompts. Restricted because re-running analysis across a deployment is expensive.
Polish a transcript
POST /api/calls/{id}/polish-transcript
Produces a cleaned-up transcript — punctuation, speaker attribution, filler
removal — into polished_transcript, leaving the raw one intact. polish_status
tracks progress.
The original is never overwritten: polishing is a model pass and can be wrong, and a disputed call needs the unedited version.
Recording playback
GET /api/calls/{id}/recording?token=<session-token>
Streams the audio. This route accepts the token in the Authorization header
or as a ?token= query parameter, because a plain <audio src> cannot set
a header.
Segments on a call
PUT /api/calls/{id}/segments
{ "segment_uuids": ["sg1a2b3c-…", "sg4d5e6f-…"] }
Replaces the call's segments. To add across many calls at once, use
POST /api/calls/bulk-segments — see Segments.
Flagged and skipped calls
A call can carry a reason explaining why it was skipped by the pipeline, or why one that does have a transcript was flagged — a recording that is almost all silence, or too short to analyse. Those are surfaced rather than quietly producing an empty analysis that looks like a real one.
Filter with unassigned=1 to find calls that were never sorted into a segment.