---
title: "Get Transfer Metrics"
method: GET
path: "/developer/transfer-metrics"
tags: ["Developer", "Developer - Transfer Metrics"]
---

# Get Transfer Metrics

`GET /developer/transfer-metrics`

## Request

- Base URL: `https://api.openintake.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.openintake.ai/developer/transfer-metrics`
- Auth: none declared

Team-level transfer metrics, bucketed in the customer's local timezone.

Teams are TransferDestination rows matched by the number the call was
transferred to; teams sort by pickup rate (best first). Pickup/missed
rates are FIRST-QUEUE rates from transfer_outcome plus the transfer_flow
snapshot: a call answered only after rolling to a downstream queue counts
as missed by the team it was sent to. Staff-transfer counts, queue waits,
and per-extension answer counts come from the flow snapshot (coverage
reported, since older calls may lack one). Trends compare against the
immediately preceding period of equal length.

period=custom takes start_date/end_date (inclusive LOCAL days, capped at
365 days apart) for any window, including ones that ended in the past.

## Query parameters

- `customer_id` string, uuid, nullable: Customer whose transfers to analyze (developers only; admins always get their own)
- `period` string
- `start_date` string, date, nullable: First day of a custom window (period=custom only); a LOCAL day in the customer's timezone, inclusive
- `end_date` string, date, nullable: Last day of a custom window (period=custom only); a LOCAL day in the customer's timezone, inclusive
- `bucket` string, nullable: Override bucket granularity (ignored for single-day windows)

## Response `200`

Successful Response

