# Call Traces

> Read a call end-to-end as a per-segment timeline that the system assembles from lifecycle events, in the console call detail and over the control-plane API.

A **call trace** is the after-the-fact story of one call. It shows every stage
the call moved through, who handled each stretch, and how long each stretch
took. The one identifier that follows the call everywhere, the **`call_sid`**,
keys all of this data. [Webhooks](/modalities/voice/api/webhooks) give you the
*live* stream of
[lifecycle events](/modalities/voice/transport-telephony/call-lifecycle) as
they occur. A trace is the **assembled** view: the same events folded into a
readable timeline. Pull up a trace when a call went wrong, when a customer
disputes what happened, or when you audit an AI→human handoff.

This page tells you how to read that timeline, its segment model, and how a
transfer changes it. It also tells you how to pull a trace two ways: in the
**console call detail** and over the **control-plane API**.

## How a trace is assembled

The engine sends a lifecycle event for every transition that a call makes on
the wire: `initiated` at the `INVITE`, `delivered` when the far end is
alerting, `established` on answer, `held` / `retrieved` on hold and resume,
`transferred` on a `REFER`, and `cleared` on the final `BYE` (with the Q.850
cause and the billable duration). You do not assemble anything yourself. The
events pipeline collects those transitions for a `call_sid` and folds them into
**segment** rows. It computes the per-stage durations (ring, queue, talk, hold,
wrap-up) as it goes. A trace is that ordered set of segments plus the roll-up
totals.

> **NOTE:**
> The trace comes from the **signalling** timeline: when the call rang, was
> answered, was held, was transferred, and cleared. For the *media* side of the
> same call (MOS, jitter, packet loss), see
> [MOS, jitter & loss](/modalities/voice/observability/mos-jitter-loss). To see
> where the latency went inside a turn, see
> [latency breakdown](/modalities/voice/observability/latency-breakdown).

## Segments: one sid, one or many stretches

A simple call is a **single segment**: one caller, one handler, start to
finish. The interesting case is a call that changed hands. **A transfer or
handoff opens a new segment.** The `call_sid` never changes. It is one call,
one CDR, one recording context. But the timeline splits at each hand-off. Thus
you can see exactly who owned which stretch and for how long.

The canonical example is a warm AI→human handoff:

```
call_sid = cs_9f21…            (one call, two segments)
│
├─ segment 0   handler: AI agent "intake-bot"        (llm)
│    ring 2s → talk 34s → transferred ▶
│
└─ segment 1   handler: "Dana Okafor"                (human)
     queue 6s → ring 3s → talk 128s → wrap-up 20s → cleared (Q.850 16)
```

Each segment has its **own** handler and its **own** stage timings. The trace's
totals sum them. Thus a transferred call reads as *one continuous call* in
reports, not as two disconnected records.

> **NOTE:**
> **What opens a new segment.** A change of *handler* opens a new segment: a
> `transferred` (`REFER`) transfer, or a
> [warm AI→human handoff](/modalities/voice/concepts/handoffs). Holds, resumes
> (`held` / `retrieved`), and mid-call media renegotiation (`reinvited`) do
> **not** open a new segment. The trace records them *inside* the current segment
> as hold time and flags. A handler has the tag **`llm`** (an AI agent owned the
> stretch) or **`human`**. Thus you can split talk time by who was on the line.

### What each segment tells you

| Field on a segment | What it means |
|---|---|
| `handlerKind` / `handlerDisplayName` | Who owned this stretch: an AI agent (`llm`) or a named human (`human`). |
| `ringTimeSec` | The time the leg spent alerting before it was answered. |
| `queueTimeSec` | The time the call waited in an ACD skill queue before a handler picked up (human segments). |
| `talkTimeSec` | The two-way audio time. This is the part of the call that was a conversation. |
| `holdTimeSec` | The time this segment spent on hold (summed across every hold). |
| `acwTimeSec` | The after-call work (wrap-up) time a human agent spent after the caller left. |
| `durationSec` | The wall-clock length of the segment. |
| `transferred` / `held` / `conference` | Flags: this stretch ended in a transfer, was held, or fanned out to a supervisor. |
| `disposition` / `hangupReason` | The wrap-up code and the Q.850 clearing cause. |
| `recordingUrl` | A link to the segment's recording, when recording is enabled. |

## Pull a trace in the console

The console call detail is the fastest way to read one call.

  1. **Open the calls report**
In the Contact Center console at `agent.firstbyte.fast`, go to
    **Supervisor → All calls**. Filter to the time window. Filter to the number, agent, or
    direction that you want.
  2. **Open the call**
Click the call to open **Call Detail**. The header shows the `call_sid`,
    the direction, the caller and callee, and the roll-up totals (total talk,
    hold, and duration; whether the call was transferred).
  3. **Read the segment timeline**
The body shows the segments oldest-first, top to bottom. Each row names
    its handler (AI or human), its stage timings, and its disposition. A
    multi-segment call shows the transfer hop inline: the AI segment, then the
    human segment that it handed to.
  4. **Play the recording**
When recording is on, each segment links its own recording. The caller is
    on the left channel and the handler is on the right channel. Thus you can
    listen to exactly the stretch that you audit.

