Calls
Upload sales-call recordings, pass the consent gate, and get AI transcripts, deal insights, and proposed CRM updates.
Calls
Calls is Pact's conversation-intelligence surface. A recording is uploaded (or captured from a live voice session), passes a SQL-enforced recording-consent gate, then flows through a pending → transcribing → extracting → ready pipeline that produces a diarized transcript, structured deal insights, and proposed CRM updates a rep can approve one click at a time.
The list lives at /calls; every recording is backed by the call_recordings table and served by the /v1/calls API (api/routes/conversation_intel.py). Access requires the sales module (require_module("sales")).
This is a live, enforced surface
Upload, the consent gate, transcription, AI extraction, and the approve/reject proposal loop are all real backend code. The one external dependency is a speech-to-text key — see the note below.
The recording lifecycle
Every row carries a status that the list renders as a colored chip:
| Status | Meaning |
|---|---|
pending | Uploaded and queued; the pipeline has not started. |
transcribing | Audio is at the STT provider. |
extracting | Transcript is back; the LLM is pulling insights. |
ready | Transcript, insights, and proposals are all available. |
failed | A phase errored (often a missing STT key). |
consent_blocked | The consent gate refused this contact — audio was preserved for audit but never transcribed. |
The list polls every few seconds, so a freshly uploaded call visibly walks through the phases without a manual refresh. Filter chips at the top of /calls narrow the list to any single status.
Uploading a recording
- 1
Open the upload dialog
Click Upload recording on
/calls. Acceptsmp3,wav,m4a, and otheraudio/*orvideo/*files. The filename extension and the multipartContent-Typeare both validated server-side, and empty or oversized files are rejected. - 2
Identify the contact for the consent gate
Provide the contact email (and optionally an opportunity ID). The email is how the gate resolves a consent subject — without a resolvable email or phone the upload cannot be cleared.
- 3
Consent gate runs at ingest
check_recording_consentlooks for agrantedcall-channel consent record for that contact. No record means default-deny: the upload returns HTTP 409, the row is written inconsent_blockedstate for audit, and no transcript is ever generated. - 4
Pipeline is enqueued
On a granted contact the audio is stored, the row goes
pending, and (with auto-process on) the transcription job is queued immediately.
The gate is deliberately strict because sales-call recording is a two-party-consent primitive. Grant or inspect call consent in the consent ledger.
Speech-to-text requires a provider key
Transcription uses AssemblyAI, keyed by the ASSEMBLYAI_API_KEY secret. When it is unset, /calls shows a banner and uploads still succeed but stay in failed until an operator sets the secret (flyctl secrets set ASSEMBLYAI_API_KEY=… -a pact-demo). The GET /v1/calls/_meta/stt-status endpoint reports whether the key is configured. Seed data still renders regardless.
What the pipeline produces
Once a call reaches ready, the call detail view exposes three things:
- Transcript — speaker-diarized segments with timestamps and rep/prospect roles.
- AI insights — extractions typed as pain, objection, competitor, criterion, next step, buying signal, and sentiment, each with an evidence quote and a click-to-jump citation into the transcript.
- Proposed CRM updates — AI-drafted field changes on the linked opportunity that a rep approves or rejects. Nothing is written to the opportunity until a human approves it.
Reprocessing
Any call except a consent_blocked one can be re-run via Reprocess (POST /v1/calls/{id}/process). This resets the row to pending and re-queues the pipeline — useful after an STT key is added or extraction logic improves. Reprocessing a consent_blocked row is refused with a 409.
Coaching
Beyond per-call insights, the pipeline computes coaching metrics (talk ratio, questions asked, longest monologue, filler density) that roll up per rep and per management subtree. Managers see their team via /v1/calls/coaching/team/{user_id}, scoped by the org hierarchy. See the coaching page for the full breakdown.