---
title: "List Calls"
method: GET
path: "/calls"
tags: ["calls"]
---

# List Calls

`GET /calls`

## 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`
- Auth: one of:
  - OAuth 2
    - Password
    - Token URL: `auth/bearer/login`
  - API key in cookie `access_token`

List calls with filtering and pagination.

Agents see their own calls, team leads see calls snapshotted to their team,
and admins see all calls.

## Query parameters

- `scenario` string[], nullable: Filter by scenario (multiple allowed)
- `date_from` string, date-time, nullable: Filter calls from this date
- `date_to` string, date-time, nullable: Filter calls to this date
- `agent_id` string, nullable: Filter by agent ID
- `min_score` number, nullable: Minimum qa_score (inclusive)
- `max_score` number, nullable: Maximum qa_score (inclusive)
- `call_source` string[], nullable: Filter by call source (multiple allowed)
- `status` string[], nullable: Filter by call status (multiple allowed)
- `call_mode` string[], nullable: Filter by call mode (multiple allowed)
- `call_direction` string[], nullable: Filter by call direction (multiple allowed)
- `user_id` string[], nullable: Filter by user IDs (multiple allowed)
- `team_id` string, uuid, nullable: Team filter; team leads are fixed to their assigned team
- `telephony_result` string[], nullable: Filter by telephony result (multiple allowed)
- `answered_by` string[], nullable: Filter by answered_by classification (multiple allowed)
- `conversation_outcome` string[], nullable: Filter by conversation outcome (multiple allowed)
- `idv_status` string[], nullable: Filter by IDV status: verified, partial, incomplete, or not_required (multiple allowed)
- `workflow_disposition` string[], nullable: Filter by autonomous workflow action (multiple allowed)
- `agent_end_reason` string[], nullable: Filter by normalized autonomous ending reason (multiple allowed)
- `coaching_feedback_rating` string[], nullable: Filter by coaching feedback rating (multiple allowed)
- `has_transfer_in` boolean, nullable: Filter calls that received a transfer (transferred_from_call_id IS NOT NULL)
- `has_transfer_out` boolean, nullable: Filter calls that were transferred out (transferred_to_call_id IS NOT NULL)
- `has_dashboard_open` boolean, nullable: Filter calls where the dashboard was open during the call (dashboard_open_duration_ms > 0)
- `crm_linked` boolean, nullable: Filter by actual CRM account link. True requires decision=linked; false includes calls with no link or a none_of_these decision.
- `is_starred` boolean, nullable: Filter by whether calls are saved for later review
- `customer_number` string, nullable: Filter by customer phone number (partial match)
- `tag_key` string[], nullable: Filter by detected call tag keys (multiple allowed)
- `client` string[], nullable: Filter by classified client (multiple allowed)
- `campaign_id` string[], nullable: Filter to calls placed by these outbound campaigns (multiple allowed). Manual dials and callback rows match no campaign.
- `sort_by` string, nullable: Column to sort by: started_at, qa_score, duration_seconds
- `sort_order` string, nullable: Sort direction: asc or desc
- `page` integer
- `size` integer

## Response `200`

Successful Response

- PageCallListItem
  - `items` CallListItem[], required
    - `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
    - `started_at` string, date-time, required
    - `ended_at` string, date-time, nullable, required
    - `duration_seconds` integer, nullable, required
    - `qa_score` integer, nullable: Overall QA scorecard score. Mirrors analyzer_artifacts['qa_scorecard']['overall_score'].
    - `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.
    - `is_starred` boolean: Whether this call has been saved for later review
    - `call_direction` 'incoming' | 'outgoing' | 'internal': Direction of the call. - INCOMING: Customer called agent - OUTGOING: Agent called customer - INTERNAL: Extension-to-extension call
    - `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.
    - `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, so clients must not guess that a missing outcome means IDV was not required.
    - `recording_disclosed` boolean, nullable: True when the IDV tracker positively captured the recording disclosure. Never False — an absent disclosure may simply have been missed, so the negative is not asserted.
    - `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
    - `customer_number` string, nullable: Phone number of the caller/customer
    - `coaching_feedback_rating` 'positive' | 'negative': User rating for AI-generated coaching feedback.
    - `coaching_feedback_comment` string, nullable: Optional user comment attached to coaching feedback
    - `tags` CallTagsListPayload: Lightweight detected tags persisted on a call for list views.
      - `tags` CallTagListItem[]: Detected tag keys for this call
        - `key` string, required: Machine-safe tag identifier
    - `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)
    - `client_id` string, uuid, nullable: Stable client identity for the display-name snapshot
    - `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. 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. Null on human calls and older autonomous calls.
  - `total` integer, required
  - `page` integer, required
  - `size` integer, required
  - `pages` integer, 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)
