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

# Call end reasons

A call ends and you want one answer. Did it connect, and if not, why not? The [session status](/api-reference/sessions-%26-call-records/get-a-sessions-status) read carries everything you need, and the [session detail](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes) repeats the same fields next to the transcript. Read this page once. Then trust the fields.

Start with `state`. **`completed`** means the call ran and ended normally. **`failed`** means there was no conversation, and `failureReason` says why. `endedBy`, when present, says who hung up. Everything else on the record is detail.

## The fields, and where each comes from

| Field                                                                                                                       | What it tells you                                                                              | Where it comes from                                            |
| --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| [`state`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-state)                                    | Where the call is. `completed` or `failed` after hangup.                                       | The agent and our platform events, never backwards.            |
| [`connected`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-connected)                            | Whether the call reached a conversation. `true` on `answered`, `active` and `completed`.       | `state`, at read time.                                         |
| [`outcome`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-outcome)                                | The coarse bucket for why there was no conversation. Present only when `connected` is `false`. | The stored failure reason, at read time.                       |
| [`failureReason`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-failure-reason)                   | The fine bucket. Present on `failed` calls.                                                    | The agent, from the carrier's SIP response or its own verdict. |
| [`failureReasonWithCode`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-failure-reason-with-code) | `failureReason` plus the carrier's SIP code, as in `busy:486`. Bare when there was none.       | The stored failure reason, at read time.                       |
| [`endedBy`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-ended-by)                               | Who hung up.                                                                                   | The agent, at close.                                           |

Three things to know before you build on these:

* **Switch on `failureReason`, show `failureReasonWithCode`.** `outcome` is coarser. `wrong_number`, `declined`, `network_error` and `call_failed` all read `failed` there.
* **Read the record once `state` is terminal.** [`call.completed`](/voice-agents/webhooks/overview) is the cue. Before that, neither `outcome` nor `endedBy` is a verdict.
* **A silent pickup ends `failed`, not `completed`.** The carrier answered, so `connectedAt` is set. Nobody spoke, so `answeredAt` is absent and `connected` is `false`.

## Why it failed

| `failureReason` | What happened                                                             | Usual cause                                                                 | SIP codes           |
| --------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------- |
| `no_answer`     | It rang for 30 seconds and nobody picked up.                              | Nobody near the phone, or a screened call.                                  | `408`, or none      |
| `busy`          | The line was busy.                                                        | Another call in progress.                                                   | `486`, `600`        |
| `declined`      | The callee rejected the call.                                             | A tap on decline.                                                           | `603`               |
| `wrong_number`  | The number does not exist, or cannot be routed at all.                    | A typo, a dead number, or a foreign or landline number on our mobile trunk. | `404`, `410`, `484` |
| `unreachable`   | The carrier said the phone was off or out of coverage. It never rang.     | A switched-off phone, no signal, or a dead SIM.                             | `480`               |
| `voicemail`     | An answering machine picked up. We detected it and hung up.               | A phone that diverts to voicemail.                                          | none                |
| `silent_pickup` | The line opened but nobody ever spoke.                                    | A carrier phantom answer, or a voicemail our detector missed.               | none                |
| `network_error` | Something failed on our side of the line, at the carrier or the platform. | A carrier 5xx, or no response to our dial request.                          | `5xx`, or none      |
| `call_failed`   | The failure matched no other bucket.                                      | A dispatch failure before the agent started.                                | none                |

The code after the colon in `failureReasonWithCode` is the carrier's SIP response. Reasons without one carry no code.

For campaign dialing, the default retry policy redials when `outcome` is `no_answer`, `busy`, `voicemail`, `silent_pickup`, `failed` or `unreachable`, up to three attempts. Here an unreachable phone is often only briefly off or out of coverage, so it earns a retry.

## Who hung up

[`endedBy`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-ended-by) has three values you will see:

* **`agent`.** The model called [`end_call`](/voice-agents/tools/system/end-call), or the agent closed the call itself: an unanswered silence check, or a voicemail verdict.
* **`callee`.** The customer hung up first.
* **`system`.** We forced the close. Credits ran out mid-call, or the agent hit an error on our side.

`supervisor` is reserved. An outbound call that completed with `answeredAt` and no stamp reads `callee`. That covers calls from before we stamped it, and calls where the worker died before it could. On inbound and web sessions a caller hangup leaves `endedBy` absent. Only the agent and system stamps apply there.

## The timestamps

[`createdAt`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-created-at), [`ringingAt`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-ringing-at), [`connectedAt`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-connected-at), [`answeredAt`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-answered-at) and [`endedAt`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-ended-at) are the life of the call, and [how it works](/voice-agents/concepts/how-it-works#after-the-call-ends) draws them in order. [`dialingAt`](/api-reference/sessions-%26-call-records/get-a-sessions-status#response-dialing-at) exists too, but our carrier rarely reports it. The full list, with what each one means while the call is live, is on [phone call states](/voice-agents/concepts/phone-call-states#the-timestamps).
