---
title: "Finalize Call"
method: POST
path: "/calls/{session_id}/finalize"
tags: ["calls"]
---

# Finalize Call

`POST /calls/{session_id}/finalize`

## Request

- Base URL: `https://dev.whispa.net` (the document declares no server; this is the origin it was published from)
- URL: `https://dev.whispa.net/calls/{session_id}/finalize`
- Auth: one of:
  - OAuth 2
    - Password
    - Token URL: `auth/bearer/login`
  - API key in cookie `access_token`

Manually trigger finalization for a call session.

## Path parameters

- `session_id` string, uuid, required

## Response `201`

Successful Response

- CallDetailResponse: Full call details including transcript and analyses.
  - `id` string, uuid, required
  - `user_id` string, uuid, nullable, required
  - `team_id` string, uuid, nullable: Team snapshot for this call
  - `user_email` string, nullable: Email of the agent who took the call
  - `user_display_name` string, nullable: Display name of the agent who took the call
  - `call_mode` 'training' | 'production' | 'ai_agent': Call mode enumeration.
  - `call_source` 'live' | 'uploaded': Call source enumeration.
  - `scenario` string, nullable, required
  - `agent_id` string, nullable, required
  - `extension` string, nullable: Phone extension that handled the call
  - `called_number` string, nullable: Inbound destination number reported by the provider, when available
  - `telephony_call_id` string, nullable: External telephony system call ID
  - `provider_call_ref` object, nullable: Provider-native call reference (e.g. TCN call_sid/call_type, AWS Connect contact_id) when the provider hosts the call recording. Only some providers support minting a link via /calls/{call_id}/provider-recording-url (TCN today; the endpoint returns 404 for others). For awsconnect this is the stored reference plus instance_alias resolved from the current Connect integration config, which together address the Connect console's contact page — so the alias is whatever is configured now, not whatever was configured when the call happened
  - `suspensions` object[]: Park windows during the call (TCN payment-line transfers): [{suspended_at, resumed_at}] ISO timestamps; resumed_at is null when the call never resumed (grace expired)
  - `timeline` object[]: Typed call lifecycle events in order: {type, at, detail?, source}. Types: ringing, connected, hold_start, hold_end, consult_start, consult_end, call_suspended, call_resumed, call_ended. Rendered as transcript markers and fed to the summary/coaching/QA prompts
  - `customer_number` string, nullable: Phone number of the caller/customer
  - `customer_name` string, nullable: Name of the caller/customer if available
  - `ring_duration_seconds` number, nullable: Time spent ringing before call was answered (seconds)
  - `call_direction` 'incoming' | 'outgoing' | 'internal': Direction of the call. - INCOMING: Customer called agent - OUTGOING: Agent called customer - INTERNAL: Extension-to-extension call
  - `audio_chunks_received` integer, nullable: Number of audio chunks received (0 = no audio captured)
  - `audio_file_path` string, nullable: S3 path to the call recording, when one is archived
  - `started_at` string, date-time, required
  - `ended_at` string, date-time, nullable, required
  - `duration_seconds` integer, nullable, required
  - `is_starred` boolean: Whether this call has been saved for later review
  - `transcript` object[]: Transcript entries (populated from call_transcript_chunks table)
  - `tool_events` object[]: Terminal tool-call events from the autonomous voice agent (persisted tool_call session events), in call order. Payload shape matches the SSE tool_call channel; interleaved into the transcript view behind a toggle.
  - `summary_markdown` string, nullable, required
  - `summary` object, nullable: Structured call summary (new format)
  - `coaching` object, nullable, required
  - `qa_score` integer, nullable: Overall QA scorecard score (mirror of analyzer_artifacts['qa_scorecard']['overall_score']).
  - `tags` CallTagsPayload: Structured detected tags persisted on a call.
    - `tags` CallTagItem[]: Detected tags for this call
      - `key` string, required: Machine-safe tag identifier
      - `evidence` string[]: Short transcript snippets supporting the detected tag
  - `call_outcome` CallOutcome: Canonical call-outcome record, adding service-derived total fields.
    - `commitment_shape` 'new_arrangement' | 'immediate_payment' | 'settlement' | 'arrangement_repair' | 'promise_reset' | 'deferral' | 'refused' | 'other', required: Taxonomy of commitment outcomes extracted from a collections call.
    - `commitment_horizon` 'immediate' | 'within_7d' | 'within_30d' | 'beyond_30d' | 'indefinite': When the committed money is expected to land.
    - `is_new_commitment` boolean, required: True when this call produced new money intent. False for arrangement_repair and all non-monetary shapes.
    - `installment_amount` number, nullable: Per-payment amount in major currency units (e.g. 40.00 for $40). For one-off commitments, equals the one-off amount. Null when no amount is stated.
    - `first_payment_amount` number, nullable: Down payment / first payment amount taken on the call, SEPARATE from the rest of the money agreed on the same call. Example: '$1000 today, then $100 every fortnight' → first_payment_amount=1000, installment_amount=100. Also covers a split lump sum ('half now, half next week' → first_payment = today's half, installment = the remaining one_off payment). Null when the call has no immediate payment component. For a standalone immediate payment (with nothing to follow), use shape=immediate_payment and installment_amount — do NOT use this field.
    - `payment_frequency` 'one_off' | 'weekly' | 'fortnightly' | 'monthly': Cadence of a scheduled commitment.
    - `installment_count` integer, nullable: Exact number of installment_amount payments (NOT counting any first_payment_amount), ONLY when explicitly stated in the transcript. Null for vague durations ('a few months', 'ongoing').
    - `explicit_total_amount` number, nullable: Total commitment value in major currency units, ONLY when the transcript states the total directly. DO NOT compute totals from installment × count — the backend handles that derivation.
    - `confidence` number, required: Classifier's self-reported confidence in this extraction
    - `evidence_quote` string, required: Short transcript excerpt that supports the classification
    - `total_commitment_amount` number, nullable: Total commitment in major currency units. Either copied from `explicit_total_amount` when present, or derived as `installment_amount × installment_count` when both are known. Null when neither is available (open-ended plan, truncated transcript, or non-monetary shape).
    - `total_commitment_source` 'explicit' | 'derived' | 'not_available': Provenance of `total_commitment_amount`.
  - `client` string, nullable: End-client the call collected for, classified against the scenario's configured client list ('Other' for an explicitly named unconfigured creditor; 'Unclear' for insufficient or ambiguous context; null when no classification result was produced). Full result with disposition, confidence, and evidence in analyzer_artifacts['client_classification'].
  - `client_id` string, uuid, nullable: Stable client identity for the display-name snapshot
  - `client_summary` ClientSummary: Read-safe client context embedded in call detail responses.
    - `id` string, uuid, required
    - `name` string, required
    - `description` string, nullable
    - `has_guidelines` boolean
  - `crm_link` CrmCallLinkRead
    - `id` string, uuid, required
    - `provider` string, required
    - `decision` 'linked' | 'none_of_these', required
    - `account_id` string, nullable
    - `account_row_id` string, uuid, nullable
    - `account_display_name` string, nullable
    - `account_number` string, nullable
    - `customer_id` string, nullable
    - `client_id` string, nullable
    - `matched_phone` string, nullable
    - `matched_on` string, nullable
    - `linked_by_user_id` string, uuid, nullable
    - `linked_at` string, date-time, required
    - `identity` CrmIdentityReferenceRead: Minimal linked-account identity facts needed by IDV cards.
      - `full_name` string, nullable
      - `phones` string[]
      - `address` string, nullable
      - `dob` string, date, nullable
      - `email` string, nullable
      - `ssn_last4` string, nullable
      - `postal_code` string, nullable
      - `account_number` string, nullable
      - `customer_id` string, nullable
    - `context` AccountContextRead: Wire shape for domain-shared account context (debt-collection state). Mirrors `src.crm.base.AccountContext`. Every field is optional because providers expose different subsets, and within a single provider some fields are only present when the underlying record has them set.
      - `product_name` string, nullable
      - `account_status` string, nullable
      - `account_status_disposition` 'workable' | 'not_workable' | 'resolved' | 'unknown': Provider-neutral meaning of a CRM's native account status. Providers keep their raw status in ``AccountContext.account_status`` for display and audit, then map it onto this small contract for autonomous outbound policy. Adding another CRM means adding its native-code mapping in that provider; the compliance gate never needs vendor status strings.
      - `collection_stage` 'open' | 'arrangement' | 'broken_arrangement' | 'paid' | 'settled' | 'excluded' | 'unknown': Provider-neutral collections-funnel position of a CRM's native status. Orthogonal to :class:`AccountStatusDisposition`, which answers "may the autonomous scheduler dial this account". The stage answers "where does this debt sit in the collections funnel" for reporting: an arrangement in place and a broken arrangement are both not-workable, but they are opposite outcomes on a performance dashboard. ``EXCLUDED`` marks accounts whose balance can move without money being collected (disputed, deceased, bankrupt, statute-barred), so recovery metrics can leave them out rather than count adjustments as payments.
      - `arrears_amount` number, nullable
      - `arrears_days` integer, nullable
      - `outstanding_amount` number, nullable
      - `original_balance` number, nullable
      - `amount_paid` number, nullable
      - `last_payment_date` string, date, nullable
      - `principal_outstanding` number, nullable
      - `referred_date` string, date, nullable
      - `interest_overdue` number, nullable
      - `interest_rate` number, nullable
      - `late_fees` number, nullable
      - `dishonour_fees` number, nullable
      - `settled_date` string, date, nullable
      - `cycle_day` integer, nullable
      - `repayment_amount` number, nullable
      - `repayment_frequency` string, nullable
      - `next_installment_amount` number, nullable
      - `next_installment_date` string, date, nullable
      - `has_hardship` boolean, nullable
      - `hardship_requested` boolean, nullable
      - `hardship_status` string, nullable
      - `hardship_notice_date` string, date, nullable
      - `active_arrangement_summary` string, nullable
      - `active_arrangement_status` string, nullable
      - `active_arrangement_next_amount` number, nullable
      - `active_arrangement_next_date` string, date, nullable
      - `collection_statuses` string[]
      - `risk_segmentation` string, nullable
      - `num_active_loans` integer, nullable
      - `num_broken_arrangements_last_6_months` integer, nullable
      - `num_dishonours_last_6_months` integer, nullable
      - `num_contacts_last_30_days` integer, nullable
      - `third_party_authority` boolean, nullable
      - `is_staff_account` boolean, nullable
      - `is_outsourced` boolean, nullable
      - `outsource_partner` string, nullable
      - `is_written_off` boolean, nullable
      - `joint_parties` JointPartyRead[]
        - `customer_id` string, nullable
        - `display_name` string, nullable
        - `relationship` string, nullable
      - `recent_transactions` LedgerEntryRead[]
        - `occurred_on` string, date, nullable
        - `description` string, nullable
        - `amount` number, nullable
        - `balance_after` number, nullable
        - `kind` string, nullable
      - `recent_contacts` ContactEventRead[]
        - `occurred_on` string, date, nullable
        - `description` string, nullable
        - `channel` string, nullable
        - `completed` boolean, nullable
        - `done_date` string, date, nullable
      - `recent_successful_contact_dates` string[]: Provider-classified successful contacts for outbound frequency caps; duplicate dates represent separate contacts.
      - `days_past_section_88` integer, nullable
      - `days_past_information_request` integer, nullable
      - `days_past_six_q` integer, nullable
  - `tts_voice_id` string, nullable: Provider-qualified voice the autonomous agent spoke with, stamped at dispatch from the resolved fallback chain — not re-derived from the scenario, which may have been re-voiced since. Null on human calls. Resolve the display name against the voice catalog (GET /ai-calls/voices).
  - `tts_model_id` string, nullable: Provider-qualified TTS model the autonomous agent used, stamped at dispatch rather than re-derived from mutable scenario config.
  - `analyzer_artifacts` object, required
  - `post_call_notes` string, nullable, required
  - `status` 'ongoing' | 'completed' | 'unanswered' | 'error': Call lifecycle state. - ONGOING: Call is currently in progress - COMPLETED: Call finished (regardless of whether it was answered) - ERROR: Technical error/exception during call processing - UNANSWERED: DEPRECATED - Use status=COMPLETED + telephony_result=NO_ANSWER instead. Kept for backwards compatibility with existing database records.
  - `telephony_result` 'connected' | 'busy' | 'no_answer' | 'rejected' | 'failed': Telephony-level call connection result. Represents what happened at the telephony level before any conversation. This is typically provided by the PBX/telephony system, not AI-classified.
  - `answered_by` 'human' | 'voicemail' | 'unclear': Who answered the call (AI-classified from transcript). This is the first stage of call classification - determining whether a human or voicemail answered the call.
  - `conversation_outcome` 'engaged' | 'refused' | 'wrong_person' | 'other': AI-classified judgement of what kind of conversation occurred. Only applicable when answered_by=HUMAN. Runtime actions such as scheduling a callback or transferring the caller are recorded independently in ``WorkflowDisposition`` and must never overwrite this judgement.
  - `workflow_disposition` 'none' | 'callback_scheduled' | 'transferred' | 'human_followup_required' | 'do_not_contact': Deterministic action the autonomous workflow took before finalizing. ``NONE`` is explicit for finalized autonomous calls with no special handoff/follow-up action. NULL means the dimension does not apply (human copilot, uploaded, or legacy calls), rather than overloading NULL with two meanings.
  - `agent_end_reason` 'conversation_complete' | 'existing_arrangement_claimed' | 'customer_requested_human' | 'identity_verification_failed' | 'customer_declined_to_engage' | 'customer_unavailable' | 'wrong_person' | 'do_not_contact' | 'hardship_detected' | 'dispute_raised' | 'escalation' | 'voicemail' | 'no_answer' | 'caller_hung_up' | 'caller_abandoned' | 'answer_failed' | 'answer_timeout' | 'dial_failed' | 'transfer_failed' | 'screening_timeout' | 'idle_timeout' | 'max_duration' | 'api_hangup' | 'error' | 'other': Normalized reason an autonomous call ended. The raw runtime string remains in ``analyzer_artifacts`` for diagnostics. This enum is the stable operational projection: semantic reasons exposed to the voice model plus bounded technical termination categories. Novel runtime endings degrade to ``OTHER`` instead of failing finalization.
  - `classification_final` boolean: Whether the call's terminal classification has been published (outcome_ready_at is set). The exact precondition for correcting answered_by / conversation_outcome — a call can be 'completed' while post-call facts are still finalizing, so clients must gate the correction UI on this rather than on status.
  - `counted_as` CallCountedAs: A call's classification and the stored fact it rests on.
    - `classification` 'contact' | 'contact_unknown' | 'attempt' | 'in_progress' | 'not_counted', required: What a call counts as for the frequency caps.
    - `basis` string, required: The stored fact behind the classification, in a few words.
  - `detected_scenario` string, nullable: AI-detected scenario from transcript content
  - `idv_outcome` 'verified' | 'partial' | 'incomplete': Identity-verification outcome for a call, denormalized from the IDV analyzer. Only set when IDV was required and a verdict was produced. A NULL column can mean IDV was not applicable, is still pending, or no verdict was available; API consumers use ``idv_required`` plus call status to distinguish those states. Derived from the terminal `identity_verification_state["status"]`: - VERIFIED: status `complete` — identity confirmed per the scenario's policy. - PARTIAL: at least one identity factor was confirmed, but the scenario's completion rule was never satisfied. - INCOMPLETE: required, and not a single factor was confirmed. PARTIAL exists because "not verified" covers two operationally different calls: one where the customer engaged with verification and it ran out of road, and one where it never started. Only the second is a candidate for "the agent didn't try". Note this is *tracking* state, not an adherence grade — the leadership IDV rate deliberately reads the reviewed QA scorecard instead (see `shared.idv`), because a human reviewer can correct it and the analyzer cannot. There is deliberately no `skipped` outcome. The live state machine still has a `skipped` terminal so the analyzer stops firing on a call that is not going to verify, but as a *result* it said nothing true that `incomplete` doesn't: verification was required and did not happen. It also read as a third, ambiguous colour on any report — neither pass nor fail — while the thing a reviewer actually needs to know (was skipping appropriate here?) is a transcript judgement the tracker never made.
  - `idv_source` 'live' | 'post_call' | 'inherited': Which mechanism produced the call's persisted IDV state. - LIVE: the in-call IDV analyzer's terminal state (the normal path). - POST_CALL: the post-call IDV judge — runs when the live analyzer never produced a state (e.g. no scenario reached the session) or the scenario was reclassified after the call, judging the full transcript against the resolved scenario's IDV rules. - INHERITED: the origin leg of a transfer verified the customer, and this recipient leg inherits that verdict — verification travels with the conversation, not the leg (see calls.idv_resolution). The verification exchange is on the origin leg's transcript, not this one's.
  - `idv_required` boolean, nullable: Whether the call's resolved scenario requires identity verification. NULL when the scenario record is unavailable.
  - `recording_disclosed` boolean, nullable: True when the IDV tracker positively captured the recording disclosure; otherwise NULL.
  - `scenario_id` string, uuid, nullable: FK to configured scenario
  - `was_transferred` boolean: Whether this call was transferred to/from another call
  - `transferred_from_call_id` string, uuid, nullable: If transferred, the call session this was transferred FROM
  - `transferred_to_call_id` string, uuid, nullable: If transferred, the call session this was transferred TO
  - `dashboard_open_percentage` number, nullable: Percentage of call duration with dashboard open (0-100)
  - `latency_summary_json` CallLatencySummary: Typed admin-only latency payload stored on a completed call.
    - `vaqi_l` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
      - `p50_ms` integer, nullable, required
      - `p95_ms` integer, nullable, required
      - `worst_ms` integer, nullable, required
      - `n` integer, required
    - `legs` SuggestedResponseLatencyLegs, required
      - `stt` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
        - `p50_ms` integer, nullable, required
        - `p95_ms` integer, nullable, required
        - `worst_ms` integer, nullable, required
        - `n` integer, required
      - `commit` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
        - `p50_ms` integer, nullable, required
        - `p95_ms` integer, nullable, required
        - `worst_ms` integer, nullable, required
        - `n` integer, required
      - `ttft` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
        - `p50_ms` integer, nullable, required
        - `p95_ms` integer, nullable, required
        - `worst_ms` integer, nullable, required
        - `n` integer, required
      - `delivery` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
        - `p50_ms` integer, nullable, required
        - `p95_ms` integer, nullable, required
        - `worst_ms` integer, nullable, required
        - `n` integer, required
    - `autonomous_voice` AutonomousVoiceLatencySummary
      - `response_total` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
        - `p50_ms` integer, nullable, required
        - `p95_ms` integer, nullable, required
        - `worst_ms` integer, nullable, required
        - `n` integer, required
      - `legs` SrcCallsSchemasAutonomousVoiceLatencyLegs, required: Stages of one spoken reply, in order; on a turn they sum to its total. The stages added after the first calls were recorded are optional, so the summaries stored on those calls still load.
        - `endpointing` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
          - `p50_ms` integer, nullable, required
          - `p95_ms` integer, nullable, required
          - `worst_ms` integer, nullable, required
          - `n` integer, required
        - `turn_release` LatencyLegSummary: Percentiles for one latency leg and its contributing sample count.
          - `p50_ms` integer, nullable, required
          - `p95_ms` integer, nullable, required
          - `worst_ms` integer, nullable, required
          - `n` integer, required
        - `llm_ttft` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
          - `p50_ms` integer, nullable, required
          - `p95_ms` integer, nullable, required
          - `worst_ms` integer, nullable, required
          - `n` integer, required
        - `tool` LatencyLegSummary: Percentiles for one latency leg and its contributing sample count.
          - `p50_ms` integer, nullable, required
          - `p95_ms` integer, nullable, required
          - `worst_ms` integer, nullable, required
          - `n` integer, required
        - `speech_release` LatencyLegSummary: Percentiles for one latency leg and its contributing sample count.
          - `p50_ms` integer, nullable, required
          - `p95_ms` integer, nullable, required
          - `worst_ms` integer, nullable, required
          - `n` integer, required
        - `tts_ttfb` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
          - `p50_ms` integer, nullable, required
          - `p95_ms` integer, nullable, required
          - `worst_ms` integer, nullable, required
          - `n` integer, required
        - `transport` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
          - `p50_ms` integer, nullable, required
          - `p95_ms` integer, nullable, required
          - `worst_ms` integer, nullable, required
          - `n` integer, required
      - `turns` AutonomousVoiceTurnCounts, required
        - `total` integer, required
        - `complete` integer, required
        - `incomplete` integer, required
      - `by_kind` SrcCallsSchemasAutonomousVoiceLatencyByKind: End-to-end response time split by what answered the caller.
        - `plain` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
          - `p50_ms` integer, nullable, required
          - `p95_ms` integer, nullable, required
          - `worst_ms` integer, nullable, required
          - `n` integer, required
        - `tool` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
          - `p50_ms` integer, nullable, required
          - `p95_ms` integer, nullable, required
          - `worst_ms` integer, nullable, required
          - `n` integer, required
        - `runtime` LatencyLegSummary, required: Percentiles for one latency leg and its contributing sample count.
          - `p50_ms` integer, nullable, required
          - `p95_ms` integer, nullable, required
          - `worst_ms` integer, nullable, required
          - `n` integer, required
      - `over_target` AutonomousVoiceOverTarget
        - `target_ms` integer, required
        - `replies` integer, required
        - `over` integer, required
      - `first_speech_ms` integer, nullable
      - `tool_handler` LatencyLegSummary: Percentiles for one latency leg and its contributing sample count.
        - `p50_ms` integer, nullable, required
        - `p95_ms` integer, nullable, required
        - `worst_ms` integer, nullable, required
        - `n` integer, required
      - `llm_thinking` LatencyLegSummary: Percentiles for one latency leg and its contributing sample count.
        - `p50_ms` integer, nullable, required
        - `p95_ms` integer, nullable, required
        - `worst_ms` integer, nullable, required
        - `n` integer, required
      - `llm_tokens_per_second` number, nullable
      - `unanswered_wait` LatencyLegSummary: Percentiles for one latency leg and its contributing sample count.
        - `p50_ms` integer, nullable, required
        - `p95_ms` integer, nullable, required
        - `worst_ms` integer, nullable, required
        - `n` integer, required
      - `barge_in_stop` LatencyLegSummary: Percentiles for one latency leg and its contributing sample count.
        - `p50_ms` integer, nullable, required
        - `p95_ms` integer, nullable, required
        - `worst_ms` integer, nullable, required
        - `n` integer, required
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/whispa/apis/whispa-api.md) - [All operations](https://skmtc.dev/whispa/apis/whispa-api/llms.txt) - [OpenAPI document](https://skmtc.dev/whispa/apis/whispa-api/revisions/12c2e4aca43c?raw)