- TransferMetricsResponse
  - `period` string, required
  - `bucket` string, required
  - `customer_id` string, uuid, required
  - `timezone` string, required
  - `ai_agent_name` string: What this firm calls its AI receptionist (customer.ai_agent_name), or the generic name
  - `window_start` string, date, required: First local day in the window (inclusive), resolved in the customer's timezone
  - `window_end` string, date, required: Last local day in the window (inclusive), resolved in the customer's timezone
  - `summary` TransferMetricsSummary, required: Whole-window aggregates with prior-period comparison. Pickup/missed are first-queue rates (downstream answers count as missed).
    - `total_transferred` integer, required
    - `answered` integer, required
    - `assumed_answered` integer, required
    - `bounced` integer, required
    - `no_answer` integer: Finalized PBX chain proves nobody picked up; counted as missed
    - `outcome_unknown` integer, required
    - `downstream_answered` integer: Answered only after rolling past the entry queue — counted as missed by the first queue
    - `pickup_rate_pct` number, nullable
    - `missed_rate_pct` number, nullable
    - `transfer_hops` integer: Staff transfers after answering (queue rolls excluded) across flow-snapshot calls
    - `calls_with_transfer` integer: Flow-snapshot calls with at least one staff transfer
    - `transfer_rate_pct` number, nullable: Share of flow-snapshot calls with at least one staff transfer. A rate, not a per-call mean: staff transfers are rare enough (single digits over thousands of calls) that a mean rounds to 0.0 and reads as broken.
    - `max_transfer_hops` integer: Most staff transfers on any one call
    - `total_duration_seconds` integer: Sum of whole-call durations, end to end (calls with a recorded duration)
    - `total_staff_talk_seconds` integer: Sum of staff talk time on the answering leg across flow-snapshot calls
    - `total_ai_talk_seconds` integer: The AI's conversation on the transferred calls (their duration_seconds, which is the AI leg alone). The AI's full talk time, including calls it never transferred, is `TransferMetricsResponse.talk_time.ai_seconds`.
    - `avg_duration_seconds` number, nullable
    - `avg_queue_wait_seconds` number, nullable: Mean landing-to-answer wait, answered flow-snapshot calls only
    - `avg_staff_talk_seconds` number, nullable: Mean staff talk time on the answering leg, answered flow-snapshot calls only
    - `avg_answer_ring_seconds` number, nullable: Mean seconds the answerer's phone rang before pickup; schema-v2 snapshots only (older captures did not record the PBX ring time)
    - `flow_calls` integer, required: Calls with a transfer_flow snapshot (hop/wait metrics denominator)
    - `flow_coverage_pct` number, nullable
    - `prior_total_transferred` integer
    - `prior_pickup_rate_pct` number, nullable
    - `prior_missed_rate_pct` number, nullable
    - `prior_transfer_rate_pct` number, nullable
    - `pickup_rate_delta` number, nullable: Percentage-point change vs prior period
    - `missed_rate_delta` number, nullable
    - `transfer_rate_delta` number, nullable
  - `teams` TransferTeamMetrics[]: Sorted by missed rate, worst first
    - `transfer_destination_id` string, uuid, nullable: Null for numbers with no matching TransferDestination
    - `name` string, required
    - `phone_number` string, nullable
    - `total` integer, required
    - `answered` integer, required
    - `assumed_answered` integer, required
    - `bounced` integer, required
    - `no_answer` integer: Finalized PBX chain proves nobody picked up (parked and abandoned, or rang out); counted as missed
    - `outcome_unknown` integer, required: Transferred before outcome gating existed; excluded from rate denominators
    - `downstream_answered` integer: Answered, but only after rolling past the entry queue (missed by the first queue)
    - `pickup_rate_pct` number, nullable: Answered by the FIRST destination queue / resolved
    - `missed_rate_pct` number, nullable: Bounced or answered downstream / resolved
    - `transfer_hops` integer: Staff transfers after answering (queue rolls excluded) across this team's flow-snapshot calls
    - `calls_with_transfer` integer: Flow-snapshot calls with at least one staff transfer
    - `transfer_rate_pct` number, nullable: Share of this team's flow-snapshot calls with at least one staff transfer
    - `avg_duration_seconds` number, nullable: Mean whole-call duration end to end — AI conversation, queue wait and staff talk combined (calls with a recorded duration)
    - `avg_queue_wait_seconds` number, nullable: Mean landing-to-answer wait, answered flow-snapshot calls only
    - `intake_calls` integer: Of `total`, how many produced a lead. A second dimension, not a re-attribution: a team can handle intake calls without being the intake team.
    - `intake_rate_pct` number, nullable
    - `extensions` TransferExtensionAnswers[]
      - `agent_extension` string, required
      - `agent_name` string, nullable
      - `answered_count` integer, required
      - `via_queue` string, nullable: Set when this extension answered in a DOWNSTREAM queue (not the team's own) — e.g. a receptionist catching a rolled call. Queue name, or extension if unnamed.
  - `handled_by` HandledByTeam[]: Where calls were actually HANDLED (credited teams, most talk time first) — the complement of `teams`, which is where they were SENT. Empty until the customer's PBX queues are configured.
    - `queue_extension` string, nullable: PBX queue extension credited, null only for legs with neither a known agent nor a queue
    - `name` string, required
    - `transfer_destination_id` string, uuid, nullable: The app-side team, when the queue has been mapped to one on the NetSapiens developer page. Enables drill-through to the Calls page.
    - `calls` integer, required: Distinct calls this team answered at least one leg of
    - `sent_here_calls` integer: Of `calls`, how many the AI actually dialed to this team's own transfer destination (alias-resolved, so an after-hours alias queue's calls count for the team it folds into). The rest arrived by rollover, staff hand-off or pickup from hold.
    - `answered_legs` integer, required: Answered legs credited to this team; more than `calls` when a team spoke to one caller twice
    - `talk_seconds` integer: Summed real staff talk time (agent-side clock, so it excludes queue wait)
    - `talk_share_pct` number, nullable: This team's share of all credited talk time in the window
    - `avg_talk_seconds` number, nullable
    - `from_park_legs` integer: Legs credited here that were park retrievals — the ones a queue-only view would have attributed elsewhere
    - `credited_from_home_team_legs` integer: Legs credited via the answering agent's home team rather than the leg's own queue. The rest fell back to the queue, meaning the agent's team was unknown.
    - `intake_calls` integer: Of `calls`, how many produced a lead (an intake call)
    - `intake_rate_pct` number, nullable
    - `extensions` TransferExtensionAnswers[]: Staff who handled calls for this team; distinct calls per person
      - `agent_extension` string, required
      - `agent_name` string, nullable
      - `answered_count` integer, required
      - `via_queue` string, nullable: Set when this extension answered in a DOWNSTREAM queue (not the team's own) — e.g. a receptionist catching a rolled call. Queue name, or extension if unnamed.
    - `sources` HandledBySource[]: Arrival breakdown: how this team's calls got here, by kind, with the upstream person where there is one. Queue rolls and park pickups ARE included (they are two of the four kinds). Per-kind totals can exceed `calls` when one call arrived twice by different routes.
      - `kind` string: How the call arrived: ai_direct (the AI dialed this team and it answered) | ai_rolled (the AI dialed elsewhere and the call rolled or overflowed here) | staff (another person handed it over) | park (picked back up off hold)
      - `agent_extension` string, nullable
      - `agent_name` string, nullable
      - `queue_extension` string, nullable
      - `queue_name` string, nullable
      - `is_ai` boolean: Derived/back-compat: true for the two AI kinds (no upstream person). Prefer `kind`.
      - `calls` integer, required: Distinct calls that arrived this way
  - `intake` IntakeSummary: Intake volume in the window, on its own denominator. Deliberately NOT a row in the team tables: a lead can be created on a call no team ever touched (the AI handled it end to end), so intake's denominator is larger than the transferred-call table's. It is a second dimension — what KIND of call it was — crossed with the team tables' question of who handled it.
    - `total_intake_calls` integer, required: Calls in the window that produced a lead
    - `transferred_intake_calls` integer: Of those, ones that reached a human
    - `ai_only_intake_calls` integer: Of those, ones the AI handled with no transfer
    - `qualified_intake_calls` integer: Of those, leads marked qualified against a case's criteria
    - `intake_share_of_calls_pct` number, nullable: Intake calls as a share of ALL calls in the window, transferred or not
  - `answerers` OverallAnswerer[]: Individual answerers across all teams, most answers first; AI bounce-backs included
    - `agent_extension` string, nullable: Null for the AI receptionist row
    - `agent_name` string, nullable
    - `answered_count` integer, required
    - `is_ai` boolean
    - `breakdown` AnswererBreakdownItem[]: Per-queue (staff) or per-intended-team (AI) counts, largest first
      - `label` string, required
      - `count` integer, required
  - `buckets` TransferMetricsBucket[]
    - `bucket_start` string, date-time, required: Naive customer-local bucket start
    - `total` integer, required
    - `answered` integer, required
    - `assumed_answered` integer, required
    - `bounced` integer, required
    - `no_answer` integer: Finalized PBX chain proves nobody picked up; counted as missed
    - `downstream_answered` integer
    - `pickup_rate_pct` number, nullable
    - `missed_rate_pct` number, nullable
    - `avg_duration_seconds` number, nullable
    - `transfer_rate_pct` number, nullable
  - `talk_time` TalkTimeBreakdown: Every second of conversation in the window, by who was talking. The parts are disjoint and sum to `total_seconds` exactly, so the page can show the total with its sources side by side and the table's grand total can reconcile against it.
    - `total_seconds` integer
    - `staff_inbound_seconds` integer: Staff talk on calls the AI transferred (credited legs, agent-side clock, so queue wait is excluded)
    - `staff_outbound_seconds` integer: Staff talk on calls staff placed from their own extension (PBX CDR mirror, answered legs only)
    - `ai_seconds` integer: All AI receptionist talk: the three ai_* parts below
    - `ai_before_transfer_seconds` integer: The AI's conversation on calls it went on to transfer
    - `ai_only_seconds` integer: Calls the AI handled end to end, never transferred
    - `ai_bounce_back_seconds` integer: The AI's second conversation on calls that bounced back after nobody picked up (ring-out time excluded)
    - `outbound_available` boolean: False when the customer has no PBX mirror to read staff outbound calls from; staff_outbound_seconds is then 0 by absence, not measurement
  - `inbound_calls` InboundCallBreakdown: Every inbound call the AI answered in the window, by how it ended. The parts sum to `total_calls`.
    - `total_calls` integer
    - `ai_only` integer: Handled by the AI alone, never transferred
    - `transferred_answered` integer: Transferred and picked up by staff (includes assumed answers)
    - `transferred_bounced` integer: Transferred, nobody picked up, the AI took the caller back
    - `transferred_no_answer` integer: Transferred, nobody picked up, the caller was not taken back
    - `transferred_unknown` integer: Transferred, outcome not yet known
  - `workload` WorkloadTeam[]: Team roster with inbound, outbound and total talk per person, the AI receptionist as a member of every team it took calls for. Most total talk first; the no-team group last.
    - `queue_extension` string, nullable: The credited PBX queue; null for the no-team group
    - `name` string, required
    - `transfer_destination_id` string, uuid, nullable
    - `is_unassigned` boolean: The catch-all group: staff with no home team, and AI calls with no team
    - `inbound_calls` integer
    - `inbound_talk_seconds` integer
    - `outbound_calls` integer
    - `outbound_talk_seconds` integer
    - `total_talk_seconds` integer
    - `talk_share_pct` number, nullable: Share of the table's total talk time
    - `members` WorkloadMember[]: Most talk time first; zero rows included; the AI row, when present, last
      - `agent_extension` string, nullable
      - `agent_name` string, nullable
      - `is_ai` boolean
      - `on_roster` boolean: True when this row is the person's own placement: their home team, or the no-team group for a roster person without one. False for someone credited here while placed elsewhere (e.g. covering another team's queue), or an extension the roster doesn't know; outbound calls always stay with the person's placement.
      - `inbound_calls` integer: Distinct inbound calls this person handled for this team. For the AI row, calls it fully handled plus bounce-backs.
      - `inbound_talk_seconds` integer
      - `outbound_calls` integer
      - `outbound_talk_seconds` integer
      - `total_talk_seconds` integer
      - `ai_only_calls` integer: AI row only: calls the AI handled end to end whose matched case belongs to this team
      - `bounce_back_calls` integer: AI row only: calls sent to this team that bounced back to the AI
  - `workload_totals` WorkloadTotals: Column totals for the workload table, plus the AI talk no team row can own (its conversation BEFORE a transfer, and calls it handled alone for callers with no case on a team), so `grand_total_seconds` equals the talk-time card's total.
    - `inbound_calls` integer
    - `inbound_talk_seconds` integer
    - `outbound_calls` integer
    - `outbound_talk_seconds` integer
    - `total_talk_seconds` integer
    - `ai_before_transfer_seconds` integer
    - `ai_no_team_calls` integer: AI calls with no team: handled alone for a caller with no case on a team (or bounced from an unmapped number)
    - `ai_no_team_seconds` integer
    - `grand_total_seconds` integer

