Live (near-real-time).
Rolling transcript and risk alerts while the call is still happening: open a session, post short audio chunks as they are recorded, and read back sentiment, escalation risk, and typed alerts with seconds-level lag. Finalizing the session runs the full analysis pipeline and produces a normal call — transcript, analysis, and cited QA scorecard included.
Session lifecycle#
| Field | Type | Description |
|---|---|---|
| open | status | Accepting chunks. Created by POST /v1/live. |
| finalizing | status | Finalize is running the full analysis. Chunks are rejected with 409. If the pipeline fails, the session reverts to open so finalize can be retried. |
| finalized | status | Done — call_id points at the analyzed call. Repeating the finalize request replays the same call instead of billing again. |
Open a session#
/v1/liveJSON body, all fields optional — an empty body opens an anonymous session.
| Field | Type | Description | |
|---|---|---|---|
| external_id | string | optional | Your own call/session reference (≤200 chars). Becomes the call's file_name after finalize, so it is searchable in the dashboard. |
| language_hint | string | optional | 3-letter ISO 639-3 code (e.g. ara, eng) applied to every chunk's transcription. Skip it for auto-detect (recommended for mixed ar/en calls). |
| queue | string | optional | Free-form tag (≤120 chars) — carried onto the finalized call and filterable on GET /v1/calls. |
| agent_external_id | string | optional | The agent's ID in your CCaaS/CRM (≤200 chars). Looked up per org — created automatically on first sight — and linked to the session and the finalized call. |
curl -X POST https://pulse.whizztech.ai/v1/live \
-H "Authorization: Bearer $PULSE_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "crm-call-8841",
"language_hint": "ara",
"queue": "billing",
"agent_external_id": "agent-104"
}'{
"id": "3c7f9a12-58e4-4b06-9d21-f0a84c65e937",
"object": "live_session",
"status": "open",
"external_id": "crm-call-8841",
"agent_id": "c3a91f70-2d84-4b16-95e3-8f01d6c42a79",
"language_hint": "ara",
"queue": "billing",
"chunk_count": 0,
"duration_sec": 0,
"started_at": "2026-07-12T09:14:03.000Z"
}Send audio chunks#
/v1/live/{id}/chunksmultipart/form-data with exactly one audio segment in the chunkfield. The chunk is transcribed (diarized, with the session's language hint), appended to the session timeline, and a fast risk check over the last ~12 turns refreshes the state — which the response returns.
| Field | Type | Description | |
|---|---|---|---|
| chunk | file | required | One audio segment, ≤10MB. Accepted: wav, mp3, m4a, ogg, webm — matched by content type or file extension. |
# one self-contained audio segment per request, sent sequentially
curl -X POST https://pulse.whizztech.ai/v1/live/3c7f9a12-58e4-4b06-9d21-f0a84c65e937/chunks \
-H "Authorization: Bearer $PULSE_KEY" \
-F "chunk=@segment-003.wav"{
"seq": 3,
"chunk_count": 3,
"duration_sec": 71.4,
"segments_added": 4,
"state": {
"sentiment": "negative",
"escalation_risk": "high",
"alerts": [
{
"type": "escalation_request",
"detail": "Customer explicitly asked to speak to a supervisor about the duplicate charge.",
"seq": 3
}
],
"updated_at": "2026-07-12T09:15:21.412Z"
}
}| Field | Type | Description |
|---|---|---|
| seq | integer | This chunk's 1-based sequence number in the session. |
| chunk_count / duration_sec | integer / number | Session totals after this chunk. duration_sec is the sum of transcribed chunk durations — it is the session timeline offset for the next chunk. |
| segments_added | integer | Diarized turns this chunk contributed. |
| state | object | The fresh rolling state — see State below. |
Chunk format rules#
Each chunk must be a self-contained, playable audio file — its own header, its own container. Recording 15–30-second segments and posting each one works; splitting a single stream at arbitrary byte offsets does not (the pieces after the first have no header and fail transcription).
Send chunks sequentially — wait for each response before posting the next. A chunk that races another chunk (or a finalize) fails with 409 conflict and nothing is stored; resend it after the in-flight request finishes. When the session is no longer open the response is 409 session_not_open.
Speaker ids are per chunk: diarization runs on each segment independently, so ids are prefixed with the chunk sequence (c3_speaker_0). Two chunks' speaker_0 are not claimed to be the same person mid-session — roles are resolved properly over the whole transcript at finalize.
Rolling state & alert types#
The state is recomputed from the most recent turns after every chunk. Alerts accumulateacross the session (deduplicated, so a repeated finding doesn't stack) — sentiment and escalation_risk always reflect the latest check.
| Field | Type | Description |
|---|---|---|
| sentiment | string | null | Customer sentiment right now: positive · neutral · negative. null until the first check completes. |
| escalation_risk | string | null | low · medium · high — how likely the call is heading to a blow-up or supervisor escalation. |
| alerts | object[] | { type, detail, seq } — accumulated typed alerts; seq is the chunk the alert first fired on. |
| updated_at | string | null | ISO timestamp of the last successful risk check. A transient LLM failure keeps the previous state — the chunk itself is never lost. |
Alert types#
| Field | Type | Description |
|---|---|---|
| churn_signal | alert | The customer threatens or hints at leaving, cancelling, or switching provider. |
| compliance_risk | alert | The agent skipped a required step (identity verification, disclosure) or mishandled sensitive data. |
| escalation_request | alert | The customer explicitly asks for a manager/supervisor or to file a complaint. |
| vulnerable_customer | alert | Signs of a vulnerable person: elderly confusion, distress, medical emergency, financial hardship. |
Poll a session#
/v1/live/{id}The current state plus the transcript tail (last 10 segments) — enough to drive a supervisor wallboard without shipping the whole transcript on every poll. IDs from other organizations return 404 not_found.
curl https://pulse.whizztech.ai/v1/live/3c7f9a12-58e4-4b06-9d21-f0a84c65e937 \
-H "Authorization: Bearer $PULSE_KEY"{
"id": "3c7f9a12-58e4-4b06-9d21-f0a84c65e937",
"object": "live_session",
"status": "open",
"external_id": "crm-call-8841",
"agent_id": "c3a91f70-2d84-4b16-95e3-8f01d6c42a79",
"language_hint": "ara",
"queue": "billing",
"chunk_count": 3,
"duration_sec": 71.4,
"state": {
"sentiment": "negative",
"escalation_risk": "high",
"alerts": [
{
"type": "escalation_request",
"detail": "Customer explicitly asked to speak to a supervisor about the duplicate charge.",
"seq": 3
}
],
"updated_at": "2026-07-12T09:15:21.412Z"
},
"segments_tail": [
{ "speaker": "c3_speaker_0", "start": 52.1, "end": 58.9, "text": "…" },
{ "speaker": "c3_speaker_1", "start": 59.2, "end": 66.0, "text": "…" }
],
"call_id": null,
"started_at": "2026-07-12T09:14:03.000Z",
"updated_at": "2026-07-12T09:15:21.000Z"
}Finalize#
/v1/live/{id}/finalizeCloses the session and runs the full pipeline over the assembled transcript — the same one uploaded calls get post-transcription: PII redaction, complete analysis (summary, topics, pain points, coaching rewrites, CSAT/churn prediction), and auto-QA against your default scorecard with cited quotes. The result is a normal call: fetch it with GET /v1/calls/{id}, and it appears in the dashboard like any other (minus the audio player — there is no single recording file).
curl -X POST https://pulse.whizztech.ai/v1/live/3c7f9a12-58e4-4b06-9d21-f0a84c65e937/finalize \
-H "Authorization: Bearer $PULSE_KEY"{
"id": "3c7f9a12-58e4-4b06-9d21-f0a84c65e937",
"object": "live_session",
"status": "finalized",
"call_id": "9f2c51b8-4a07-4e63-b1d8-72e0a5c93f14",
"chunk_count": 14,
"duration_sec": 371.8,
"credits_billed": 1,
"call": {
"qa_score": 71.5,
"verdict": "partial",
"sentiment": "negative",
"csat_predicted": 2,
"escalation_risk": "high",
"churn_risk": "medium",
"summary": "Customer called about a double charge on this month's invoice…"
}
}A session with no transcribed audio returns 422 empty_session. Finalizing an already-finalized session is idempotent — it returns the existing call instead of billing again:
{
"id": "3c7f9a12-58e4-4b06-9d21-f0a84c65e937",
"object": "live_session",
"status": "finalized",
"call_id": "9f2c51b8-4a07-4e63-b1d8-72e0a5c93f14",
"replayed": true
}Billing#
A session bills exactly like an uploaded call under the 8-minute fair-use rule: 1 credit per started 8 minutes of transcribed audio (credits_billed — a 6-minute session is 1 credit, a 20-minute session is 3). State polls are free.
Credits are charged as the session streams, not only at finalize: each chunk settles whatever the session owes for the audio received so far, so an abandoned session is still charged for what it consumed, and finalize only collects the remainder. Resending a chunk never charges twice — a duplicate chunk index fails the 409 conflict guard before any debit runs.
Opening a session with an empty wallet returns 402 insufficient_credits, and so does a chunk posted once the balance can no longer cover it — the chunk is refused before any transcription is bought. Top up, then continue or finalize the session.