> ## Documentation Index
> Fetch the complete documentation index at: https://docs.upliftai.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Session details & transcripts

Every call and web session is a **session**, and there are two ways to read one. The **status** read is lean and safe to poll while the call is live. The **detail** read is the full record, and it fills in **only after the call ends**.

Both take the `sessionId`, the `callId` from the dispatch response. Campaign ids contain `#` and can contain `+`, so percent-encode them.

## While the call is live, poll the status

[Session status](/api-reference/sessions-%26-call-records/get-a-sessions-status) says where the call stands right now: [`state`](/voice-agents/concepts/phone-call-states), the timestamps, `failureReason`, `endedBy`, and the `variables` you passed in. It also names the [version](/voice-agents/assistants/versions) that ran: **`assistantVersion`** is its number, and **`assistantAlias`** is `prod` or `draft` when you launched by alias. To hear the call as it happens, use [live listen](/voice-agents/calling/live-listen).

## After the call, read the detail

[Session detail](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes) is the same record plus everything the call produced: the **transcript** as timed turns, the **toolCalls** with arguments and results, the **grade** from your [scorecard](/voice-agents/assistants/scorecards) with `sentiment`, a **summary** in English with a headline, bullets and facts for whoever will not read the transcript, any **conversion** or **callback** the grader extracted, and the recording.

<Warning>
  **None of that exists until the call ends.** While a call is live, the detail read does not fail. It returns `200` with the same fields as the status read, an empty `transcript`, an empty `toolCalls`, and no `grade`, `conversion`, or `callback`.
</Warning>

Here is why. The transcript and tool calls come from the agent's **session report**, uploaded as it shuts down, and land within seconds of hangup. Grading and the summary come last, and the [webhooks](/voice-agents/webhooks/overview) fire after them. The summary is written for connected calls of stored assistants only, and never for a transient one. [How it works](/voice-agents/concepts/how-it-works#after-the-call-ends) has the full sequence.

Two rules fall out of this:

* **On `call.completed`, read the detail immediately.** Everything is written by then.
* **If you poll, poll the status.** When `state` is `completed` or `failed`, read the detail. An empty `transcript` on a connected call that ended seconds ago means the report has not landed. Read again.

## What a finished call looks like

A real outreach call, trimmed to the fields that matter, with the name replaced. `gradedAt` sits just over a second after `endedAt`:

```json theme={null}
{
  "state": "completed",
  "connected": true,
  "endedBy": "callee",
  "calleeName": "احمد",
  "answeredAt": "2026-08-24T11:07:31.772Z",
  "endedAt": "2026-08-24T11:10:18.905Z",
  "durationSec": 167,
  "transcript": [
    { "speaker": "agent", "text": "کیا آپ اگلی مَکَئِی کی بوائی میں Cruiser ایک بار آزمائیں گے؟", "atSec": 58 },
    { "speaker": "caller", "text": "ازما لیں گے۔", "atSec": 65 },
    { "speaker": "agent", "text": "ٹھیک ہے، ہماری team آپ سے جلد رابطہ کر لے گی۔ بہت شکریہ۔ اللہ حافظ۔", "atSec": 169 },
    { "speaker": "caller", "text": "جی۔", "atSec": 173 }
  ],
  "toolCalls": [
    { "name": "record_outreach_interest", "arguments": "{\"status\": \"interested\", \"notes\": \"Farmer agreed to try Cruiser on next corn sowing but wanted to speak to an expert first about temperature conditions for rice pollination, so expert callback requested.\"}", "result": "{'ok': True}", "atSec": 130 }
  ],
  "grade": {
    "score": 100,
    "sentiment": "neutral",
    "gradedAt": "2026-08-24T11:10:20.320Z",
    "criteria": [
      { "ruleId": "identified", "title": "Confirmed the right person", "points": 15, "earned": 15, "passed": true },
      { "ruleId": "interest", "title": "Gauged interest / next step", "points": 15, "earned": 15, "passed": true }
    ]
  },
  "conversion": { "goalKind": "outreach", "payload": { "kind": "outreach", "status": "interested" }, "createdAt": "2026-08-24T11:10:20.320Z" },
  "callback": { "reason": "Contact asked for an expert callback about rice temperature/pollination.", "status": "open", "callbackAfter": "1-2 days", "createdAt": "2026-08-24T11:10:20.320Z" }
}
```

`endedBy: callee` says the farmer hung up. The [Call end reasons](/voice-agents/calling/call-end-reasons) page covers `agent` and the rest.

`grade.score` can be **`null`**. That means the call was graded but no criterion applied to it. Check for `null` before you compare the score to a threshold.

## The recording

Add `include_audio_url=true` to the detail read for `audioUrl`. The link is **presigned and valid for one hour**, so mint it when you need it. On a call that was never recorded, like a failed dial, the link is signed anyway and fails with a `404` or `403` when fetched.

## Finding a session

[List an assistant's sessions](/api-reference/sessions-%26-call-records/list-an-assistants-sessions) returns status-shaped rows, newest first, filtered by `channel`, `direction`, `state`, and a `createdAt` range set with `from` and `to`. Filters apply after each page is read, so a page can come back short or empty with a `nextCursor` still present. Keep paging until it is absent. Add `include_summary=true` and each row carries its call's **summary**, so a list can show what happened without a detail read per call. A row with no summary leaves the key out rather than sending `null`. Records are kept for **90 days**.