## Other responses

- `422`: Validation Error

## Changes

- **2026-09-26** `7296f576bb94`: 5 info
  - added the optional property `ai_agent_name` to the response with the `200` status
  - added the optional property `inbound_calls` to the response with the `200` status
  - added the optional property `talk_time` to the response with the `200` status
  - added the optional property `workload` to the response with the `200` status
  - …1 more
- **2026-09-18** `317be3bbe003`: 2 info
  - added the optional property `handled_by/items/sent_here_calls` to the response with the `200` status
  - added the optional property `handled_by/items/sources/items/kind` to the response with the `200` status
- **2026-09-14** `2a47b07c930e`: 2 info
  - added the optional property `handled_by/items/extensions` to the response with the `200` status
  - added the optional property `handled_by/items/sources` to the response with the `200` status
- **2026-09-11** `546117b4e7b4`: 4 info
  - added the optional property `handled_by` to the response with the `200` status
  - added the optional property `intake` to the response with the `200` status
  - added the optional property `teams/items/intake_calls` to the response with the `200` status
  - added the optional property `teams/items/intake_rate_pct` to the response with the `200` status
- …earlier changes not shown

[Operation history](https://skmtc.dev/openintake/apis/openintake-api/changes/developer/transfer-metrics/get.md)

---

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