> ## 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.

# Phone call states

Every phone call is a session. The [`state`](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes#response-state) tells you where it is right now, the [`failureReason`](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes#response-failure-reason) tells you why it ended badly, and the timestamps tell you what actually happened on the line. This page is the decoder. Poll [session status](/api-reference/sessions-%26-call-records/get-a-sessions-status) while the call is live. After it ends, read the same fields plus the [session detail](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes).

## The states of an outbound call

| `state`      | What flipped it                                                     |
| ------------ | ------------------------------------------------------------------- |
| `dispatched` | Your dial request was accepted. The record exists from this moment. |
| `dialing`    | The carrier was contacted. Stamps `dialingAt`.                      |
| `ringing`    | The carrier reported ring progress. Stamps `ringingAt`.             |
| `answered`   | The call connected. Stamps `connectedAt`.                           |
| `completed`  | The call ended. Stamps `endedAt`.                                   |
| `failed`     | The dial failed — `failureReason` says why. Also stamps `endedAt`.  |

Three things to know before you build on these:

* **Every middle hop is optional.** Carriers differ in what they report — a call can jump straight from `dispatched` to `answered`. Never wait for a specific intermediate state.
* **`ringing` means the carrier sent ring progress, not that a phone audibly rang.** Some carriers report it milliseconds before a failure.
* **`answered` is not proof a human spoke.** The line opening sets it — voicemail and phantom answers included. The proof of a human is the `answeredAt` timestamp, which is stamped only on real callee speech.

Web sessions run `created` → `active` → `completed` instead, and inbound phone calls arrive already `active`. Neither gets `answeredAt` or `durationSec` — those belong to outbound.

## Reading the fields

**While the call is live**, poll [session status](/api-reference/sessions-%26-call-records/get-a-sessions-status) and read [`state`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-state). Ignore [`outcome`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-outcome) until the call is terminal: on a call that's still dialing or ringing, it reads `no_answer` as a placeholder, not a verdict.

**After the call**, the same endpoint carries the verdict: `state`, `failureReason`, `outcome`, [`connected`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-connected), and [`endedBy`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-ended-by). That last one is `agent`, `callee`, or `system`, or absent when nothing stamped the row. The [Call end reasons](/voice-agents/calling/call-end-reasons) page has the rules, and the campaign retry policy. For the transcript, tool calls, grade, and recording, read the [session detail](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes).

There is no webhook for dial progress, and no `call.failed` event. `call.completed` fires when the attempt ends, failed dials included, so polling is the only way to watch a dial unfold.

## The timestamps

* [`createdAt`](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes#response-created-at) — the record exists. For outbound, this is the dispatch moment.
* [`dialingAt`](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes#response-dialing-at) / [`ringingAt`](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes#response-ringing-at) — carrier progress, when reported.
* [`connectedAt`](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes#response-connected-at) — the line opened.
* [`answeredAt`](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes#response-answered-at) — a human actually spoke. Outbound only, best-effort.
* [`endedAt`](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes#response-ended-at) — hangup.
* [`durationSec`](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes#response-duration-sec) — `answeredAt` to `endedAt`. Absent whenever `answeredAt` is.

The gap between `connectedAt` and `answeredAt` is where phantom answers live: `connectedAt` set with `answeredAt` absent means the line opened and nobody spoke.
