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

# Call ended

> Payload telli sends when a call reaches a terminal state

## Call outcome

Three fields describe what happened, and they are the ones to build on:

* `state` — where the call is in its lifecycle
* `status` — how it ended, once it has ended
* `follow_up` — the follow-up telli scheduled, if any

<Warning>
  `call_status` is deprecated and will be removed. Use `state`, `status`, and `follow_up` instead.
</Warning>

The legacy field maps onto the new ones like this:

| `call_status` | `state`       | `status`        | `follow_up` |
| ------------- | ------------- | --------------- | ----------- |
| `INITIATED`   | `queued`      | `null`          | `null`      |
| `RINGING`     | `ringing`     | `null`          | `null`      |
| `IN_PROGRESS` | `in_progress` | `null`          | `null`      |
| `IN_PROGRESS` | `processing`  | `null`          | `null`      |
| `COMPLETED`   | `ended`       | `connected`     | `null`      |
| `ANSWERED`    | `ended`       | `connected`     | set         |
| `NOT_REACHED` | `ended`       | `not_connected` | `null`      |
| `VOICEMAIL`   | `ended`       | `voicemail`     | `null`      |
| `ERROR`       | `ended`       | `failed`        | `null`      |

`call_status` is lossy in both directions: `IN_PROGRESS` cannot tell `in_progress` from `processing`, and `COMPLETED` and `ANSWERED` differ only by whether a follow-up was scheduled.

## Analysis fields

The payload carries two kinds of analysis, and **their shapes differ**.

`call_analysis` holds telli's built-in analysis. Every entry has a boolean `value`; entries that support supporting detail also carry `details`:

```json theme={null}
{
  "appointment": {
    "value": true,
    "details": "2025-02-18T15:30:00Z"
  }
}
```

`call_outcome` holds the [custom analysis fields](/deep-dives/call-analysis) you configure in telli. Entries are keyed by field name and carry the extracted `value` plus the schema it was validated against:

```json theme={null}
{
  "custom_lost_reason": {
    "value": "PRODUCT_TOO_EXPENSIVE",
    "fieldSchema": {
      "type": ["string", "null"],
      "enum": ["CUSTOMER_NOT_INTERESTED", "CUSTOMER_PREVIOUSLY_CONTACTED", "PRODUCT_TOO_EXPENSIVE"]
    }
  }
}
```

A `call_outcome` entry may also include `reason`, explaining why the agent chose that value, and `error` when extraction failed.

## Collected data

When the agent has [Collect Data](/deep-dives/collected-data) tasks, `collected_data` reports what was gathered and confirmed during the call:

```json theme={null}
{
  "email": {
    "status": "confirmed",
    "value": "user@example.com"
  },
  "case_number": {
    "status": "declined",
    "value": null
  }
}
```

Only `confirmed` entries carry a value you should trust. When no Collect Data tasks are configured or none were triggered, `collected_data` is empty or `null` — handle both.

## Retry attempts

Automatic retries within one scheduled sequence share a `loop_id`, and `attempt` counts up within it. Scheduling the same contact again starts a new loop, so use `contact_id` to track a contact across sequences.


## OpenAPI

````yaml openapi-v2.json webhook call_ended
openapi: 3.1.1
info:
  title: telli API
  version: 2.0.0
  description: telli V2 API
servers:
  - url: https://api.telli.com
    description: prod
security:
  - BearerAuth: []
paths: {}
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: API key authentication. Use your telli API key as the bearer token.

````