> **NOTE:**
> **In-flight calls.** The system does not fold a call into segments while the
> call is still on the wire. Call detail shows a **live view** for it: the
> direction, the tenant, the bound agent, and the start time. Thus an active call
> is not a blank page. The full segment timeline appears when the call clears.

## Pull a trace over the API

The same assembled trace is available as a **control-plane read**. The read is
keyed by `call_sid`. Your API key scopes it to your workspace. Use it to feed a
QA tool, a dispute workflow, or your own reports — anything that needs the
structured timeline instead of the raw event stream.

> **NOTE:**
> This is a control-plane *reporting* read. It is different from
> [`calls.get`](/modalities/voice/api/calls#get-a-call), which returns a
> point-in-time `Call` **snapshot** (current status), not the historical
> timeline. Fetch the trace from the control-plane API host with your API key.
> The response is the same data that the console shows.

**Fetch a trace:**
```ts
// Assembled per-segment timeline for one call, keyed by call_sid.
const res = await fetch(
  `https://engine.firstbyte.fast/reports/calls/${callSid}`,
  { headers: { authorization: `Bearer ${process.env.CLUTCHCALL_API_KEY}` } },
);
const trace = await res.json();

for (const seg of trace.segments) {
  console.log(
    `#${seg.segment}`,
    seg.handlerKind,                       // "llm" | "human"
    seg.handlerDisplayName,
    `talk=${seg.talkTimeSec}s hold=${seg.holdTimeSec}s`,
    seg.transferred ? "→ transferred" : "",
  );
}
console.log("total talk:", trace.totals.talkTimeSec);
```

**Fetch a trace:**
```python
import os, requests

res = requests.get(
    f"https://engine.firstbyte.fast/reports/calls/{call_sid}",
    headers={"authorization": f"Bearer {os.environ['CLUTCHCALL_API_KEY']}"},
)
trace = res.json()

for seg in trace["segments"]:
    print(seg["segment"], seg["handlerKind"], seg["handlerDisplayName"],
          f"talk={seg['talkTimeSec']}s hold={seg['holdTimeSec']}s",
          "→ transferred" if seg["transferred"] else "")
print("total talk:", trace["totals"]["talkTimeSec"])
```

### Response shape

- **`callId`** (`string`) — The `call_sid` that this trace belongs to.
- **`segments`** (`array`) — The ordered segments, oldest first. Each segment has `segment` (index), `startedAt` / `endedAt`, `handlerId` / `handlerKind` (`llm` | `human`) / `handlerDisplayName`, the stage timings (`ringTimeSec`, `queueTimeSec`, `talkTimeSec`, `holdTimeSec`, `acwTimeSec`, `durationSec`), the flags (`transferred`, `held`, `conference`), `disposition`, `hangupReason`, and `recordingUrl`.
- **`totals`** (`object`) — The roll-up across all segments: `segments` (count), `talkTimeSec`, `holdTimeSec`, `ringTimeSec`, `queueTimeSec`, `durationSec`, and whether the call was `transferred` or `conference`d anywhere.
- **`live`** (`object | null`) — Present only while the call is still on the wire. It shows the direction, the bound agent, and the start time for an in-flight call whose segments are not assembled yet. It is `null` when the call cleared and segments exist.

## When a trace looks wrong

| Symptom | Likely cause | What to check |
|---|---|---|
| The trace is empty and `live` is null | The `call_sid` never landed, or it belongs to another workspace | Check the sid against the calls list. A trace is tenant-scoped. A sid from another workspace returns not-found. |
| The call shows one segment but you expected a transfer | The transfer was a `held`/`reinvited` (same handler), not a handler change | Only a handler change (`transferred` / handoff) splits segments. Holds stay in the segment. |
| Segments exist but the timings are zero | The system still assembles the call, or the call never reached two-way media | An in-flight call shows as `live` first. A call that failed before answer has no talk time. |
| The peer transfer was acknowledged but the onward leg is missing | The *outbound* leg from a **peer-initiated** `REFER` is only partially wired | Prefer [operator-initiated transfers](/modalities/voice/api/calls#transfer-a-call). The peer-REFER onward leg possibly does not complete. |
| The recording link is missing on a segment | Recording was not enabled for that leg, or the upload did not finish | Check that recording is on for the trunk or agent. Recordings land shortly after `cleared`. |

## Related

  - **[Call lifecycle](/modalities/voice/transport-telephony/call-lifecycle)** — The signalling state machine that every event in a trace comes from.
  - **[Webhooks & events](/modalities/voice/api/webhooks)** — The live event stream that the system assembles a trace from.
  - **[Handoffs](/modalities/voice/concepts/handoffs)** — How a transfer or warm AI→human handoff opens a new segment.
  - **[MOS, jitter & loss](/modalities/voice/observability/mos-jitter-loss)** — The media-quality side of the same call.
  - **[Dashboards](/modalities/voice/observability/dashboards)** — Fleet-wide views built from the same call data.
  - **[Calls API](/modalities/voice/api/calls)** — Originate, get, transfer, and hang up calls. This is the live control
    plane.
